后端八股之mysql

1.慢查询

慢查询是 MySQL 中执行耗时超过 long_query_time 阈值的 SQL 语句,阈值可通过配置调整(建议根据业务响应要求设为 1-3 秒)。

慢查询的本质是 "SQL 执行效率低" 或 "资源供给不足",具体可分为 5 大类原因:

原因

1. SQL 本身的问题(最常见)

(1)全表扫描 / 全索引扫描
  • 未建索引:如 SELECT * FROM orders WHERE user_id = 123user_id 无索引,触发全表扫描(EXPLAINtype=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=0status 无索引,索引失效)。
(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 与业务链路绑定:

  1. 探针埋点:在应用服务(如 Java 服务)中挂载 Agent(如 SkyWalking Agent),自动拦截 JDBC 调用,采集 SQL 执行时间、语句、数据库地址等信息;
  2. 链路关联 :将 SQL 数据与当前请求的 Trace ID(全链路唯一标识)绑定,形成 "用户请求→网关→服务 A→服务 B→MySQL" 的完整链路;
  3. 可视化展示:在 UI 界面中定位慢 SQL 所属的接口、请求参数、调用链环节,快速关联业务场景。
(2)SkyWalking 实战步骤
  1. 部署 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
  2. 定位慢 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)核心原理

非聚簇索引的 叶子节点不存储完整行数据,只存储 "索引键 + 聚簇索引键(主键)"

  • 非聚簇索引是基于 "非主键字段" 创建的索引(如 nameage 字段),本质是 "通过非主键字段找到主键,再通过主键找完整数据"。
  • 数据的物理存储顺序与非聚簇索引无关(数据仍按聚簇索引顺序存储),相当于 "独立的目录,目录指向正文页码(主键)"。
(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 是非聚簇索引:

  1. 第一步:查 name 非聚簇索引,找到 name="张三" 对应的主键 id=10(叶子节点只存 nameid);
  2. 第二步:用 id=10 查聚簇索引,找到完整行数据 (id=10, name="张三", age=20),获取 age 字段;
  3. 第三步:返回结果 (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) 长字符串字段(如 emailaddress
全文索引 针对文本内容(如 varchartext)的分词索引,支持关键词模糊查询 不支持 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=1WHERE a=1 AND b=2WHERE a=1 AND b=2 AND c=3
  • 不支持的查询条件:WHERE b=2WHERE c=3WHERE a=1 AND c=3(跳过 b)。
(2)原因:复合索引的 B + 树结构

复合索引的 B + 树按 "a→b→c" 的顺序排序,非叶子节点的索引键是 (a)(a,b)(a,b,c),叶子节点按 a 有序,同一 a 下按 b 有序,同一 b 下按 c 有序 ------ 若跳过 ab,索引树无法定位到 b 的位置,只能全表扫描。

3. 哪些情况会导致索引失效?(面试必问)

索引失效即 "查询未使用索引,触发全表扫描",常见场景如下:

  • 索引字段用函数 / 运算WHERE SUBSTR(name,1,3)='张'name 是索引字段);
  • 类型不匹配WHERE id='123'id 是 int 类型,传入字符串,触发隐式转换);
  • 模糊查询以 % 开头WHERE name LIKE '%三'(前缀模糊无法走索引,后缀 / 中间模糊可用);
  • 逻辑运算符 OR 连接非索引字段WHERE a=1 OR b=2a 是索引字段,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 标准定义):
    1. Read Uncommitted(读未提交)
      • 可能读取到其他事务未提交的数据(脏读)。
    2. Read Committed(读已提交)
      • 只能读取到其他事务已提交的数据(避免脏读,但可能出现不可重复读)。
    3. Repeatable Read(可重复读,MySQL 默认)
      • 事务开启后,多次读取同一范围数据的结果一致(避免不可重复读,但理论上可能出现幻读,InnoDB 通过 Next-Key Lock 避免大部分幻读)。
    4. Serializable(串行化)
      • 最高隔离级别,所有事务串行执行,无并发问题,但性能最低。

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 UPDATEUPDATEDELETE)。
  • 实现前提:必须通过索引条件检索数据,否则会退化为表锁。

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:最后一次修改该行的事务 ID
    • DB_ROLL_PTR:指向 Undo Log 中的上一个版本(形成版本链)
  • 事务开启时,会创建一个 Read View(读视图),记录当前系统中活跃的事务 ID。
  • 快照读(普通 SELECT):根据 Read View 规则,从 Undo Log 链中找到事务可见的版本。
  • 当前读(SELECT ... FOR UPDATE / INSERT / UPDATE / DELETE):读取最新版本,并加锁。

6.2 可见性判断规则

假设事务 ID 递增分配:

  1. 如果数据版本的 DB_TRX_ID < Read View 中的最小活跃事务 ID → 可见。
  2. 如果数据版本的 DB_TRX_ID > Read View 中的最大事务 ID → 不可见。
  3. 如果数据版本的 DB_TRX_ID 在活跃事务 ID 列表中 → 不可见。
  4. 否则 → 可见。

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)
    1. Prepare 阶段
      • 写 Redo Log(包含 Undo Log 信息),并刷盘。
      • 记录 XID(事务 ID)到 Redo Log。
    2. Commit 阶段
      • 写 Binlog,并刷盘。
      • 在 Redo Log 中写入 Commit Mark。
  • 这样保证 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(二进制日志) 的异步复制过程,主要涉及三个线程:

  1. 主库(Master)的 binlog dump 线程

    • 主库开启 binlog,记录所有 DDL 和 DML 操作;
    • 当有从库连接时,Master 创建 binlog dump 线程,发送 binlog 内容给从库。
  2. 从库(Slave)的 I/O 线程

    • 连接主库,接收 binlog 内容,并写入本地的 relay log(中继日志) 文件。
  3. 从库(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)
  • 按数据行拆分:将一张表的数据按某种规则分散到多张表 / 多个库中;
  • 常见分片策略
    1. 范围分片:按 ID 范围(如 1~100 万放表 1,100~200 万放表 2);
    2. 哈希分片hash(key) % N,数据分布均匀,但扩容麻烦;
    3. 时间分片:按天 / 月 / 年拆分(适合日志、订单等有时序特性的数据);
    4. 目录表分片:维护一张路由表,记录 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 PROCESSLISTINFORMATION_SCHEMA.INNODB_LOCKS
  • 死锁检测:SHOW ENGINE INNODB STATUS\G
  • 预防死锁:统一加锁顺序、缩小事务范围、加锁时间尽量短。

四、总结(思维导图版)

plaintext

复制代码
MySQL 高级
├─ 主从同步
│  ├─ 作用:读写分离、高可用、备份
│  ├─ 流程:binlog dump → I/O线程 → relay log → SQL线程
│  ├─ 复制模式:异步 / 半同步 / 全同步
│  └─ 延迟优化:并行复制、大事务拆分、网络优化
├─ 分库分表
│  ├─ 垂直拆分(按功能/字段)
│  ├─ 水平拆分(范围/哈希/时间/目录表)
│  ├─ 挑战:全局ID、跨库事务、跨分片查询
│  └─ 中间件:ShardingSphere、MyCAT、TDDL
└─ 面试高频
   ├─ InnoDB vs MyISAM
   ├─ 索引最左前缀原则
   ├─ 慢查询优化
   ├─ 事务隔离级别实现
   ├─ 主从延迟监控
   ├─ 高可用架构
   ├─ 备份恢复
   └─ 锁等待与死锁
相关推荐
洋不写bug14 小时前
数据库数据类型,数据值类型,字符串类型,日期类型详解
数据库·mysql
Paraverse_徐志斌14 小时前
RAG架构(检索增强生成)与向量数据库
数据库·ai·llm·embedding·milvus·rag
NineData15 小时前
NineData将亮相第27届GOPS全球运维大会,并带来技术演讲
运维·数据库·ninedata·智能·ai agent·数据管理工具·gops全球运维大会
Java水解15 小时前
MySQL 中 ROW_NUMBER() 函数详解
后端·mysql
不良人天码星15 小时前
谈谈redis的持久化
数据库·redis·缓存
qq_4798754316 小时前
TimerFd & Epoll
java·服务器·数据库
绵绵细雨中的乡音16 小时前
MySQL 数据库核心操作全解析:从创建到备份与连接管理
数据库·oracle
wayuncn18 小时前
哈尔滨电商企业服务器托管方案
运维·服务器·数据库
重整旗鼓~18 小时前
27.Redisson基本使用和可重入性
数据库·redis·缓存