数据库面试题

# 数据库面试题

# 🗄️ 数据库基础

# 1. 关系型数据库

Q: ACID特性是什么?

A:

  • 原子性(Atomicity):事务是不可分割的最小执行单位
  • 一致性(Consistency):事务执行前后数据库保持一致状态
  • 隔离性(Isolation):并发事务之间相互隔离
  • 持久性(Durability):事务提交后,对数据的修改是永久的

Q: 数据库事务隔离级别有哪些?

A:

  1. 读未提交(Read Uncommitted)

    • 可能出现:脏读、不可重复读、幻读
  2. 读已提交(Read Committed)

    • 可能出现:不可重复读、幻读
    • Oracle默认级别
  3. 可重复读(Repeatable Read)

    • 可能出现:幻读
    • MySQL默认级别
  4. 串行化(Serializable)

    • 最高隔离级别,无并发问题
-- 设置事务隔离级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 查看当前隔离级别
SELECT @@transaction_isolation;

# 2. 索引优化

Q: 索引的类型和特点?

A:

  • B+树索引:最常用,支持范围查询
  • 哈希索引:等值查询快,不支持范围查询
  • 全文索引:用于文本搜索
  • 空间索引:用于地理位置数据

Q: 什么情况下索引会失效?

A:

  1. 使用函数或表达式
  2. 隐式类型转换
  3. 使用NOT、!=、<>
  4. LIKE以通配符开头
  5. OR条件(部分情况)
  6. 复合索引不遵循最左前缀原则
-- 索引失效示例
-- 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:

  1. 分析执行计划
EXPLAIN SELECT * FROM orders o 
JOIN customers c ON o.customer_id = c.id 
WHERE o.order_date >= '2024-01-01';
  1. 添加合适索引
-- 为经常查询的列添加索引
CREATE INDEX idx_order_date ON orders(order_date);
CREATE INDEX idx_customer_id ON orders(customer_id);
  1. 重写查询语句
-- 优化前:使用子查询
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';
  1. 分页优化
-- 优化前: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:

  1. 主库写入binlog:所有写操作记录到二进制日志
  2. 从库IO线程:连接主库,读取binlog并写入relay log
  3. 从库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. 索引优化
  2. 查询优化
  3. 表结构优化
  4. 配置优化
  5. 硬件优化
-- 表结构优化示例
-- 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)
);

# 💡 面试技巧

  1. 理论结合实践:不仅要知道概念,还要能说出实际应用
  2. 性能意识:总是考虑性能影响和优化方案
  3. 场景分析:根据不同场景选择合适的技术方案
  4. 问题排查:掌握常见问题的排查方法

# 📚 推荐资源

  • 《高性能MySQL》
  • 《MySQL技术内幕》
  • 《Redis设计与实现》
  • 《MongoDB权威指南》