数据库设计和查询优化、运维

文章目录

  • [数据库设计 + SQL 查询优化 面试50题(含标准答案,MySQL为主,后端通用)](#数据库设计 + SQL 查询优化 面试50题(含标准答案,MySQL为主,后端通用))
    • [一、数据库基础 & 表设计规范(1--15)](#一、数据库基础 & 表设计规范(1–15))
      • [1. 数据库三大范式是什么?作用?](#1. 数据库三大范式是什么?作用?)
      • [2. 什么是反范式?什么时候用?](#2. 什么是反范式?什么时候用?)
      • [3. 主键、唯一索引、普通索引区别](#3. 主键、唯一索引、普通索引区别)
      • [4. 聚簇索引与非聚簇索引(InnoDB)](#4. 聚簇索引与非聚簇索引(InnoDB))
      • [5. 为什么不建议用业务字段做主键(手机号/订单号)?](#5. 为什么不建议用业务字段做主键(手机号/订单号)?)
      • [6. 逻辑删除设计思路与优缺点](#6. 逻辑删除设计思路与优缺点)
      • [7. 一张标准业务表必备通用字段](#7. 一张标准业务表必备通用字段)
      • [8. 金额为什么不用float/double,用decimal?](#8. 金额为什么不用float/double,用decimal?)
      • [9. TEXT、VARCHAR 怎么选择?](#9. TEXT、VARCHAR 怎么选择?)
      • [10. 物理外键为什么项目基本不用?](#10. 物理外键为什么项目基本不用?)
      • [11. 联合索引最左匹配原则](#11. 联合索引最左匹配原则)
      • [12. 索引失效常见场景(列举4种)](#12. 索引失效常见场景(列举4种))
      • [13. 索引是不是越多越好?弊端?](#13. 索引是不是越多越好?弊端?)
      • [14. 什么是覆盖索引?优势?](#14. 什么是覆盖索引?优势?)
      • [15. 大表优化方案(建表阶段)](#15. 大表优化方案(建表阶段))
    • [二、SQL 查询编写规范与坑(16--30)](#二、SQL 查询编写规范与坑(16–30))
      • [16. 为什么禁止 select *?](#16. 为什么禁止 select *?)
      • [17. limit offset 大分页慢如何优化?](#17. limit offset 大分页慢如何优化?)
      • [18. IN 条件数据量太大有什么问题?如何处理?](#18. IN 条件数据量太大有什么问题?如何处理?)
      • [19. inner join / left join 使用场景区分](#19. inner join / left join 使用场景区分)
      • [20. 子查询和JOIN哪个性能好?](#20. 子查询和JOIN哪个性能好?)
      • [21. where 条件顺序有影响吗?](#21. where 条件顺序有影响吗?)
      • [22. distinct 和 group by 取舍](#22. distinct 和 group by 取舍)
      • [23. union 和 union all 区别,推荐哪个?](#23. union 和 union all 区别,推荐哪个?)
      • [24. 隐式类型转换举例与危害](#24. 隐式类型转换举例与危害)
      • [25. count(*) / count(1) / count(字段) 区别](#25. count(*) / count(1) / count(字段) 区别)
      • [26. order by 不走索引怎么优化?](#26. order by 不走索引怎么优化?)
      • [27. group by 产生临时表、文件排序怎么解决?](#27. group by 产生临时表、文件排序怎么解决?)
      • [28. delete / update 必须注意什么?](#28. delete / update 必须注意什么?)
      • [29. 批量插入优化手段](#29. 批量插入优化手段)
      • [30. exists 和 in 怎么选?](#30. exists 和 in 怎么选?)
    • [三、执行计划 & 调优工具(31--40)](#三、执行计划 & 调优工具(31–40))
      • [31. explain 关键字段有哪些?](#31. explain 关键字段有哪些?)
      • [32. type 性能等级从优到劣?](#32. type 性能等级从优到劣?)
      • [33. Extra 中 Using filesort、Using temporary 含义?](#33. Extra 中 Using filesort、Using temporary 含义?)
      • [34. key_len 代表什么?有什么用?](#34. key_len 代表什么?有什么用?)
      • [35. 什么是覆盖索引在explain里标识?](#35. 什么是覆盖索引在explain里标识?)
      • [36. 如何定位慢SQL?](#36. 如何定位慢SQL?)
      • [37. show profile 作用?](#37. show profile 作用?)
      • [38. MySQL 缓冲池 buffer pool 作用?](#38. MySQL 缓冲池 buffer pool 作用?)
      • [39. 什么是回表?如何避免?](#39. 什么是回表?如何避免?)
      • [40. 索引下推ICP是什么?作用?](#40. 索引下推ICP是什么?作用?)
    • 四、事务、锁、高并发、分库分表(41--50)
      • [41. 事务四大特性 ACID](#41. 事务四大特性 ACID)
      • [42. 事务四大隔离级别及问题](#42. 事务四大隔离级别及问题)
      • [43. MVCC 实现原理,作用?](#43. MVCC 实现原理,作用?)
      • [44. 行锁、表锁、意向锁区别](#44. 行锁、表锁、意向锁区别)
      • [45. 死锁产生条件与解决方案](#45. 死锁产生条件与解决方案)
      • [46. 分库分表解决什么问题?带来什么新问题?](#46. 分库分表解决什么问题?带来什么新问题?)
      • [47. 分表分片规则常用两种](#47. 分表分片规则常用两种)
      • [48. 分布式事务三种常见方案](#48. 分布式事务三种常见方案)
      • [49. MySQL 主从复制作用、原理](#49. MySQL 主从复制作用、原理)
      • [50. 读写分离如何落地,注意什么坑?](#50. 读写分离如何落地,注意什么坑?)
  • [数据库设计 + SQL查询编写 20条规范好习惯(分设计10条、SQL编写10条)](#数据库设计 + SQL查询编写 20条规范好习惯(分设计10条、SQL编写10条))
  • [数据库(设计+SQL优化+运维)高频面试10题 标准详解](#数据库(设计+SQL优化+运维)高频面试10题 标准详解)
  • 结合具体业务场景给出10个数据库设计查询运维案例,详解原理
      • [1. 电商订单表:大表分库分表与水平拆分](#1. 电商订单表:大表分库分表与水平拆分)
      • [2. 用户积分流水:深分页(Deep Pagination)优化](#2. 用户积分流水:深分页(Deep Pagination)优化)
      • [3. 文章/商品标签:多对多关联查询优化](#3. 文章/商品标签:多对多关联查询优化)
      • [4. 秒杀库存扣减:热点行更新与锁竞争](#4. 秒杀库存扣减:热点行更新与锁竞争)
      • [5. 历史订单归档:大表DDL与数据清理](#5. 历史订单归档:大表DDL与数据清理)
      • [6. 模糊搜索:前缀索引与倒排索引](#6. 模糊搜索:前缀索引与倒排索引)
      • [7. 状态机流转:乐观锁与版本号](#7. 状态机流转:乐观锁与版本号)
      • [8. 报表统计:读写分离与列式存储](#8. 报表统计:读写分离与列式存储)
      • [9. 分布式ID:雪花算法与数据库号段](#9. 分布式ID:雪花算法与数据库号段)
      • [10. 连接池耗尽:慢SQL与运维监控](#10. 连接池耗尽:慢SQL与运维监控)
      • 总结:数据库设计的核心原则

数据库设计 + SQL 查询优化 面试50题(含标准答案,MySQL为主,后端通用)

一、数据库基础 & 表设计规范(1--15)

1. 数据库三大范式是什么?作用?

1NF:列原子性,字段不可再拆分;

2NF:消除部分依赖,主键完全决定所有字段;

3NF:消除传递依赖,非主键字段不依赖其他非主键。

作用:减少数据冗余、避免增删改异常;实际业务常适度反范式提升查询性能。

2. 什么是反范式?什么时候用?

主动冗余字段、拆分/合并表打破3NF。

场景:多表JOIN频繁、读远大于写;如订单冗余用户姓名,避免每次关联用户表。

3. 主键、唯一索引、普通索引区别

主键:非空+唯一,一张表只能一个,InnoDB聚簇索引;

唯一索引:字段不能重复,允许一个NULL;

普通索引:仅加速查询,允许重复、NULL。

4. 聚簇索引与非聚簇索引(InnoDB)

聚簇:主键和数据行存在一起,叶子节点是完整数据;一张表仅1个;

非聚簇(二级索引):叶子存主键值,回表查询拿完整数据。

5. 为什么不建议用业务字段做主键(手机号/订单号)?

  1. 业务字段可变,修改主键代价极大;
  2. 字符串主键占用空间大,二级索引膨胀;
  3. 无序字符串造成页分裂,插入性能暴跌;
    推荐自增BIGINT无符号主键。

6. 逻辑删除设计思路与优缺点

字段 is_deleted tinyint default 0,0正常1删除,查询必带 is_deleted=0

优点:数据可恢复、便于审计;缺点:所有SQL强制加条件,索引效率轻微下降。

7. 一张标准业务表必备通用字段

id 主键、create_time、update_time、create_user、update_user、is_deleted。

8. 金额为什么不用float/double,用decimal?

浮点存在二进制精度丢失;DECIMAL定点数精确存储,DECIMAL(18,2) 适配财务金额。

9. TEXT、VARCHAR 怎么选择?

短文本、高频查询用VARCHAR;大内容(文章、备注)用TEXT,独立分表存放,避免拖慢主表查询。

10. 物理外键为什么项目基本不用?

  1. 高并发易触发锁、死锁;
  2. 分库分表无法使用;
  3. 耦合太强,删改流程受限;
    解决方案:应用层代码做关联校验。

11. 联合索引最左匹配原则

索引(a,b,c),where条件必须出现最左前列才能走索引;

where b=?where c=? 无法走完整联合索引。

12. 索引失效常见场景(列举4种)

  1. 索引列使用函数运算;
  2. 隐式类型转换;
  3. like %前缀模糊匹配;
  4. or连接无索引字段、not != <> 判断。

13. 索引是不是越多越好?弊端?

不是。索引占用磁盘;增删改时需要同步维护所有索引,写性能大幅下降。

14. 什么是覆盖索引?优势?

索引包含查询所需全部字段,无需回表。

优势:减少IO,查询速度极大提升,EXPLAIN Extra 显示Using index。

15. 大表优化方案(建表阶段)

  1. 冷热数据分表;
  2. 大字段拆分扩展表;
  3. 合理分区表;
  4. 提前设计分库分表规则;
  5. 精简字段类型,减少行宽度。

二、SQL 查询编写规范与坑(16--30)

16. 为什么禁止 select *?

  1. 读取无用字段,浪费IO、网络;
  2. 无法使用覆盖索引,必回表;
  3. 表新增字段后业务出现兼容异常。

17. limit offset 大分页慢如何优化?

偏移量大时数据库扫描大量行;

优化:主键分页 where id > 上次最大id limit 10

18. IN 条件数据量太大有什么问题?如何处理?

IN值过多解析耗时、优化器失效;

处理:分批查询、临时表JOIN、批量拆分。

19. inner join / left join 使用场景区分

inner join:两边匹配数据才返回;

left join:左表全部保留,右表无匹配补NULL;

多表关联优先小表驱动大表。

20. 子查询和JOIN哪个性能好?

MySQL优化器多数情况下JOIN优于子查询;多层嵌套子查询容易生成临时表,效率低,优先改写JOIN。

21. where 条件顺序有影响吗?

MySQL优化器会自动调整条件顺序,无关人工书写顺序;但索引字段尽量前置。

22. distinct 和 group by 取舍

仅去重用distinct;需要聚合统计(sum/count)用group by;数据量大时group by可配合索引优化。

23. union 和 union all 区别,推荐哪个?

union 自动去重排序,性能差;union all 直接拼接无去重;无重复数据必用union all。

24. 隐式类型转换举例与危害

例:varchar字段传数字 phone = 13800138000,字段隐式转数字,索引失效。

危害:查询全表扫描,性能雪崩。

25. count(*) / count(1) / count(字段) 区别

InnoDB下count(*)、count(1)性能几乎一致;

count(字段)会忽略NULL值,且需要读取字段数据,最慢。

26. order by 不走索引怎么优化?

排序字段加入联合索引最右侧;避免函数、混合升降序;超大分页排序用主键分页。

27. group by 产生临时表、文件排序怎么解决?

分组字段建立联合索引,让分组、过滤、排序全部走索引,避免Using temporary、Using filesort。

28. delete / update 必须注意什么?

  1. 禁止无where全表操作;
  2. 执行前先用同等where select校验数据;
  3. 批量更新分批执行,防止长事务锁表。

29. 批量插入优化手段

  1. 多值INSERT批量写入;
  2. 关闭自动提交,手动事务批量提交;
  3. load data 导入超大批量数据。

30. exists 和 in 怎么选?

子查询数据量大用exists(外层驱动内层);子查询数据少用in;not in存在NULL时结果异常,优先not exists。

三、执行计划 & 调优工具(31--40)

31. explain 关键字段有哪些?

id、select_type、table、type、key、key_len、ref、rows、Extra。

32. type 性能等级从优到劣?

system > const > eq_ref > ref > range > index > ALL

ALL全表扫描最差,调优目标至少range/ref。

33. Extra 中 Using filesort、Using temporary 含义?

Using filesort:无法使用索引排序,磁盘文件排序,性能差;

Using temporary:分组/去重创建临时表,需要优化索引。

34. key_len 代表什么?有什么用?

索引使用的字节长度,判断联合索引是否完整使用,判断字段类型是否冗余。

35. 什么是覆盖索引在explain里标识?

Extra出现 Using index,无需回表,性能最优。

36. 如何定位慢SQL?

开启慢查询日志slow_query_log;设置long_query_time阈值;pt-query-digest分析日志;业务监控平台抓取耗时SQL。

37. show profile 作用?

查看SQL执行各阶段耗时(磁盘IO、排序、锁等待),精准定位慢在哪一步。

38. MySQL 缓冲池 buffer pool 作用?

缓存索引和数据页,减少磁盘IO,核心性能组件;合理设置为物理内存50%~70%。

39. 什么是回表?如何避免?

二级索引仅存主键,拿到主键再查聚簇索引拿完整数据叫回表;用覆盖索引避免。

40. 索引下推ICP是什么?作用?

MySQL5.6+优化;存储引擎层过滤索引条件,减少回表次数,降低IO开销。

四、事务、锁、高并发、分库分表(41--50)

41. 事务四大特性 ACID

原子Atomic、一致性Consistent、隔离Isolated、持久性Durable。

42. 事务四大隔离级别及问题

读未提交:脏读、不可重复读、幻读;

读已提交:不可重复读、幻读;

可重复读(InnoDB默认):幻读;

串行化:全部解决,并发极低。

43. MVCC 实现原理,作用?

多版本并发控制,undo日志+read view;不加锁实现读写并发,解决快照读隔离问题。

44. 行锁、表锁、意向锁区别

行锁:InnoDB,索引有效才锁行,并发高;无索引退化为表锁;

表锁:MyISAM,全表锁定,并发差;

意向锁:表级辅助锁,协调行锁与表锁冲突检测。

45. 死锁产生条件与解决方案

条件:互斥、持有等待、不可剥夺、循环等待;

解决:统一资源加锁顺序、缩短事务、超时重试、死锁检测。

46. 分库分表解决什么问题?带来什么新问题?

解决:单表数据量过大、读写压力瓶颈;

引入问题:分布式事务、跨表JOIN、分页排序、ID唯一、扩容迁移。

47. 分表分片规则常用两种

水平分表:按id/时间哈希拆分数据行;

垂直分表:冷热字段拆分,大字段独立表。

48. 分布式事务三种常见方案

  1. 本地消息表(可靠消息最终一致性);
  2. Seata AT/TCC/SAGA;
  3. MQ事务消息。

49. MySQL 主从复制作用、原理

作用:读写分离、数据备份、故障切换;

原理:binlog二进制日志,主库记录变更,从库IO线程拉取,SQL线程重放。

50. 读写分离如何落地,注意什么坑?

写走主库,读走从库;

坑:主从延迟导致读取旧数据;重要实时查询强制走主库。

数据库设计 + SQL查询编写 20条规范好习惯(分设计10条、SQL编写10条)

一、数据库表结构设计好习惯(10条)

  1. 统一命名规范,杜绝拼音、中文、特殊字符
    表、字段全英文小写,下划线分隔;禁止userName驼峰、用户表user-name;示例:user_infoorder_id
  2. 每张表必须设计主键,优先自增数字ID,不使用业务字段做主键
    主键id BIGINT UNSIGNED AUTO_INCREMENT,订单号、手机号等业务字段只建唯一索引,不充当主键。
  3. 核心业务表增加逻辑删除字段,不物理删数据
    统一is_deleted TINYINT DEFAULT 0,0正常1删除,所有查询强制带上where is_deleted=0,保留数据溯源。
  4. 通用审计字段每张表标配
    create_time DATETIMEupdate_time DATETIME,新增自动填创建时间,更新自动刷新修改时间。
  5. 合理选择字段数据类型,能小不大、能整不浮点
    状态用TINYINT不用INT;金额用DECIMAL(10,2)禁止FLOAT/DOUBLE;字符串按需选VARCHAR长度,不统一255。
  6. 字段加注释,表加注释,避免后期看不懂业务含义
    COMMENT '用户唯一ID',枚举状态在注释标注:0正常,1禁用,2冻结。
  7. 外键逻辑在代码层控制,数据库不创建物理外键
    物理外键容易锁表、影响分库分表、删改卡顿,应用程序做关联校验代替数据库约束。
  8. 高频查询条件提前建立索引,避免全表扫描
    where、join、order by、group by字段建索引;联合索引遵循最左匹配原则。
  9. 区分普通索引、唯一索引,重复业务字段加唯一约束
    手机号、用户名、身份证等不重复字段创建UNIQUE INDEX,防止脏数据重复插入。
  10. 大字段独立拆分,text/blob不放在高频查询表
    文章内容、图片大文本单独建扩展表,主表只存短字段,减少IO开销,提升查询速度。

二、SQL查询语句编写好习惯(10条)

  1. *禁止SELECT ,只查询业务需要的指定字段
    减少网络传输、避免冗余大字段加载,后期表扩容不会出现多余字段兼容问题。
  2. WHERE条件禁止对索引字段做函数运算,会失效索引
    错误:YEAR(create_time) = 2026;正确:create_time BETWEEN '2026-01-01' AND '2026-12-31'
  3. 模糊查询前缀不加%,%xxx会完全放弃索引
    name LIKE '%张三'不走索引;name LIKE '张三%'可正常走前缀索引。
  4. 多表JOIN关联必须带上关联主键,避免笛卡尔积
    inner join/left join 强制写两张表相等ID,禁止无关联多表查询造成数据爆炸。
  5. 分页大数据不使用OFFSET超大偏移,改用主键分页
    错误:limit 100000,10;正确:where id>100000 limit 10,大幅提升分页性能。
  6. SQL关键字统一大写,缩进分层,格式化排版
    SELECT、FROM、WHERE、JOIN、GROUP BY大写,换行缩进,方便阅读排查问题。
  7. 批量操作使用IN、批量INSERT,避免循环单条SQL
    循环单次插入千次会频繁建立事务,批量合并语句减少数据库连接损耗。
  8. 避免在IN子句放上万条数据,拆分分批查询
    IN列表数据量过大会解析缓慢,拆分多段查询或临时表中转。
  9. 复杂统计优先GROUP BY,少用子查询嵌套,优先JOIN
    多层嵌套子查询执行效率低,关联表JOIN改写,同时用EXPLAIN分析执行计划。
  10. 写更新/删除语句先SELECT校验,业务必须加WHERE条件
    执行UPDATE/DELETE前先用相同WHERE查询核对数据;禁止不带条件全表更新、删除,防止线上事故。

数据库(设计+SQL优化+运维)高频面试10题 标准详解

1、InnoDB聚簇索引、二级索引区别,什么是回表、覆盖索引?

详解

  1. 聚簇索引
    InnoDB主键就是聚簇索引,叶子节点直接存储完整一行数据;一张表只能有1个,数据按主键有序存放。
  2. 二级索引(普通/唯一索引)
    叶子节点不存完整数据,只存主键ID;查询时拿到主键,再去聚簇索引查完整数据,这个过程叫回表,会多一次IO,性能损耗。
  3. 覆盖索引
    查询需要的所有字段全部包含在二级索引里,不用回表,EXPLAIN Extra显示Using index,是最优查询方案。
    举例select name from user where phone='138xxx'
    联合索引idx_phone_name(phone,name)就是覆盖索引,无回表。

2、索引失效常见场景,至少说5种

详解

  1. 索引列使用函数运算:year(create_time) = 2026
  2. 隐式类型转换:varchar手机号传数字,字段自动转数字,索引失效
  3. like '%关键词' 前缀模糊匹配,不走索引;'关键词%'可走
  4. or 一侧字段无索引,整体索引失效
  5. !=、<>、not in 大概率放弃索引全表扫描
  6. 联合索引不满足最左匹配原则,跳过前列直接查后列

3、limit 超大分页为什么慢?怎么优化?

详解

  • 慢的原因:limit 100000,10,数据库需要先扫描丢弃前10万行,再取10条,扫描行数巨大。
  • 优化方案:主键分页(书签分页)
sql 复制代码
-- 差写法
select * from order limit 100000,10;
-- 优化写法,记录上一页最大id
select * from order where id > 100000 limit 10;

适用:有序自增主键场景;无序数据可借助时间、唯一字段分页。

4、三大范式是什么?业务中为什么经常反范式设计?

详解

  1. 三大范式
  • 1NF:字段原子不可拆分;
  • 2NF:消除主键部分依赖;
  • 3NF:消除非主键字段传递依赖,减少冗余。
  1. 反范式场景(读多写少、多表频繁JOIN)
    主动冗余字段,减少关联查询损耗。
    例:订单表冗余user_name,不用每次JOIN用户表;
    代价:写操作需要同步更新冗余字段,存在数据一致性成本。

5、EXPLAIN 中 type 字段性能等级,哪些指标代表SQL需要优化?

详解

  1. type 从最优到最差:
    system > const > eq_ref > ref > range > index > ALL
  • ALL:全表扫描,必须优化;
  • index:遍历整棵索引树,性能很差;
    最低优化目标:至少达到 range / ref。
  1. Extra 危险标识:
  • Using filesort:无法利用索引排序,磁盘文件排序;
  • Using temporary:分组/去重生成临时表;
    出现二者说明缺少合适联合索引,需要重构索引。

6、count(*)、count(1)、count(字段) 性能与区别

详解

  1. InnoDB下 count(*)count(1) 性能几乎无差别,底层优化逻辑一致;
  2. count(字段):会过滤字段为NULL的数据,且需要读取字段数据,速度最慢;
  3. 注意:统计总行数优先 count(*);若要统计非空数据,用对应字段。

7、MySQL事务四大隔离级别、默认级别、各自存在什么问题?

详解

ACID:原子性、一致性、隔离性、持久性。

四种隔离级别:

  1. 读未提交:脏读、不可重复读、幻读;
  2. 读已提交:不可重复读、幻读;
  3. 可重复读(InnoDB默认):解决脏读、不可重复读,存在幻读;依靠MVCC多版本并发控制实现;
  4. 串行化:全部问题解决,并发性能极低。

8、MVCC实现原理,作用是什么?

详解

  1. 核心组件:undo log(数据快照)+ read view(读视图)
  2. 原理:
    写操作生成数据旧版本快照存undo日志;快照读不加锁,通过read view判断数据版本可见性;当前读(update/delete)会上行锁。
  3. 作用:
    读写不阻塞,大幅提升并发;实现可重复读隔离级别;无锁快照读,避免大量锁竞争。

9、线上大表慢SQL排查、优化完整流程(运维向)

详解

  1. 定位慢SQL:开启慢查询日志,设置阈值(如1s),用pt-query-digest汇总分析;业务监控抓取超时SQL;
  2. 执行 EXPLAIN 查看执行计划,定位全表扫描、文件排序、临时表;
  3. 优化手段:
    • 补全联合索引,遵循最左匹配、覆盖索引;
    • 改写SQL:去掉select *、优化分页、拆分in大量数据;
    • 冷热分表、大字段拆分、分库分表;
  4. 上线验证:预发压测,观察QPS、耗时、锁等待;长期监控索引维护成本。

10、MySQL主从复制原理、读写分离存在什么坑?

详解

  1. 主从原理
    主库操作记录binlog二进制日志;从库IO线程拉取binlog,SQL线程重放日志,实现数据同步。
    用途:读写分离、数据备份、故障切换。
  2. 读写分离核心坑
  • 主从延迟:刚写入主库,立刻查从库可能读到旧数据;
    解决方案:实时数据、新增数据强制走主库;
  • 大事务导致同步延迟暴涨;
  • 多从库同步压力大,binlog日志过大占用磁盘;
    运维优化:拆分大事务、定时清理过期binlog、监控同步延迟Seconds_Behind_Master。

结合具体业务场景给出10个数据库设计查询运维案例,详解原理

这里为您整理了10个结合具体业务场景的数据库设计、查询与运维案例。这些案例涵盖了从表结构设计、SQL优化到日常运维的常见痛点,并附带了底层原理的详解。


1. 电商订单表:大表分库分表与水平拆分

业务场景 :电商平台订单量达到千万级,单表查询变慢,写入遇到瓶颈。

设计方案 :按 user_id 进行水平分表(Hash取模),将数据打散到多张物理表中。

原理解析

  • IO瓶颈突破:单表数据量过大时,B+树高度增加,导致磁盘随机IO增多。分表后单表数据量下降,B+树高度降低,内存能缓存更多索引页。
  • 写入并发:单机写入受限于磁盘IOPS,分表后写入压力分散到多个物理节点或实例。
  • 注意 :按 user_id 分表会导致按 order_id 查询困难,通常需要引入异构索引表(如 order_id -> user_id 映射表)或借助 ES 解决非主键查询。

2. 用户积分流水:深分页(Deep Pagination)优化

业务场景 :用户查询自己的积分明细,翻到第1000页,接口响应超过3秒。

查询优化

  • 错误写法SELECT * FROM points_log WHERE user_id = ? LIMIT 10000, 20(MySQL会扫描10020行然后丢弃前10000行)。
  • 正确写法(延迟关联/游标法)SELECT * FROM points_log WHERE user_id = ? AND id > 上次最后一条ID LIMIT 20
    原理解析
  • 回表代价 :深分页的本质是大量无效的"回表"操作。通过 id > 利用主键聚簇索引的有序性,直接定位到起始位置,避免了扫描和丢弃大量数据的过程。

3. 文章/商品标签:多对多关联查询优化

业务场景 :查询带有"AI"和"数据库"两个标签的文章,传统写法使用多次 JOININ 子查询,性能极差。

设计方案 :使用位图(Bitmap)或 Elasticsearch,或者在业务层做内存过滤。若必须用SQL,采用 GROUP BY + HAVING COUNT

查询示例

sql 复制代码
SELECT article_id 
FROM article_tag_rel 
WHERE tag_id IN (101, 102) 
GROUP BY article_id 
HAVING COUNT(DISTINCT tag_id) = 2;

原理解析

  • 索引覆盖(tag_id, article_id) 联合索引可以实现覆盖索引(Covering Index),避免回表。
  • 执行计划 :MySQL 对 IN 列表会做索引合并或范围扫描,GROUP BY 利用索引的有序性进行分组,HAVING 在内存中过滤,比多次 JOIN 产生的临时表和文件排序(Filesort)效率高得多。

4. 秒杀库存扣减:热点行更新与锁竞争

业务场景 :秒杀开始瞬间,10万请求同时执行 UPDATE stock SET num = num - 1 WHERE id = 1,数据库CPU飙升,大量事务超时。

运维/设计优化

  1. 库存拆分 :将一行库存拆分为10行(id=1-1, 1-2...),请求随机扣减某一行。
  2. 异步扣减 :前端请求进 Redis 预扣减,MQ 异步落库。
    原理解析
  • 行锁机制:InnoDB 的行锁是加在索引上的。所有事务争抢同一行,导致严重的锁等待(Lock Wait)。
  • Redo Log 刷盘:高频更新导致 Redo Log 刷盘压力剧增,磁盘IO成为瓶颈。拆分库存本质是将"单点热点"转化为"多点并发",降低锁冲突概率。

5. 历史订单归档:大表DDL与数据清理

业务场景 :需要删除3年前的1000万条历史订单,直接 DELETE 导致主从延迟,业务卡顿。

运维方案

  • 分批删除 :按主键范围分批 DELETE ... LIMIT 1000,每批休眠100ms。
  • 归档策略 :新建归档表,INSERT INTO archive SELECT ...,然后 DROP 原表或 RENAME
    原理解析
  • Undo Log 膨胀 :大批量 DELETE 会产生巨大的 Undo Log 用于回滚,占用大量 Buffer Pool,甚至挤占热点数据。
  • Binlog 堆积:大事务产生的 Binlog 从库回放极慢,导致主从延迟。分批操作可以将大事务拆解为小事务,平滑 IO 和 CPU 负载。

6. 模糊搜索:前缀索引与倒排索引

业务场景 :用户输入 LIKE '%keyword%' 搜索商品名称,全表扫描,CPU 100%。

设计方案

  • 轻度需求LIKE 'keyword%' 利用索引最左前缀原则。
  • 重度需求:引入 Elasticsearch 或 ClickHouse。
  • 折中方案 :如果必须用MySQL,考虑 n-gram 分词插件(MySQL 5.7+)。
    原理解析
  • B+树特性 :B+树是排序树,只能支持范围查询和前缀匹配。%keyword% 破坏了有序性,必然全表扫描。
  • 倒排索引:ES 使用倒排索引(Term Dictionary + Posting List),将"文档包含词"转换为"词包含文档列表",实现 O(1) 级别的关键词定位。

7. 状态机流转:乐观锁与版本号

业务场景 :订单状态从"待支付"变为"已支付",防止并发重复支付或状态回退。

设计方案

sql 复制代码
UPDATE orders 
SET status = 'PAID', version = version + 1 
WHERE id = ? AND status = 'UNPAID' AND version = 1;

原理解析

  • CAS思想 :Compare And Swap。利用 WHERE status = 'UNPAID' 作为条件,如果其他事务已修改状态,affected_rows 为0,当前事务自动失败。
  • 避免死锁 :相比先 SELECT FOR UPDATEUPDATE,乐观锁减少了持锁时间,大幅降低死锁概率。

8. 报表统计:读写分离与列式存储

业务场景 :运营后台需要统计"过去30天各省份销售额",复杂聚合查询拖垮线上交易库。

设计方案

  • 基础:读写分离,查询走从库。
  • 进阶 :引入 ClickHouse / Doris / StarRocks。
    原理解析
  • 行存 vs 列存:MySQL 是行存,聚合查询需要扫描整行数据,大量无效IO。列存数据库只读取涉及的列,且相同列数据相似度高,压缩率极高(通常10:1)。
  • 向量化执行:现代OLAP引擎利用 CPU SIMD 指令集,一次处理一批数据,比 MySQL 逐行处理快几个数量级。

9. 分布式ID:雪花算法与数据库号段

业务场景 :分库分表后,自增ID不再适用,需要全局唯一且趋势递增的ID。

设计方案

  • Snowflake:时间戳+机器ID+序列号。
  • Leaf/美团号段模式 :从DB批量获取ID段(如1000-2000)在内存分配。
    原理解析
  • B+树分裂:如果ID完全随机(如UUID),新插入记录会随机落在叶子节点,导致频繁的页分裂(Page Split)和碎片,写入性能下降50%以上。
  • 趋势递增:雪花算法和号段模式保证ID大致递增,新数据总是追加在B+树最右侧叶子节点,避免分裂,维持顺序IO写入。

10. 连接池耗尽:慢SQL与运维监控

业务场景 :高峰期应用频繁报 Connection Timeout,但数据库CPU并不高。

运维排查

  1. SHOW PROCESSLIST 发现大量 SleepSending data 状态。
  2. 定位到一条未走索引的慢SQL,执行时间10秒。
    原理解析
  • 连接池阻塞 :应用层连接池大小有限(如50)。如果50个连接都被慢SQL占满,后续请求在队列中等待,超过 maxWaitTime 即报错。
  • 资源隔离:数据库内部是共享资源的,一条烂SQL占满IO或CPU,会导致所有正常请求变慢(Noisy Neighbor 问题)。
  • 解决 :Kill 掉慢SQL,加索引,配置 max_execution_time 强制超时,优化连接池配置。

总结:数据库设计的核心原则

  1. 索引不是越多越好:每个索引都是写入时的负担(维护B+树)和空间开销。
  2. 避免大事务:事务越大,Undo/Redo Log 压力越大,锁持有时间越长,死锁概率越高。
  3. 理解存储引擎:InnoDB 是索引组织表(IOT),数据即索引,主键设计至关重要。
  4. 架构大于SQL:当单表达到千万级或QPS过万,不要试图通过SQL优化解决,必须引入缓存、分库分表或异构存储(ES/ClickHouse)。
  5. 可观测性:没有监控的数据库就是定时炸弹。必须开启慢查询日志,配置连接池、锁等待、Buffer Pool命中率的告警。

如果您需要针对其中某个场景(例如:如何具体实施分库分表,或 ClickHouse 的表引擎选择)进行深入探讨,请告诉我。