文章目录
- [数据库设计 + 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题 标准详解)
-
- 1、InnoDB聚簇索引、二级索引区别,什么是回表、覆盖索引?
- 2、索引失效常见场景,至少说5种
- [3、limit 超大分页为什么慢?怎么优化?](#3、limit 超大分页为什么慢?怎么优化?)
- 4、三大范式是什么?业务中为什么经常反范式设计?
- [5、EXPLAIN 中 type 字段性能等级,哪些指标代表SQL需要优化?](#5、EXPLAIN 中 type 字段性能等级,哪些指标代表SQL需要优化?)
- [6、count(*)、count(1)、count(字段) 性能与区别](#6、count(*)、count(1)、count(字段) 性能与区别)
- 7、MySQL事务四大隔离级别、默认级别、各自存在什么问题?
- 8、MVCC实现原理,作用是什么?
- 9、线上大表慢SQL排查、优化完整流程(运维向)
- 10、MySQL主从复制原理、读写分离存在什么坑?
- 结合具体业务场景给出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. 为什么不建议用业务字段做主键(手机号/订单号)?
答
- 业务字段可变,修改主键代价极大;
- 字符串主键占用空间大,二级索引膨胀;
- 无序字符串造成页分裂,插入性能暴跌;
推荐自增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. 物理外键为什么项目基本不用?
答
- 高并发易触发锁、死锁;
- 分库分表无法使用;
- 耦合太强,删改流程受限;
解决方案:应用层代码做关联校验。
11. 联合索引最左匹配原则
答
索引(a,b,c),where条件必须出现最左前列才能走索引;
where b=?、where c=? 无法走完整联合索引。
12. 索引失效常见场景(列举4种)
答
- 索引列使用函数运算;
- 隐式类型转换;
- like %前缀模糊匹配;
- or连接无索引字段、not != <> 判断。
13. 索引是不是越多越好?弊端?
答
不是。索引占用磁盘;增删改时需要同步维护所有索引,写性能大幅下降。
14. 什么是覆盖索引?优势?
答
索引包含查询所需全部字段,无需回表。
优势:减少IO,查询速度极大提升,EXPLAIN Extra 显示Using index。
15. 大表优化方案(建表阶段)
答
- 冷热数据分表;
- 大字段拆分扩展表;
- 合理分区表;
- 提前设计分库分表规则;
- 精简字段类型,减少行宽度。
二、SQL 查询编写规范与坑(16--30)
16. 为什么禁止 select *?
答
- 读取无用字段,浪费IO、网络;
- 无法使用覆盖索引,必回表;
- 表新增字段后业务出现兼容异常。
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 必须注意什么?
答
- 禁止无where全表操作;
- 执行前先用同等where select校验数据;
- 批量更新分批执行,防止长事务锁表。
29. 批量插入优化手段
答
- 多值INSERT批量写入;
- 关闭自动提交,手动事务批量提交;
- 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. 分布式事务三种常见方案
答
- 本地消息表(可靠消息最终一致性);
- Seata AT/TCC/SAGA;
- MQ事务消息。
49. MySQL 主从复制作用、原理
答
作用:读写分离、数据备份、故障切换;
原理:binlog二进制日志,主库记录变更,从库IO线程拉取,SQL线程重放。
50. 读写分离如何落地,注意什么坑?
答
写走主库,读走从库;
坑:主从延迟导致读取旧数据;重要实时查询强制走主库。
数据库设计 + SQL查询编写 20条规范好习惯(分设计10条、SQL编写10条)
一、数据库表结构设计好习惯(10条)
- 统一命名规范,杜绝拼音、中文、特殊字符
表、字段全英文小写,下划线分隔;禁止userName驼峰、用户表、user-name;示例:user_info、order_id。 - 每张表必须设计主键,优先自增数字ID,不使用业务字段做主键
主键id BIGINT UNSIGNED AUTO_INCREMENT,订单号、手机号等业务字段只建唯一索引,不充当主键。 - 核心业务表增加逻辑删除字段,不物理删数据
统一is_deleted TINYINT DEFAULT 0,0正常1删除,所有查询强制带上where is_deleted=0,保留数据溯源。 - 通用审计字段每张表标配
create_time DATETIME、update_time DATETIME,新增自动填创建时间,更新自动刷新修改时间。 - 合理选择字段数据类型,能小不大、能整不浮点
状态用TINYINT不用INT;金额用DECIMAL(10,2)禁止FLOAT/DOUBLE;字符串按需选VARCHAR长度,不统一255。 - 字段加注释,表加注释,避免后期看不懂业务含义
COMMENT '用户唯一ID',枚举状态在注释标注:0正常,1禁用,2冻结。 - 外键逻辑在代码层控制,数据库不创建物理外键
物理外键容易锁表、影响分库分表、删改卡顿,应用程序做关联校验代替数据库约束。 - 高频查询条件提前建立索引,避免全表扫描
where、join、order by、group by字段建索引;联合索引遵循最左匹配原则。 - 区分普通索引、唯一索引,重复业务字段加唯一约束
手机号、用户名、身份证等不重复字段创建UNIQUE INDEX,防止脏数据重复插入。 - 大字段独立拆分,text/blob不放在高频查询表
文章内容、图片大文本单独建扩展表,主表只存短字段,减少IO开销,提升查询速度。
二、SQL查询语句编写好习惯(10条)
- *禁止SELECT ,只查询业务需要的指定字段
减少网络传输、避免冗余大字段加载,后期表扩容不会出现多余字段兼容问题。 - WHERE条件禁止对索引字段做函数运算,会失效索引
错误:YEAR(create_time) = 2026;正确:create_time BETWEEN '2026-01-01' AND '2026-12-31'。 - 模糊查询前缀不加%,%xxx会完全放弃索引
name LIKE '%张三'不走索引;name LIKE '张三%'可正常走前缀索引。 - 多表JOIN关联必须带上关联主键,避免笛卡尔积
inner join/left join 强制写两张表相等ID,禁止无关联多表查询造成数据爆炸。 - 分页大数据不使用OFFSET超大偏移,改用主键分页
错误:limit 100000,10;正确:where id>100000 limit 10,大幅提升分页性能。 - SQL关键字统一大写,缩进分层,格式化排版
SELECT、FROM、WHERE、JOIN、GROUP BY大写,换行缩进,方便阅读排查问题。 - 批量操作使用IN、批量INSERT,避免循环单条SQL
循环单次插入千次会频繁建立事务,批量合并语句减少数据库连接损耗。 - 避免在IN子句放上万条数据,拆分分批查询
IN列表数据量过大会解析缓慢,拆分多段查询或临时表中转。 - 复杂统计优先GROUP BY,少用子查询嵌套,优先JOIN
多层嵌套子查询执行效率低,关联表JOIN改写,同时用EXPLAIN分析执行计划。 - 写更新/删除语句先SELECT校验,业务必须加WHERE条件
执行UPDATE/DELETE前先用相同WHERE查询核对数据;禁止不带条件全表更新、删除,防止线上事故。
数据库(设计+SQL优化+运维)高频面试10题 标准详解
1、InnoDB聚簇索引、二级索引区别,什么是回表、覆盖索引?
详解
- 聚簇索引
InnoDB主键就是聚簇索引,叶子节点直接存储完整一行数据;一张表只能有1个,数据按主键有序存放。 - 二级索引(普通/唯一索引)
叶子节点不存完整数据,只存主键ID;查询时拿到主键,再去聚簇索引查完整数据,这个过程叫回表,会多一次IO,性能损耗。 - 覆盖索引
查询需要的所有字段全部包含在二级索引里,不用回表,EXPLAIN Extra显示Using index,是最优查询方案。
举例 :select name from user where phone='138xxx'
联合索引idx_phone_name(phone,name)就是覆盖索引,无回表。
2、索引失效常见场景,至少说5种
详解
- 索引列使用函数运算:
year(create_time) = 2026 - 隐式类型转换:varchar手机号传数字,字段自动转数字,索引失效
like '%关键词'前缀模糊匹配,不走索引;'关键词%'可走or一侧字段无索引,整体索引失效!=、<>、not in大概率放弃索引全表扫描- 联合索引不满足最左匹配原则,跳过前列直接查后列
3、limit 超大分页为什么慢?怎么优化?
详解
- 慢的原因:
limit 100000,10,数据库需要先扫描丢弃前10万行,再取10条,扫描行数巨大。 - 优化方案:主键分页(书签分页)
sql
-- 差写法
select * from order limit 100000,10;
-- 优化写法,记录上一页最大id
select * from order where id > 100000 limit 10;
适用:有序自增主键场景;无序数据可借助时间、唯一字段分页。
4、三大范式是什么?业务中为什么经常反范式设计?
详解
- 三大范式
- 1NF:字段原子不可拆分;
- 2NF:消除主键部分依赖;
- 3NF:消除非主键字段传递依赖,减少冗余。
- 反范式场景(读多写少、多表频繁JOIN)
主动冗余字段,减少关联查询损耗。
例:订单表冗余user_name,不用每次JOIN用户表;
代价:写操作需要同步更新冗余字段,存在数据一致性成本。
5、EXPLAIN 中 type 字段性能等级,哪些指标代表SQL需要优化?
详解
- type 从最优到最差:
system > const > eq_ref > ref > range > index > ALL
- ALL:全表扫描,必须优化;
- index:遍历整棵索引树,性能很差;
最低优化目标:至少达到 range / ref。
- Extra 危险标识:
Using filesort:无法利用索引排序,磁盘文件排序;Using temporary:分组/去重生成临时表;
出现二者说明缺少合适联合索引,需要重构索引。
6、count(*)、count(1)、count(字段) 性能与区别
详解
- InnoDB下
count(*)和count(1)性能几乎无差别,底层优化逻辑一致; count(字段):会过滤字段为NULL的数据,且需要读取字段数据,速度最慢;- 注意:统计总行数优先
count(*);若要统计非空数据,用对应字段。
7、MySQL事务四大隔离级别、默认级别、各自存在什么问题?
详解
ACID:原子性、一致性、隔离性、持久性。
四种隔离级别:
- 读未提交:脏读、不可重复读、幻读;
- 读已提交:不可重复读、幻读;
- 可重复读(InnoDB默认):解决脏读、不可重复读,存在幻读;依靠MVCC多版本并发控制实现;
- 串行化:全部问题解决,并发性能极低。
8、MVCC实现原理,作用是什么?
详解
- 核心组件:undo log(数据快照)+ read view(读视图)
- 原理:
写操作生成数据旧版本快照存undo日志;快照读不加锁,通过read view判断数据版本可见性;当前读(update/delete)会上行锁。 - 作用:
读写不阻塞,大幅提升并发;实现可重复读隔离级别;无锁快照读,避免大量锁竞争。
9、线上大表慢SQL排查、优化完整流程(运维向)
详解
- 定位慢SQL:开启慢查询日志,设置阈值(如1s),用pt-query-digest汇总分析;业务监控抓取超时SQL;
- 执行
EXPLAIN查看执行计划,定位全表扫描、文件排序、临时表; - 优化手段:
- 补全联合索引,遵循最左匹配、覆盖索引;
- 改写SQL:去掉select *、优化分页、拆分in大量数据;
- 冷热分表、大字段拆分、分库分表;
- 上线验证:预发压测,观察QPS、耗时、锁等待;长期监控索引维护成本。
10、MySQL主从复制原理、读写分离存在什么坑?
详解
- 主从原理
主库操作记录binlog二进制日志;从库IO线程拉取binlog,SQL线程重放日志,实现数据同步。
用途:读写分离、数据备份、故障切换。 - 读写分离核心坑
- 主从延迟:刚写入主库,立刻查从库可能读到旧数据;
解决方案:实时数据、新增数据强制走主库; - 大事务导致同步延迟暴涨;
- 多从库同步压力大,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"和"数据库"两个标签的文章,传统写法使用多次 JOIN 或 IN 子查询,性能极差。
设计方案 :使用位图(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飙升,大量事务超时。
运维/设计优化:
- 库存拆分 :将一行库存拆分为10行(
id=1-1,1-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 UPDATE再UPDATE,乐观锁减少了持锁时间,大幅降低死锁概率。
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并不高。
运维排查:
SHOW PROCESSLIST发现大量Sleep或Sending data状态。- 定位到一条未走索引的慢SQL,执行时间10秒。
原理解析:
- 连接池阻塞 :应用层连接池大小有限(如50)。如果50个连接都被慢SQL占满,后续请求在队列中等待,超过
maxWaitTime即报错。 - 资源隔离:数据库内部是共享资源的,一条烂SQL占满IO或CPU,会导致所有正常请求变慢(Noisy Neighbor 问题)。
- 解决 :Kill 掉慢SQL,加索引,配置
max_execution_time强制超时,优化连接池配置。
总结:数据库设计的核心原则
- 索引不是越多越好:每个索引都是写入时的负担(维护B+树)和空间开销。
- 避免大事务:事务越大,Undo/Redo Log 压力越大,锁持有时间越长,死锁概率越高。
- 理解存储引擎:InnoDB 是索引组织表(IOT),数据即索引,主键设计至关重要。
- 架构大于SQL:当单表达到千万级或QPS过万,不要试图通过SQL优化解决,必须引入缓存、分库分表或异构存储(ES/ClickHouse)。
- 可观测性:没有监控的数据库就是定时炸弹。必须开启慢查询日志,配置连接池、锁等待、Buffer Pool命中率的告警。
如果您需要针对其中某个场景(例如:如何具体实施分库分表,或 ClickHouse 的表引擎选择)进行深入探讨,请告诉我。