1.慢查询
慢查询是 MySQL 中执行耗时超过 long_query_time
阈值的 SQL 语句,阈值可通过配置调整(建议根据业务响应要求设为 1-3 秒)。
慢查询的本质是 "SQL 执行效率低" 或 "资源供给不足",具体可分为 5 大类原因:
原因
1. SQL 本身的问题(最常见)
(1)全表扫描 / 全索引扫描
- 未建索引:如
SELECT * FROM orders WHERE user_id = 123
中user_id
无索引,触发全表扫描(EXPLAIN
中type=ALL
); - 索引失效:即使建了索引,以下场景会导致索引 "失效",退化为全表扫描:
- 索引字段用函数 / 运算:
SELECT * FROM user WHERE SUBSTR(phone,1,3) = '138'
(索引字段phone
被函数操作); - 类型不匹配:
SELECT * FROM user WHERE id = '123'
(id
是 int 类型,传入字符串,触发隐式转换); - 模糊查询以
%
开头:SELECT * FROM goods WHERE name LIKE '%手机'
(前缀模糊无法走索引); - 逻辑运算符
OR
连接非索引字段:SELECT * FROM order WHERE id=1 OR status=0
(status
无索引,索引失效)。
- 索引字段用函数 / 运算:
(2)SQL 写法不合理
- 过度查询:用
SELECT *
读取所有字段,即使只需要 2 个字段(增加 IO 和内存消耗); - 子查询效率低:多层嵌套子查询(如
SELECT * FROM (SELECT ... FROM (SELECT ...))
)会生成临时表,性能差; - join 方式不当:多表 join 时未用 "小表驱动大表",或 join 字段无索引(导致笛卡尔积计算);
- 排序 / 分组低效:
ORDER BY
/GROUP BY
字段无索引,触发Using filesort
(磁盘排序)或Using temporary
(临时表),耗时剧增。
2. 表结构与索引设计问题
(1)表数据量过大,未拆分
- 单表数据量超过 1000 万行(InnoDB 引擎),即使有索引,查询也会因 "索引树层级深""磁盘 IO 多" 变慢;
- 例:订单表存储 5 年数据(1 亿行),查询某用户近 1 个月订单时,仍需扫描大量历史数据。
(2)索引设计不合理
- 无 "高频查询字段" 索引:如 "用户下单" 场景,
user_id
"order_time" 是高频查询字段,未建索引; - 过度索引:一张表建 10+ 个索引,写操作(
INSERT/UPDATE/DELETE
)会因 "维护索引树" 耗时增加; - 索引粒度不当:用 "大字段"(如
varchar(255)
)建索引,索引文件过大,查询时加载慢; - 未用复合索引:如查询
WHERE user_id=123 AND order_time>='2024-01-01'
,仅建user_id
单字段索引,无法覆盖order_time
,仍需回表查询。
(3)表字段设计不合理
- 字段类型冗余:用
varchar(255)
存储 "性别"(实际只需char(1)
或tinyint
),增加存储和查询成本; - 缺少主键 / 主键不合理:无主键会导致 InnoDB 用 "隐藏主键",查询效率低;用 UUID 作为主键(随机字符串)会导致索引树碎片化,插入性能差。
3. 数据库配置问题
(1)缓存配置不当
innodb_buffer_pool_size
过小:InnoDB 缓存表数据和索引的核心参数,若设为 1G(而表数据有 10G),会频繁触发 "磁盘 IO",慢查询剧增(建议设为物理内存的 50%-70%);- 查询缓存(Query Cache)滥用:MySQL 8.0 已移除查询缓存,低版本中若开启,会因 "缓存失效频繁"(写操作会清空缓存)导致额外开销。
(2)连接池与并发配置不合理
- 连接池最大连接数(
max_connections
)过小:慢查询占用连接时间长,导致新请求无法获取连接; - 线程池配置不当:
innodb_thread_concurrency
设得过高,导致线程上下文切换频繁,CPU 利用率下降。
(3)其他参数问题
sort_buffer_size
/join_buffer_size
过小:排序 /join 时的临时缓存不足,触发磁盘临时文件,效率骤降;log_queries_not_using_indexes
未开启:无法监控 "未用索引的 SQL",错过潜在慢查询。
4. 硬件与环境问题
(1)硬件资源瓶颈
- CPU 不足:复杂 SQL(如多表 join、聚合计算)占用大量 CPU,导致执行缓慢;
- 内存不足:数据库缓存命中率低,频繁读写磁盘(IO 速度比内存慢 1000+ 倍);
- 磁盘 IO 瓶颈:用机械硬盘(HDD)存储数据,随机读写速度慢(建议用 SSD,IOPS 提升 100+ 倍)。
(2)网络问题
- 数据库与应用服务器跨机房部署,网络延迟高(如跨地域调用,延迟 50ms+);
- 网络带宽不足:大量慢查询返回大结果集(如
SELECT *
读取 10 万行数据),占用带宽导致阻塞。
5. 架构设计问题
- 无读写分离:所有查询(读 + 写)都走主库,主库被读请求压垮,写操作排队;
- 无缓存层:高频查询(如商品详情、用户信息)直接查数据库,未用 Redis 缓存,导致重复执行相同 SQL;
- 分库分表缺失:单库单表数据量超过 1000 万行,未做水平分表(如按时间 / 用户 ID 分表),查询范围过大。
定位工具
定位慢查询的核心是 "找到哪条 SQL 慢→为什么慢→属于哪个业务链路",不同场景需用不同工具组合。
1. 原生工具:MySQL 自带的 "基础定位武器"
适合 单节点数据库 或 无分布式链路的场景,快速定位慢 SQL 本身。
(1)慢查询日志:记录所有慢 SQL
-
开启配置 (永久生效需改
my.cnf
):[mysqld] slow_query_log = 1 # 开启慢查询日志 slow_query_log_file = /var/log/mysql/slow.log # 日志路径 long_query_time = 1 # 阈值设为 1 秒 log_queries_not_using_indexes = 1 # 记录未用索引的 SQL(关键!) log_output = FILE # 日志输出为文件(支持 TABLE,但性能差)
-
分析工具 :
-
mysqldumpslow
(MySQL 自带):快速筛选 TOP 慢 SQL:mysqldumpslow -s t -t 10 /var/log/mysql/slow.log # 按耗时排序,取前 10 条 mysqldumpslow -s c -t 10 /var/log/mysql/slow.log # 按调用次数排序,取前 10 条
-
pt-query-digest
(Percona 工具集,推荐):更强大的分析,支持按 SQL 模板分组、统计耗时分布:pt-query-digest /var/log/mysql/slow.log > slow_analysis.txt
输出会包含 "总查询数、平均耗时、SQL 模板、调用来源" 等关键信息,例如:
# Query 1: 0.01 QPS, 0.05x concurrency, ID 0x123456 # Time range: 2024-01-01 00:00:00 to 00:30:00 # Attribute total min max avg 95% median 75% 25% # ============ ======= ======= ======= ======= ======= ======= ======= ======= # Exec time 120s 2s 5s 3s 4s 3s 3.5s 2.5s # Rows sent 100 1 5 2 3 2 2.5 1.5 # Rows exam 10000 200 500 300 400 300 350 250 # Query_time distribution # 1s-2s : 10% (4) # 2s-3s : 60% (24) # 3s-4s : 30% (12) # SQL 模板: SELECT * FROM orders WHERE user_id = ? AND status = ?
-
(2)实时定位:发现正在执行的慢查询
-
SHOW PROCESSLIST
:查看当前数据库连接的执行状态,快速定位 "卡住的 SQL":SHOW PROCESSLIST;
关键字段解读:
Time
:SQL 已执行的时间(秒),超过阈值即为慢查询;State
:执行状态(如Sending data
表示正在读取数据,Locked
表示锁等待);Info
:完整的 SQL 语句(若过长,用SHOW FULL PROCESSLIST
查看)。
-
筛选慢查询:
SELECT id, user, host, db, time, info FROM information_schema.processlist WHERE time > 3 -- 筛选执行超过 3 秒的 SQL ORDER BY time DESC;
(3)执行计划分析:为什么 SQL 慢?
找到慢 SQL 后,用 EXPLAIN
分析其执行逻辑,定位核心瓶颈:
EXPLAIN SELECT * FROM orders WHERE user_id = 123 AND order_time >= '2024-01-01';
字段 | 含义与判断标准 |
---|---|
id |
SQL 执行顺序(越大越先执行,子查询 id 更大) |
select_type |
查询类型(SIMPLE 为简单查询,SUBQUERY 为子查询,DERIVED 为临时表查询,复杂类型通常慢) |
table |
涉及的表 |
type |
访问类型(性能从好到差 :system > const > eq_ref > ref > range > index > ALL ),ALL 表示全表扫描,必须优化 |
key |
实际使用的索引(NULL 表示未用索引) |
rows |
预计扫描的行数(数值越大,IO 消耗越高) |
Extra |
额外信息(Using filesort /Using temporary 表示排序 / 临时表,需优化;Using index 表示覆盖索引,性能好) |
2. 分布式 APM 工具:定位慢查询的 "业务链路"
适合 分布式系统 (如微服务架构),解决原生工具的痛点 ------"只知道 SQL 慢,不知道属于哪个业务请求"。常用工具:SkyWalking、Pinpoint、Zipkin+Brave。
(1)核心原理
通过 "探针埋点→链路关联→数据聚合",将慢 SQL 与业务链路绑定:
- 探针埋点:在应用服务(如 Java 服务)中挂载 Agent(如 SkyWalking Agent),自动拦截 JDBC 调用,采集 SQL 执行时间、语句、数据库地址等信息;
- 链路关联 :将 SQL 数据与当前请求的
Trace ID
(全链路唯一标识)绑定,形成 "用户请求→网关→服务 A→服务 B→MySQL" 的完整链路; - 可视化展示:在 UI 界面中定位慢 SQL 所属的接口、请求参数、调用链环节,快速关联业务场景。
(2)SkyWalking 实战步骤
-
部署 Agent :启动服务时挂载 SkyWalking Agent,采集 SQL 数据:
bash
java -javaagent:/path/skywalking-agent.jar \ -Dskywalking.agent.service_name=order-service \ # 服务名 -Dskywalking.collector.backend_service=127.0.0.1:11800 \ # OAP 地址 -jar order-service.jar
-
定位慢 SQL :
- 进入 SkyWalking UI → 「链路追踪」:按 "服务名""耗时" 筛选,找到耗时久的链路;
- 点击链路详情,找到 "Database" 类型的 Span 节点(标注 "SQL"),查看:
- 业务上下文:所属接口(如
/api/order/list
)、请求参数(如user_id=123
)、Trace ID; - SQL 信息:执行时间(如 2.5 秒)、完整 SQL 语句、数据库实例;
- 业务上下文:所属接口(如
- 进入「数据库监控」:查看全局慢 SQL 统计(TOP 10 慢 SQL、耗时分布)。
(3)优势
- 业务关联:明确慢 SQL 是 "用户查看订单列表" 还是 "管理员导出数据",避免优化非核心业务;
- 跨服务定位:若慢 SQL 是 "服务 A 调用服务 B→服务 B 查 MySQL",可快速定位到具体服务;
- 实时告警:设置 "SQL 执行超过 2 秒" 的告警规则,第一时间发现问题。
3. 第三方监控工具:实现 "慢查询监控闭环"
适合 生产环境长期监控,将慢查询数据与指标、日志联动,形成 "发现→告警→分析" 闭环。常用工具:
- Prometheus+Grafana :通过
mysql_exporter
采集慢查询指标(如mysql_slow_queries_total
),在 Grafana 中制作仪表盘,实时查看慢查询趋势; - Zabbix:监控慢查询日志文件,当新增慢查询时触发邮件 / 短信告警;
- ELK Stack:将慢查询日志导入 Elasticsearch,通过 Kibana 搜索、筛选、可视化,支持按 "SQL 模板""耗时区间" 聚合分析。
解决办法
解决慢查询需遵循 "先优化 SQL / 索引,再优化表结构,最后升级架构" 的原则(成本从低到高)。
1. 优先优化:SQL 语句与索引(成本最低,效果最明显)
(1)SQL 优化:让语句 "更高效"
- 避免全表扫描 :
- 去掉
SELECT *
,只查需要的字段(如SELECT id, order_no FROM orders
); - 优化模糊查询:用
LIKE '手机%'
(前缀匹配)替代LIKE '%手机'
(后缀匹配); - 避免索引失效场景:不用函数操作索引字段(如
SUBSTR(phone,1,3)
改为phone BETWEEN '13800000000' AND '13899999999'
)。
- 去掉
- 优化排序与分组 :
ORDER BY
/GROUP BY
字段建索引(如INDEX idx_user_time (user_id, order_time)
),避免Using filesort
;- 若排序字段无法建索引,减少排序数据量(如先
WHERE
过滤再ORDER BY
)。
- 优化 join 与子查询 :
- 用 "小表驱动大表"(如
SELECT * FROM small_table s JOIN big_table b ON s.id = b.s_id
); - 子查询转 join(如
SELECT * FROM user WHERE id IN (SELECT user_id FROM orders)
改为SELECT u.* FROM user u JOIN orders o ON u.id = o.user_id
),避免临时表。
- 用 "小表驱动大表"(如
- 限制结果集大小 :
- 分页查询用
LIMIT
且配合索引(如SELECT * FROM orders WHERE user_id=123 ORDER BY id LIMIT 100, 20
); - 禁止无限制
LIMIT
(如LIMIT 100000
),需分页或分批处理。
- 分页查询用
(2)索引优化:让查询 "走对路"
- 建 "高频查询字段" 索引 :
- 单字段索引:针对
WHERE
中频繁出现的字段(如user_id
order_status
); - 复合索引:针对多字段查询(如
WHERE user_id=123 AND order_time>='2024-01-01'
,建idx_user_time (user_id, order_time)
),遵循 "最左前缀原则"(查询需包含复合索引的第一个字段); - 覆盖索引:索引包含查询所需的所有字段(如
SELECT id, order_no FROM orders WHERE user_id=123
,建idx_user_id_no (user_id, id, order_no)
),避免 "回表查询"(Using index
标识)。
- 单字段索引:针对
- 避免过度索引 :
- 一张表索引不超过 5-8 个(写操作需维护索引树,过多索引会降低插入 / 更新效率);
- 删除 "未使用的索引"(通过
sys.schema_unused_indexes
查看 MySQL 中未使用的索引)。
- 选择合适的索引类型 :
- 普通查询用 B+ 树索引(支持范围查询、排序);
- 等值查询(如
WHERE phone='138xxxx8888'
)用 哈希索引(Memory 引擎或 InnoDB 自适应哈希索引); - 全文检索(如
WHERE content LIKE '%MySQL优化%'
)用 全文索引 (避免LIKE %xx%
的全表扫描)。
2. 表结构优化:解决 "数据量过大" 问题
(1)分表分库:拆分大表 / 大库
当单表数据量超过 1000 万行、单库容量超过 50GB 时,需拆分:
- 水平分表 (按行拆分):将同一表的数据按规则分散到多个表,结构相同;
- 按时间拆分:订单表拆分为
orders_2023
orders_2024
(适合按时间查询的场景); - 按用户 ID 哈希拆分:用户表拆分为
user_0
user_1
...user_15
(哈希取模 16,适合用户维度查询);
- 按时间拆分:订单表拆分为
- 垂直分表 (按列拆分):将表中 "高频查询字段" 和 "低频查询字段" 拆分到不同表;
- 例:用户表拆分为
user_base
(id, name, phone 等高频字段)和user_extend
(avatar, address 等低频字段),减少单表数据量和 IO。
- 例:用户表拆分为
- 分库:将多个分表分散到不同数据库实例,避免单库资源瓶颈(需用 Sharding-JDBC、MyCat 等中间件实现)。
(2)优化表字段
- 选择合适的字段类型 :
- 整数用
tinyint
(1 字节)/int
(4 字节)/bigint
(8 字节),避免用varchar
; - 字符串用
varchar
(可变长度)替代char
(固定长度),减少存储; - 时间用
datetime
(8 字节)/timestamp
(4 字节,适合跨时区),避免用varchar
存储时间;
- 整数用
- 添加主键 :InnoDB 表必须有主键(推荐用自增
int
/bigint
,避免 UUID),主键会作为聚簇索引,提升查询效率; - 避免 NULL 值 :NULL 值会增加存储和查询成本,可设默认值(如
status
默认为 0)。
3. 数据库配置优化:让 MySQL "跑更快"
(1)核心参数调整(My.cnf)
- 缓存相关 :
innodb_buffer_pool_size = 16G
:InnoDB 缓存表数据和索引,建议设为物理内存的 50%-70%(如 32G 内存设 20G);innodb_log_buffer_size = 64M
:redo log 缓冲区,减少磁盘 IO;
- 并发相关 :
max_connections = 1000
:最大连接数,根据业务并发调整(避免过小导致连接不足,过大导致资源浪费);innodb_thread_concurrency = 8
:InnoDB 并发线程数,建议设为 CPU 核心数的 2-4 倍;
- 查询优化相关 :
sort_buffer_size = 2M
:排序缓存,避免过小触发磁盘排序;join_buffer_size = 2M
:join 缓存,提升多表 join 效率;query_cache_type = 0
:关闭查询缓存(MySQL 8.0 已移除,低版本中缓存失效频繁,弊大于利)。
(2)存储引擎优化
- 所有表用 InnoDB 引擎(支持事务、行锁、聚簇索引,性能远优于 MyISAM);
- 调整 InnoDB 刷盘策略:
innodb_flush_log_at_trx_commit = 1
(事务提交时刷盘,保证数据安全;若允许少量数据丢失,可设为 2,提升性能)。
4. 架构升级:解决 "资源瓶颈" 问题
(1)读写分离:分担主库压力
- 架构:1 主库(写)+ N 从库(读),主库通过 binlog 同步数据到从库;
- 实现:用 MySQL 原生主从复制,配合中间件(如 MyCat、ProxySQL)实现 "读请求路由到从库,写请求路由到主库";
- 效果:将 80% 的读请求(如商品查询、订单列表)分流到从库,主库仅处理写请求,减少慢查询概率。
(2)添加缓存层:减少数据库访问
- 架构:应用服务→Redis 缓存→MySQL;
- 策略 :
- 高频读数据(如商品详情、用户信息)缓存到 Redis,过期时间设为 5-10 分钟;
- 缓存穿透:用布隆过滤器过滤不存在的 key;
- 缓存雪崩:给缓存 key 加随机过期时间,避免同时失效;
- 效果:90% 以上的读请求直接命中 Redis,无需访问 MySQL,彻底解决读引发的慢查询。
(3)硬件升级:提升资源供给
- 磁盘:机械硬盘(HDD)换为固态硬盘(SSD),IOPS 从 100+ 提升到 10000+,解决 IO 瓶颈;
- 内存 :增加物理内存(如从 16G 升到 64G),提升
innodb_buffer_pool_size
,减少磁盘读写; - CPU:用多核 CPU(如 16 核→32 核),提升复杂 SQL 的计算效率。
索引
一、索引的本质与核心价值
1. 定义
索引是 MySQL 存储引擎(如 InnoDB)为提升查询效率而创建的 有序数据结构,它通过 "提前排序 + 快速定位",将查询从 "全表扫描(O (n))" 优化为 "索引查找(O (log n))"。
2. 核心价值
- 减少扫描行数:无需遍历全表,通过索引直接定位到符合条件的数据;
- 降低磁盘 IO:数据库数据存储在磁盘,索引能减少磁盘读写次数(IO 是数据库性能瓶颈的核心);
- 加速排序 / 分组 :若索引本身有序(如 B + 树索引),
ORDER BY
/GROUP BY
可直接复用索引顺序,避免额外排序。
3. 索引的 "代价"(为何不能滥用)
- 写操作变慢 :
INSERT/UPDATE/DELETE
时,需同步维护索引结构(如 B + 树的插入 / 删除 / 平衡),索引越多,写开销越大; - 占用磁盘空间 :索引是独立存储的文件(如 InnoDB 的
.ibd
文件包含索引和数据),过多索引会浪费磁盘资源。
二、索引的核心类型:从 "物理存储" 划分(聚簇 vs 非聚簇)
MySQL 索引按 数据与索引的存储关系 ,可分为 聚簇索引(Clustered Index) 和 非聚簇索引(Non-Clustered Index,也称二级索引)------ 这是面试最高频的分类,必须吃透两者的区别。
1. 聚簇索引(主键索引):"索引即数据,数据即索引"
(1)核心原理
聚簇索引的 叶子节点直接存储完整的行数据,而非叶子节点存储 "索引键 + 子节点指针"。
- InnoDB 引擎中,聚簇索引与主键强绑定:若表定义了主键,主键就是聚簇索引;若未定义主键,InnoDB 会选第一个非空唯一索引作为聚簇索引;若都没有,会自动生成一个隐藏的 "行 ID" 作为聚簇索引。
- 数据按聚簇索引的顺序物理存储在磁盘上(即 "索引有序 = 数据有序"),相当于 "目录与正文内容合并"。
(2)结构示意图(简化)
非叶子节点(存储主键+子节点指针)
├─ 10 → 指向子节点1
├─ 20 → 指向子节点2
└─ 30 → 指向子节点3
叶子节点(存储完整行数据,按主键有序)
├─ 10: (id=10, name="张三", age=20, ...)
├─ 15: (id=15, name="李四", age=25, ...)
├─ 20: (id=20, name="王五", age=30, ...)
└─ ...(叶子节点用双向链表连接,支持范围查询)
2. 非聚簇索引(二级索引):"索引指向数据"
(1)核心原理
非聚簇索引的 叶子节点不存储完整行数据,只存储 "索引键 + 聚簇索引键(主键)"。
- 非聚簇索引是基于 "非主键字段" 创建的索引(如
name
、age
字段),本质是 "通过非主键字段找到主键,再通过主键找完整数据"。 - 数据的物理存储顺序与非聚簇索引无关(数据仍按聚簇索引顺序存储),相当于 "独立的目录,目录指向正文页码(主键)"。
(2)结构示意图(以 name
字段的非聚簇索引为例)
非叶子节点(存储name+子节点指针)
├─ "李四" → 指向子节点1
├─ "王五" → 指向子节点2
└─ "张三" → 指向子节点3
叶子节点(存储name+主键id,按name有序)
├─ "李四": id=15
├─ "王五": id=20
├─ "张三": id=10
└─ ...(叶子节点双向链表连接)
3. 关键概念:回表(Bookkeeping)
回表是 非聚簇索引查询的 "额外步骤",也是聚簇与非聚簇索引的核心差异体现:
- 当用非聚簇索引查询时,若查询的字段 不在非聚簇索引的叶子节点中(即除了索引键和主键外的其他字段),MySQL 需先通过非聚簇索引找到主键,再通过主键去聚簇索引中查询完整数据 ------ 这个 "从非聚簇索引到聚簇索引的二次查找" 就是回表。
示例:回表过程
假设查询 SELECT id, name, age FROM user WHERE name = "张三"
,且 name
是非聚簇索引:
- 第一步:查
name
非聚簇索引,找到name="张三"
对应的主键id=10
(叶子节点只存name
和id
); - 第二步:用
id=10
查聚簇索引,找到完整行数据(id=10, name="张三", age=20)
,获取age
字段; - 第三步:返回结果
(10, "张三", 20)
。
回表的影响:
- 回表会增加一次磁盘 IO(从非聚簇索引到聚簇索引),若查询频繁回表,会抵消索引的性能优势 ------覆盖索引就是为解决回表问题而生。
4. 聚簇索引 vs 非聚簇索引:面试对比表
对比维度 | 聚簇索引(主键索引) | 非聚簇索引(二级索引) |
---|---|---|
叶子节点存储 | 完整行数据 | 索引键 + 聚簇索引键(主键) |
与数据的关系 | 索引即数据,数据按索引顺序存储 | 索引与数据独立,数据按聚簇索引存储 |
数量限制 | 一张表仅 1 个(InnoDB 引擎) | 一张表可创建多个(通常建议≤5 个) |
查询效率 | 无回表,查询效率高(尤其查完整数据) | 需回表(除非覆盖索引),效率略低 |
适用场景 | 按主键查询(如 WHERE id=10 ) |
按非主键字段查询(如 WHERE name="张三" ) |
引擎支持 | InnoDB 支持,MyISAM 不支持 | InnoDB/MyISAM 均支持(但 MyISAM 非聚簇叶子存数据地址) |
三、其他面试常问的索引类型(按 "功能" 划分)
除了聚簇 / 非聚簇,面试还会问按 "功能" 划分的索引类型,需掌握每种类型的核心特点和适用场景。
索引类型 | 定义 | 特点 / 注意事项 | 适用场景 |
---|---|---|---|
唯一索引 | 索引键值唯一(允许 NULL,但 NULL 只能出现一次) | 避免重复数据(如 user 表的 phone 字段),查询效率与普通索引类似 |
需保证字段唯一性的场景(手机号、身份证号) |
普通索引 | 无任何约束的索引,键值可重复 | 最基础的索引类型,仅用于提升查询效率,不保证唯一性 | 普通查询字段(如 order 表的 status ) |
复合索引 | 基于多个字段创建的索引(如 idx_user_age (user_id, age) ) |
遵循 "最左前缀原则":仅匹配索引的最左字段组合(如 WHERE user_id=1 可用,WHERE age=20 不可用) |
多字段联合查询(如 WHERE user_id=1 AND age=20 ) |
覆盖索引 | 索引的叶子节点包含查询所需的 "所有字段",无需回表 | 本质是 "优化后的非聚簇索引",避免回表的 IO 开销,查询效率接近聚簇索引 | 高频查询且字段固定(如 SELECT id, name FROM user WHERE name="张三" ,idx_name (name, id) 就是覆盖索引) |
前缀索引 | 对字符串字段的 "前 N 个字符" 创建索引(如 idx_email (email(10)) ) |
减少索引存储体积(长字符串索引占空间),但可能降低区分度(需选合适的 N) | 长字符串字段(如 email 、address ) |
全文索引 | 针对文本内容(如 varchar 、text )的分词索引,支持关键词模糊查询 |
不支持 LIKE %xx% ,需用 MATCH AGAINST 语法,InnoDB 5.6+ 支持 |
文章内容、商品描述的关键词搜索(如 "MySQL 优化") |
四、索引底层结构:为什么是 B + 树?(面试核心对比)
所有主流数据库(MySQL、PostgreSQL)的索引底层都是 B + 树 ,而非二叉树、红黑树、B 树。核心原因是:B + 树最适合磁盘 IO 特性,能最小化查询的 IO 次数。
要理解这一点,需先明确数据库索引的存储场景:索引数据存储在磁盘上,每次数据读取都是 "按页读取"(默认页大小 4KB 或 8KB),IO 操作的耗时远高于内存计算 ------ 因此,索引结构的设计目标是 "尽量减少磁盘 IO 次数"。
1. B + 树的结构特点(为磁盘 IO 优化而生)
B + 树是 多路平衡查找树(非二叉),核心结构特点如下:
- 多路分支:非叶子节点可存储多个索引键(如 100 个),大幅降低树的高度(层高);
- 叶子节点存数据:聚簇索引叶子节点存完整行数据,非聚簇索引存主键,非叶子节点仅存索引键 + 指针(不存数据,节省空间);
- 叶子节点有序且链表连接 :所有叶子节点按索引键有序排列,并用双向链表连接,支持高效的范围查询(如
WHERE id BETWEEN 10 AND 30
); - 平衡特性:插入 / 删除时会自动调整树结构,保证树的高度平衡(层高稳定)。
示例:B + 树的层高优势
假设每个磁盘页(非叶子节点)可存储 100 个索引键 + 指针:
- 1 层:1 个节点,最多存 100 条数据;
- 2 层:1(根)+ 100(子)= 101 个节点,最多存 100*100=10^4 条数据;
- 3 层:1 + 100 + 100^2 = 10101 个节点,最多存 100^3=10^6 条数据;
- 4 层:最多存 10^8 条数据。
即便是 1 亿条数据 ,B + 树也只需 4 次磁盘 IO(根节点→子节点→孙子节点→叶子节点),而磁盘 IO 次数是数据库性能的核心瓶颈 ------ 这是 B + 树的核心优势。
2. 为什么不用二叉树?
二叉树的每个节点最多有 2 个分支(左 / 右子树),存在致命缺陷:
- 层高过高:若数据有序插入(如 1,2,3,4...),二叉树会退化为 "线性链表",层高等于数据量(如 100 万条数据,层高 100 万),查询需 100 万次 IO,完全无法使用;
- 未适配磁盘 IO:二叉树的 "二叉分支" 导致每个磁盘页(4KB)只能存 1 个索引键 + 2 个指针,空间利用率极低,进一步推高层高。
示意图:二叉树退化问题
有序插入 1,2,3,4 后,二叉树退化为链表:
1 → 2 → 3 → 4(层高 4,查询 4 需 4 次 IO)
3. 为什么不用红黑树?
红黑树是 "自平衡二叉树",能避免退化,但仍未解决 "层高过高" 的问题:
- 层高仍高 :红黑树的层高约为
2*log2(n)
,若数据量为 100 万,层高约 20(log2(1e6)≈20
),查询需 20 次 IO------ 远高于 B + 树的 3-4 次; - 空间利用率低:红黑树仍是二叉分支,每个磁盘页的空间利用率低,无法像 B + 树那样通过 "多路分支" 降低层高。
对比:100 万数据的 IO 次数
索引结构 | 层高 | 磁盘 IO 次数 |
---|---|---|
二叉树 | 100 万 | 100 万次 |
红黑树 | ~20 | 20 次 |
B + 树 | ~3 | 3 次 |
4. 为什么不用 B 树?
B 树与 B + 树同属 "多路平衡树",但核心差异是 B 树的非叶子节点也存储数据,导致以下问题:
- 非叶子节点空间利用率低:B 树非叶子节点既要存索引键,也要存数据,每个磁盘页能容纳的索引键数量大幅减少(如原本能存 100 个索引键,现在只能存 10 个),层高会比 B + 树高;
- 范围查询效率低:B 树的叶子节点无序(无链表连接),范围查询需回溯到父节点查找下一个叶子,而 B + 树叶子节点的双向链表可直接遍历范围数据;
- 数据冗余:B 树的非叶子节点和叶子节点都存数据,导致数据冗余,浪费磁盘空间。
示意图:B 树与 B + 树的结构差异
B 树(非叶子节点存数据):
非叶子节点:(10, 数据10) → (20, 数据20) → (30, 数据30)
叶子节点:(15, 数据15) → (25, 数据25) → (35, 数据35)(无序,无链表)
B+树(仅叶子节点存数据):
非叶子节点:10 → 20 → 30(仅索引键)
叶子节点:(10, 数据10) ↔ (15, 数据15) ↔ (20, 数据20)(有序,双向链表)
5. 总结:B + 树成为索引首选的核心原因
优势 | 针对磁盘 IO 的优化效果 |
---|---|
多路分支 → 低层高 | 1 亿数据仅需 4 次 IO,远低于红黑树的 20 次、二叉树的百万次 |
非叶子节点仅存索引键 | 提升磁盘页的索引键存储量,进一步降低层高 |
叶子节点链表连接 | 范围查询无需回溯,直接遍历链表,效率比 B 树高 10-100 倍 |
叶子节点存完整数据 | 聚簇索引无需回表,非聚簇索引通过主键关联,兼顾效率与一致性 |
五、面试拓展:索引的高频问题与避坑点
1. 为什么 InnoDB 推荐用 "自增主键" 作为聚簇索引?
若用 UUID(随机字符串)作为主键,会导致 聚簇索引的页分裂(Page Split),严重影响性能:
- 自增主键(1,2,3...):插入时数据按顺序追加到叶子节点末尾,无需移动已有数据,磁盘空间利用率高;
- UUID(随机值):插入时需找到 UUID 对应的有序位置插入,若该位置的磁盘页已满,需拆分页(页分裂),导致大量 IO 操作,且产生碎片。
示例:页分裂问题
自增主键插入:1→2→3→4,直接追加到页末尾,无分裂;
UUID 插入:a1→b3→a2→c5,a2 需插入 a1 和 b3 之间,若页已满,拆分页为两页,移动数据。
2. 复合索引的 "最左前缀原则" 是什么?为什么会有这个原则?
(1)定义
复合索引(如 idx_a_b_c (a,b,c)
)的查询匹配,仅支持 "从最左字段开始的连续组合",不支持 "跳过左字段" 的匹配:
- 支持的查询条件:
WHERE a=1
、WHERE a=1 AND b=2
、WHERE a=1 AND b=2 AND c=3
; - 不支持的查询条件:
WHERE b=2
、WHERE c=3
、WHERE a=1 AND c=3
(跳过 b)。
(2)原因:复合索引的 B + 树结构
复合索引的 B + 树按 "a→b→c" 的顺序排序,非叶子节点的索引键是 (a)
、(a,b)
、(a,b,c)
,叶子节点按 a
有序,同一 a
下按 b
有序,同一 b
下按 c
有序 ------ 若跳过 a
查 b
,索引树无法定位到 b
的位置,只能全表扫描。
3. 哪些情况会导致索引失效?(面试必问)
索引失效即 "查询未使用索引,触发全表扫描",常见场景如下:
- 索引字段用函数 / 运算 :
WHERE SUBSTR(name,1,3)='张'
(name
是索引字段); - 类型不匹配 :
WHERE id='123'
(id
是 int 类型,传入字符串,触发隐式转换); - 模糊查询以 % 开头 :
WHERE name LIKE '%三'
(前缀模糊无法走索引,后缀 / 中间模糊可用); - 逻辑运算符 OR 连接非索引字段 :
WHERE a=1 OR b=2
(a
是索引字段,b
不是); - 查询条件用 NOT IN/<>/IS NOT NULL:这些条件无法利用索引的有序性,需全表扫描;
- MySQL 优化器判断全表扫描更快:若查询结果占表数据的 30% 以上,优化器会认为全表扫描比索引查找快(IO 次数更少)。
4. 覆盖索引和回表的关系?如何用覆盖索引优化查询?
(1)关系
覆盖索引是 "避免回表" 的解决方案 ------ 当非聚簇索引的叶子节点包含 "查询所需的所有字段" 时,无需回表,直接从索引中获取数据。
(2)优化示例
原查询(需回表):
SELECT id, name, age FROM user WHERE name = "张三";
-- name 是非聚簇索引,叶子节点存 (name, id),age 需回表查
优化为覆盖索引(无需回表):
-- 创建包含 name、id、age 的复合索引(覆盖查询字段)
CREATE INDEX idx_name_age ON user (name, id, age);
-- 再次查询时,直接从索引叶子节点获取 (name, id, age),无需回表
SELECT id, name, age FROM user WHERE name = "张三";
六、总结:索引的核心考点图谱
bash
索引
├─ 本质:磁盘上的有序数据结构,减少 IO 次数
├─ 类型划分
│ ├─ 按存储关系:聚簇索引(1个,存数据)vs 非聚簇索引(多个,存主键)
│ └─ 按功能:唯一/普通/复合/覆盖/前缀/全文索引
├─ 核心概念:回表(非聚簇索引二次查找)、最左前缀原则(复合索引)
├─ 底层结构:B+树(多路、低层高、叶子链表、适配磁盘 IO)
│ └─ 对比:二叉树(层高太高)、红黑树(层高仍高)、B树(非叶子存数据)
└─ 面试避坑:自增主键、索引失效场景、覆盖索引优化
事物
1. 事务的概念
事务(Transaction) 是数据库管理系统(DBMS)执行过程中的一个逻辑单位 ,由一个或多个 SQL 语句组成,这些语句要么全部执行成功 ,要么全部执行失败,不能出现部分成功部分失败的情况。
举例:银行转账,A 账户扣钱、B 账户加钱,这两个操作必须在一个事务里完成,否则如果中途出错,可能出现 A 扣了钱但 B 没收到的情况。
2. 事务的四大特性(ACID)
MySQL(尤其是 InnoDB 引擎)通过一系列机制保证事务具备四大特性:原子性(Atomicity) 、一致性(Consistency) 、隔离性(Isolation) 、持久性(Durability)。
2.1 原子性(Atomicity)
- 定义:事务中的所有操作,要么全部成功提交,要么全部失败回滚(Rollback),不会停留在中间状态。
- 实现机制 :
- Undo Log:记录数据修改前的状态,事务回滚时可以撤销已执行的修改。
- 事务提交(commit)时,将 Redo Log 中的数据真正落盘。
2.2 一致性(Consistency)
- 定义 :事务执行前后,数据库从一个合法(满足业务规则)的状态转换到另一个合法状态。
- 实现机制 :
- 数据库的约束(主键、唯一索引、外键等)。
- 业务逻辑代码(例如转账时保证余额不为负)。
- 原子性、隔离性、持久性共同保证一致性。
2.3 隔离性(Isolation)
- 定义:多个事务并发执行时,一个事务的中间状态对其他事务是不可见的,事务之间互不干扰。
- 实现机制 :
- 锁机制(悲观控制并发)
- MVCC(多版本并发控制)(乐观读取,提高并发性能)
- 隔离级别 (MySQL 有 4 种,SQL 标准定义):
- Read Uncommitted(读未提交)
- 可能读取到其他事务未提交的数据(脏读)。
- Read Committed(读已提交)
- 只能读取到其他事务已提交的数据(避免脏读,但可能出现不可重复读)。
- Repeatable Read(可重复读,MySQL 默认)
- 事务开启后,多次读取同一范围数据的结果一致(避免不可重复读,但理论上可能出现幻读,InnoDB 通过 Next-Key Lock 避免大部分幻读)。
- Serializable(串行化)
- 最高隔离级别,所有事务串行执行,无并发问题,但性能最低。
- Read Uncommitted(读未提交)
2.4 持久性(Durability)
- 定义 :事务一旦提交,其对数据库的修改是永久保存的,即使发生宕机也不会丢失。
- 实现机制 :
- Redo Log:记录数据页的物理修改,事务提交时刷写到磁盘,数据库崩溃后可恢复。
- InnoDB 的
innodb_flush_log_at_trx_commit=1
配置(每次事务提交立即刷盘)。
3. 并发事务带来的问题
多个事务同时操作同一批数据时,如果隔离性做得不好,会出现以下问题:
3.1 脏读(Dirty Read)
- 定义:事务 A 读取到了事务 B 尚未提交的数据。
- 风险:如果事务 B 回滚,事务 A 读到的数据就是无效的 "脏数据"。
3.2 不可重复读(Non-Repeatable Read)
- 定义:事务 A 两次读取同一行数据,中间事务 B 修改并提交了该行数据,导致事务 A 两次读取结果不一致。
3.3 幻读(Phantom Read)
- 定义:事务 A 两次按相同条件范围查询,中间事务 B 插入了新的符合条件的数据,导致第二次查询多了 "幻影" 行。
3.4 更新丢失(Lost Update)
- 定义:两个事务同时更新同一行数据,后提交的事务覆盖了先提交事务的修改。
- 避免方式:加锁或使用乐观锁(版本号 / 时间戳)。
4. 隔离级别对比表
隔离级别 | 脏读 | 不可重复读 | 幻读 | 性能 |
---|---|---|---|---|
Read Uncommitted | ✅ | ✅ | ✅ | 高 |
Read Committed | ❌ | ✅ | ✅ | 中 |
Repeatable Read | ❌ | ❌ | 部分 | 中高 |
Serializable | ❌ | ❌ | ❌ | 低 |
MySQL 默认是 Repeatable Read ,并且 InnoDB 通过 Next-Key Lock 机制几乎避免了幻读。
5. 锁机制(保证隔离性的重要手段)
MySQL InnoDB 支持多种锁类型,按粒度分为:
5.1 行锁(Row Lock)
- 作用于索引记录,只锁定符合条件的行。
- 类型:
- 共享锁(S Lock) :允许事务读取一行数据(
SELECT ... LOCK IN SHARE MODE
)。 - 排他锁(X Lock) :允许事务更新或删除一行数据(
SELECT ... FOR UPDATE
、UPDATE
、DELETE
)。
- 共享锁(S Lock) :允许事务读取一行数据(
- 实现前提:必须通过索引条件检索数据,否则会退化为表锁。
5.2 表锁(Table Lock)
- 作用于整张表,开销小、加锁快,但并发性能低。
- MyISAM 引擎只支持表锁;InnoDB 在某些特殊操作(如无索引条件更新)也会使用表锁。
5.3 意向锁(Intention Lock)
- 表级锁,用来表示事务准备在某行上加共享锁或排他锁。
- 类型:
- 意向共享锁(IS Lock)
- 意向排他锁(IX Lock)
- 作用:提高加锁效率,判断是否有事务锁住了某些行,避免全表扫描检测冲突。
5.4 间隙锁(Gap Lock)与 Next-Key Lock
- 间隙锁(Gap Lock):锁定索引记录之间的区间,防止插入新数据(防止幻读)。
- Next-Key Lock:行锁 + 间隙锁的组合,锁定记录及记录前的区间。
- 在 Repeatable Read 下,InnoDB 默认使用 Next-Key Lock 避免幻读。
6. MVCC(多版本并发控制)
MVCC 是 InnoDB 实现高并发读写的核心机制,它通过保存数据的多个版本,实现读不加锁,读写不冲突。
6.1 工作原理
- 每行数据除了存储实际值,还会有隐藏列:
DB_TRX_ID
:最后一次修改该行的事务 IDDB_ROLL_PTR
:指向 Undo Log 中的上一个版本(形成版本链)
- 事务开启时,会创建一个 Read View(读视图),记录当前系统中活跃的事务 ID。
- 快照读(普通 SELECT):根据 Read View 规则,从 Undo Log 链中找到事务可见的版本。
- 当前读(SELECT ... FOR UPDATE / INSERT / UPDATE / DELETE):读取最新版本,并加锁。
6.2 可见性判断规则
假设事务 ID 递增分配:
- 如果数据版本的
DB_TRX_ID
< Read View 中的最小活跃事务 ID → 可见。 - 如果数据版本的
DB_TRX_ID
> Read View 中的最大事务 ID → 不可见。 - 如果数据版本的
DB_TRX_ID
在活跃事务 ID 列表中 → 不可见。 - 否则 → 可见。
6.3 MVCC 与隔离级别的关系
- Read Committed:每次 SELECT 都会创建新的 Read View,因此可能出现不可重复读。
- Repeatable Read:事务开始时创建 Read View,整个事务期间复用,因此可重复读。
7. MySQL 事务日志(binlog /redo log /undo log)
事务的 ACID 特性依赖三大日志实现:
7.1 Redo Log(重做日志)
- 作用 :保证事务的持久性。
- 类型:物理日志,记录数据页的修改。
- 特点 :
- 按页存储,循环写(固定大小的文件组)。
- 写入策略:Write Ahead Log(先写日志,再写磁盘)。
- 崩溃恢复:数据库重启时,根据 Redo Log 重放未刷盘的已提交事务。
7.2 Undo Log(回滚日志)
- 作用 :保证事务的原子性 和隔离性(MVCC)。
- 类型:逻辑日志,记录与原操作相反的操作(例如 INSERT 的 Undo 是 DELETE,UPDATE 的 Undo 是恢复旧值)。
- 特点 :
- 存储在 Undo 段(位于共享表空间或独立 Undo 表空间)。
- 事务回滚时用于撤销已执行的修改。
- MVCC 中用于构建数据的历史版本链。
7.3 Binlog(二进制日志)
- 作用 :用于主从复制 和基于时间点的恢复。
- 类型:逻辑日志,记录 SQL 语句或行变更。
- 特点 :
- 所有引擎都支持。
- 追加写,不会覆盖。
- 三种格式:
- STATEMENT:记录 SQL 语句(可能有主从数据不一致风险)。
- ROW:记录行的变更(推荐,数据一致)。
- MIXED:混合模式。
7.4 三者关系与二阶段提交
- Redo Log 是 InnoDB 引擎层日志,Binlog 是 MySQL Server 层日志。
- 事务提交时,MySQL 采用二阶段提交(2PC) :
- Prepare 阶段 :
- 写 Redo Log(包含 Undo Log 信息),并刷盘。
- 记录 XID(事务 ID)到 Redo Log。
- Commit 阶段 :
- 写 Binlog,并刷盘。
- 在 Redo Log 中写入 Commit Mark。
- Prepare 阶段 :
- 这样保证 Redo Log 与 Binlog 数据一致,主从复制和崩溃恢复不会出错。
8. 面试扩展与实践建议
8.1 RC vs RR 选择
- RC:每次 SELECT 都创建新的 Read View,适合对数据实时性要求高、可接受不可重复读的场景。
- RR(默认):事务开始时创建 Read View,适合需要可重复读的场景(如报表生成)。
- 注意:RC 下 Gap Lock 仅在外键约束和唯一索引场景下生效,幻读风险比 RR 高。
8.2 死锁问题
- 成因:两个或多个事务互相等待对方释放锁。
- 处理 :
- 设置
innodb_lock_wait_timeout
超时自动回滚。 - InnoDB 有死锁检测机制,主动回滚代价最小的事务。
- 设置
- 预防 :
- 统一加锁顺序(例如所有事务都先锁表 A 再锁表 B)。
- 减少事务范围,缩短锁持有时间。
8.3 大事务危害
- 长时间占用锁,阻塞其他事务。
- 回滚时 Undo Log 变大,影响性能。
- 建议:拆分事务,减少单次事务操作的数据量。
8.4 索引与事务的关系
- 行锁依赖索引,如果 SQL 不走索引,会退化为表锁,严重影响并发性能。
- 在高并发场景,合理的索引设计可以减少锁冲突。
9. 总结(思维导图版)
事务
├─ 概念:逻辑执行单元,要么全成功,要么全失败
├─ ACID
│ ├─ 原子性(Atomicity):Undo Log 回滚
│ ├─ 一致性(Consistency):业务规则 + 约束
│ ├─ 隔离性(Isolation):锁 / MVCC
│ └─ 持久性(Durability):Redo Log 刷盘
├─ 并发问题
│ ├─ 脏读 / 不可重复读 / 幻读 / 更新丢失
│ └─ 隔离级别:RU / RC / RR / Serializable
├─ 锁机制
│ ├─ 行锁(S/X)、表锁
│ ├─ 意向锁(IS/IX)
│ └─ Gap / Next-Key Lock(防幻读)
├─ MVCC
│ ├─ 版本链(Undo Log)
│ ├─ Read View
│ └─ 快照读 / 当前读
└─ 三大日志
├─ Redo Log(持久化)
├─ Undo Log(回滚 + MVCC)
└─ Binlog(复制 + 恢复)
其它
一、MySQL 主从同步(Replication)原理
1. 主从同步的作用
- 读写分离:主库(Master)负责写操作,从库(Slave)负责读操作,提升整体吞吐量;
- 高可用:主库宕机后,可切换到从库,保证服务不中断;
- 数据备份:从库可作为热备,避免直接在主库上做备份影响服务。
2. 主从同步的核心流程
MySQL 主从复制是 基于 binlog(二进制日志) 的异步复制过程,主要涉及三个线程:
-
主库(Master)的 binlog dump 线程
- 主库开启 binlog,记录所有 DDL 和 DML 操作;
- 当有从库连接时,Master 创建 binlog dump 线程,发送 binlog 内容给从库。
-
从库(Slave)的 I/O 线程
- 连接主库,接收 binlog 内容,并写入本地的 relay log(中继日志) 文件。
-
从库(Slave)的 SQL 线程
- 读取 relay log,解析出 SQL 语句并执行,实现数据同步。
3. 复制的三种模式
(1)异步复制(Asynchronous Replication)
- Master 写完 binlog 就返回客户端,不关心 Slave 是否接收;
- 优点:性能最好;
- 缺点:Slave 可能丢失数据(Master 宕机时,binlog 未同步到 Slave)。
(2)半同步复制(Semi-Synchronous Replication)
- Master 等待至少一个 Slave 将 binlog 写到 relay log 并返回 ACK 后才提交;
- 优点:数据安全性比异步高;
- 缺点:性能比异步差(多一次网络往返)。
(3)全同步复制(Fully Synchronous Replication)
- Master 等待所有 Slave 都执行完 binlog 才返回客户端;
- 优点:数据零丢失;
- 缺点:性能最差,一般只用于关键业务。
4. 主从延迟的原因与优化
常见原因
- 网络延迟;
- Slave 负载过高(SQL 线程执行慢);
- 大事务(如批量导入)导致 binlog 传输和回放耗时;
- 从库并发回放能力不足(单 SQL 线程)。
优化方法
- 升级网络(万兆网、同机房部署);
- 从库使用更强的硬件(SSD、多核 CPU);
- 大事务拆小;
- 使用 并行复制(MySQL 5.7+ 支持基于 Group Commit 的并行回放);
- 减少从库非必要操作(如避免在从库建大量索引)。
二、分库分表
当数据量或并发量超过单库单表的承载上限时,需要分库分表来水平扩展。
1. 为什么要分库分表
- 单表数据量过大:InnoDB 单表超过 1000 万行,查询性能明显下降;
- 单库并发过高:单 MySQL 实例 TPS/QPS 上限有限(一般 1~5 万 QPS);
- 磁盘 / IO 瓶颈:单库数据文件过大,备份恢复慢。
2. 分库分表的两种方式
(1)垂直拆分(Vertical Split)
- 按功能拆分 :
- 分库:将一个数据库按业务模块拆成多个库(如 user_db、order_db、product_db);
- 分表:将一张表按字段拆成多张表(如 user_base、user_profile)。
- 优点 :
- 降低单库 / 单表复杂度;
- 便于按业务独立扩展。
- 缺点 :
- 跨库事务和关联查询复杂。
(2)水平拆分(Horizontal Split)
- 按数据行拆分:将一张表的数据按某种规则分散到多张表 / 多个库中;
- 常见分片策略 :
- 范围分片:按 ID 范围(如 1~100 万放表 1,100~200 万放表 2);
- 哈希分片 :
hash(key) % N
,数据分布均匀,但扩容麻烦; - 时间分片:按天 / 月 / 年拆分(适合日志、订单等有时序特性的数据);
- 目录表分片:维护一张路由表,记录 key 到实际库表的映射。
- 优点 :
- 理论上可无限扩展;
- 高并发支持好。
- 缺点 :
- 分布式事务难保证;
- 跨分片关联查询复杂。
3. 分库分表的挑战与解决方案
(1)全局唯一 ID
- 不能再用自增主键(各分片会重复);
- 解决方案:
- 雪花算法(Snowflake):生成 64 位 ID(时间戳 + 机器 ID + 序列号);
- 号段模式(Leaf / 美团):批量分配 ID 段;
- UUID:无序,不推荐做主键(索引性能差)。
(2)跨库事务
- 两阶段提交(2PC):一致性高但性能差;
- TCC(Try-Confirm-Cancel):业务层实现;
- Saga 模式:长事务拆成多个本地事务,补偿机制保证最终一致。
(3)跨分片关联查询
- 全局表:将字典表等小表复制到每个分片;
- ER 分片:将关联关系紧密的表放在同一分片;
- 应用层聚合:多次查询后在应用层合并结果。
4. 常用分库分表中间件
- ShardingSphere(推荐) :
- 包含 Sharding-JDBC(客户端方案)和 Sharding-Proxy(代理方案);
- 功能全面(读写分离、分库分表、分布式事务)。
- MyCAT :
- 基于 Proxy 模式,部署简单,但性能略低于 Sharding-JDBC。
- TDDL(淘宝分布式数据层) :
- 阿里内部使用,开源版本功能有限。
三、面试高频补充问题
1. InnoDB 与 MyISAM 区别
特性 | InnoDB | MyISAM |
---|---|---|
事务支持 | ✅ | ❌ |
行锁 | ✅ | ❌(只支持表锁) |
外键 | ✅ | ❌ |
全文索引 | ✅(5.6 + 支持) | ✅ |
表空间 | 支持独立表空间(.ibd) | 每个表两个文件(.MYD/.MYI) |
崩溃恢复 | ✅(Redo/Undo) | ❌(需 repair table) |
2. MySQL 索引的最左前缀原则
- 复合索引
(a,b,c)
,查询条件必须从 a 开始连续使用,才能用到索引; - 例:
WHERE a=1
✅WHERE a=1 AND b=2
✅WHERE b=2
❌(跳过 a)WHERE a=1 AND c=3
❌(跳过 b)
3. MySQL 如何优化慢查询
- 开启慢查询日志
slow_query_log
; - 使用
EXPLAIN
分析执行计划; - 优化索引(避免全表扫描、索引失效);
- 减少返回字段和行数;
- 避免大事务、长连接;
- 适当增加缓存(Redis)。
4. MySQL 事务隔离级别实现原理
- Read Uncommitted:直接读最新数据;
- Read Committed:每次 SELECT 都生成新的 Read View;
- Repeatable Read:事务开始时生成 Read View,整个事务复用;
- Serializable:读也加锁(范围锁),完全串行化。
5. MySQL 主从延迟监控
SHOW SLAVE STATUS\G
查看Seconds_Behind_Master
;- 使用 Percona Monitoring 或 Prometheus+Grafana 监控延迟趋势;
- 延迟超过阈值可报警(如 5 秒)。
6. MySQL 高可用架构
- 主从 + 哨兵(MySQL 自带 MHA、Orchestrator);
- InnoDB Cluster(MySQL 官方集群方案);
- Galera Cluster(多主模式,同步复制)。
7. MySQL 备份与恢复
- 物理备份 :
XtraBackup
(在线热备,支持增量备份); - 逻辑备份 :
mysqldump
(生成 SQL 文件,适合小数据量); - binlog 恢复:基于时间点恢复(Point-in-Time Recovery)。
8. MySQL 锁等待与死锁
- 查看锁等待:
SHOW PROCESSLIST
、INFORMATION_SCHEMA.INNODB_LOCKS
; - 死锁检测:
SHOW ENGINE INNODB STATUS\G
; - 预防死锁:统一加锁顺序、缩小事务范围、加锁时间尽量短。
四、总结(思维导图版)
plaintext
MySQL 高级
├─ 主从同步
│ ├─ 作用:读写分离、高可用、备份
│ ├─ 流程:binlog dump → I/O线程 → relay log → SQL线程
│ ├─ 复制模式:异步 / 半同步 / 全同步
│ └─ 延迟优化:并行复制、大事务拆分、网络优化
├─ 分库分表
│ ├─ 垂直拆分(按功能/字段)
│ ├─ 水平拆分(范围/哈希/时间/目录表)
│ ├─ 挑战:全局ID、跨库事务、跨分片查询
│ └─ 中间件:ShardingSphere、MyCAT、TDDL
└─ 面试高频
├─ InnoDB vs MyISAM
├─ 索引最左前缀原则
├─ 慢查询优化
├─ 事务隔离级别实现
├─ 主从延迟监控
├─ 高可用架构
├─ 备份恢复
└─ 锁等待与死锁