数据库面试题
# 数据库面试题
# 🗄️ 数据库基础
# 1. 关系型数据库
Q: ACID特性是什么?
A:
- 原子性(Atomicity):事务是不可分割的最小执行单位
- 一致性(Consistency):事务执行前后数据库保持一致状态
- 隔离性(Isolation):并发事务之间相互隔离
- 持久性(Durability):事务提交后,对数据的修改是永久的
Q: 数据库事务隔离级别有哪些?
A:
读未提交(Read Uncommitted)
- 可能出现:脏读、不可重复读、幻读
读已提交(Read Committed)
- 可能出现:不可重复读、幻读
- Oracle默认级别
可重复读(Repeatable Read)
- 可能出现:幻读
- MySQL默认级别
串行化(Serializable)
- 最高隔离级别,无并发问题
-- 设置事务隔离级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 查看当前隔离级别
SELECT @@transaction_isolation;
# 2. 索引优化
Q: 索引的类型和特点?
A:
- B+树索引:最常用,支持范围查询
- 哈希索引:等值查询快,不支持范围查询
- 全文索引:用于文本搜索
- 空间索引:用于地理位置数据
Q: 什么情况下索引会失效?
A:
- 使用函数或表达式
- 隐式类型转换
- 使用NOT、!=、<>
- LIKE以通配符开头
- OR条件(部分情况)
- 复合索引不遵循最左前缀原则
-- 索引失效示例
-- 1. 使用函数
SELECT * FROM users WHERE UPPER(name) = 'JOHN'; -- 索引失效
SELECT * FROM users WHERE name = 'John'; -- 索引有效
-- 2. 隐式类型转换
SELECT * FROM users WHERE phone = 12345678; -- 索引失效(phone是varchar)
SELECT * FROM users WHERE phone = '12345678'; -- 索引有效
-- 3. 复合索引最左前缀
CREATE INDEX idx_name_age_city ON users(name, age, city);
SELECT * FROM users WHERE name = 'John' AND age = 25; -- 索引有效
SELECT * FROM users WHERE age = 25 AND city = 'Beijing'; -- 索引失效
# 3. SQL优化
Q: 如何优化慢查询?
A:
- 分析执行计划
EXPLAIN SELECT * FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.order_date >= '2024-01-01';
- 添加合适索引
-- 为经常查询的列添加索引
CREATE INDEX idx_order_date ON orders(order_date);
CREATE INDEX idx_customer_id ON orders(customer_id);
- 重写查询语句
-- 优化前:使用子查询
SELECT * FROM products
WHERE category_id IN (SELECT id FROM categories WHERE name = 'Electronics');
-- 优化后:使用JOIN
SELECT p.* FROM products p
JOIN categories c ON p.category_id = c.id
WHERE c.name = 'Electronics';
- 分页优化
-- 优化前:OFFSET性能差
SELECT * FROM users ORDER BY id LIMIT 10000, 20;
-- 优化后:使用游标
SELECT * FROM users WHERE id > 10000 ORDER BY id LIMIT 20;
# 🔄 MySQL相关
# 1. 存储引擎
Q: InnoDB和MyISAM的区别?
A:
特性 | InnoDB | MyISAM |
---|---|---|
事务支持 | 支持 | 不支持 |
锁粒度 | 行锁 | 表锁 |
外键 | 支持 | 不支持 |
崩溃恢复 | 支持 | 不支持 |
MVCC | 支持 | 不支持 |
存储空间 | 较大 | 较小 |
# 2. 主从复制
Q: MySQL主从复制的原理?
A:
- 主库写入binlog:所有写操作记录到二进制日志
- 从库IO线程:连接主库,读取binlog并写入relay log
- 从库SQL线程:读取relay log并执行SQL语句
-- 主库配置
[mysqld]
server-id = 1
log-bin = mysql-bin
binlog-format = ROW
-- 从库配置
[mysqld]
server-id = 2
relay-log = mysql-relay-bin
read-only = 1
-- 设置主从关系
CHANGE MASTER TO
MASTER_HOST='192.168.1.100',
MASTER_USER='replication',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=154;
START SLAVE;
# 3. 分库分表
Q: 什么时候需要分库分表?如何实现?
A: 需要分库分表的场景:
- 单表数据量超过1000万
- 单库连接数不够
- 磁盘IO成为瓶颈
分片策略:
// 水平分表示例
@Component
public class ShardingStrategy {
// 按用户ID分表
public String getTableName(String baseTableName, Long userId) {
int shardIndex = (int) (userId % 8); // 分8张表
return baseTableName + "_" + shardIndex;
}
// 按时间分表
public String getTableNameByDate(String baseTableName, Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");
return baseTableName + "_" + sdf.format(date);
}
// 按哈希分库
public String getDatabaseName(String baseDatabaseName, String shardKey) {
int hash = shardKey.hashCode();
int shardIndex = Math.abs(hash) % 4; // 分4个库
return baseDatabaseName + "_" + shardIndex;
}
}
# 📊 NoSQL数据库
# 1. Redis
Q: Redis的数据类型和应用场景?
A:
- String:缓存、计数器、分布式锁
- Hash:用户信息、购物车
- List:消息队列、最新列表
- Set:标签、好友关系
- ZSet:排行榜、延时队列
// Redis应用示例
@Service
public class RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 分布式锁
public boolean tryLock(String key, String value, long expireTime) {
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
return Boolean.TRUE.equals(result);
}
// 计数器
public Long increment(String key) {
return redisTemplate.opsForValue().increment(key);
}
// 排行榜
public void addToLeaderboard(String leaderboard, String member, double score) {
redisTemplate.opsForZSet().add(leaderboard, member, score);
}
public Set<Object> getTopN(String leaderboard, int n) {
return redisTemplate.opsForZSet().reverseRange(leaderboard, 0, n - 1);
}
}
Q: Redis持久化机制?
A:
- RDB:快照持久化,适合备份和灾难恢复
- AOF:追加文件,记录每个写操作,数据安全性更高
# RDB配置
save 900 1 # 900秒内至少1个key变化
save 300 10 # 300秒内至少10个key变化
save 60 10000 # 60秒内至少10000个key变化
# AOF配置
appendonly yes
appendfsync everysec # 每秒同步一次
# 2. MongoDB
Q: MongoDB的特点和适用场景?
A: 特点:
- 文档型数据库,使用BSON格式
- 支持动态模式
- 内置分片和复制
- 强大的查询语言
适用场景:
- 内容管理系统
- 实时分析
- 物联网数据存储
- 日志系统
// MongoDB查询示例
// 查找年龄大于25的用户
db.users.find({age: {$gt: 25}});
// 聚合查询
db.orders.aggregate([
{$match: {status: "completed"}},
{$group: {
_id: "$customerId",
totalAmount: {$sum: "$amount"},
orderCount: {$sum: 1}
}},
{$sort: {totalAmount: -1}},
{$limit: 10}
]);
# 🔍 数据库设计
# 1. 范式设计
Q: 数据库范式有哪些?
A:
- 第一范式(1NF):字段不可再分
- 第二范式(2NF):消除部分依赖
- 第三范式(3NF):消除传递依赖
- BCNF:消除主属性对候选键的部分和传递依赖
-- 违反2NF的例子
CREATE TABLE order_items (
order_id INT,
product_id INT,
product_name VARCHAR(100), -- 依赖于product_id,违反2NF
quantity INT,
price DECIMAL(10,2)
);
-- 符合2NF的设计
CREATE TABLE orders (
order_id INT PRIMARY KEY,
customer_id INT,
order_date DATE
);
CREATE TABLE products (
product_id INT PRIMARY KEY,
product_name VARCHAR(100),
price DECIMAL(10,2)
);
CREATE TABLE order_items (
order_id INT,
product_id INT,
quantity INT,
PRIMARY KEY (order_id, product_id)
);
# 2. 性能优化
Q: 数据库性能优化的方法?
A:
- 索引优化
- 查询优化
- 表结构优化
- 配置优化
- 硬件优化
-- 表结构优化示例
-- 1. 选择合适的数据类型
CREATE TABLE users (
id INT UNSIGNED AUTO_INCREMENT, -- 使用UNSIGNED
name VARCHAR(50) NOT NULL, -- 合适的长度
email VARCHAR(100) UNIQUE,
status TINYINT DEFAULT 1, -- 使用TINYINT而不是INT
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
INDEX idx_email (email),
INDEX idx_status_created (status, created_at)
);
-- 2. 分区表
CREATE TABLE sales (
id INT AUTO_INCREMENT,
sale_date DATE,
amount DECIMAL(10,2),
PRIMARY KEY (id, sale_date)
) PARTITION BY RANGE (YEAR(sale_date)) (
PARTITION p2022 VALUES LESS THAN (2023),
PARTITION p2023 VALUES LESS THAN (2024),
PARTITION p2024 VALUES LESS THAN (2025)
);
# 💡 面试技巧
- 理论结合实践:不仅要知道概念,还要能说出实际应用
- 性能意识:总是考虑性能影响和优化方案
- 场景分析:根据不同场景选择合适的技术方案
- 问题排查:掌握常见问题的排查方法
# 📚 推荐资源
- 《高性能MySQL》
- 《MySQL技术内幕》
- 《Redis设计与实现》
- 《MongoDB权威指南》