MySQL 8.0 性能优化与索引原理

前言

💡 痛点: EXPLAIN 看得一头雾水?索引失效频繁发生?慢查询优化全靠猜?连接池配置一塌糊涂?

🎯 解决方案: 从索引结构原理→慢查询分析→执行计划解读→索引设计规范,系统掌握 MySQL 性能优化。
#mermaid-svg-5cm3AScfaPDu5gs3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5cm3AScfaPDu5gs3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5cm3AScfaPDu5gs3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5cm3AScfaPDu5gs3 .error-icon{fill:#552222;}#mermaid-svg-5cm3AScfaPDu5gs3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5cm3AScfaPDu5gs3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5cm3AScfaPDu5gs3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5cm3AScfaPDu5gs3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5cm3AScfaPDu5gs3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5cm3AScfaPDu5gs3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5cm3AScfaPDu5gs3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5cm3AScfaPDu5gs3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5cm3AScfaPDu5gs3 .marker.cross{stroke:#333333;}#mermaid-svg-5cm3AScfaPDu5gs3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5cm3AScfaPDu5gs3 p{margin:0;}#mermaid-svg-5cm3AScfaPDu5gs3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5cm3AScfaPDu5gs3 .cluster-label text{fill:#333;}#mermaid-svg-5cm3AScfaPDu5gs3 .cluster-label span{color:#333;}#mermaid-svg-5cm3AScfaPDu5gs3 .cluster-label span p{background-color:transparent;}#mermaid-svg-5cm3AScfaPDu5gs3 .label text,#mermaid-svg-5cm3AScfaPDu5gs3 span{fill:#333;color:#333;}#mermaid-svg-5cm3AScfaPDu5gs3 .node rect,#mermaid-svg-5cm3AScfaPDu5gs3 .node circle,#mermaid-svg-5cm3AScfaPDu5gs3 .node ellipse,#mermaid-svg-5cm3AScfaPDu5gs3 .node polygon,#mermaid-svg-5cm3AScfaPDu5gs3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5cm3AScfaPDu5gs3 .rough-node .label text,#mermaid-svg-5cm3AScfaPDu5gs3 .node .label text,#mermaid-svg-5cm3AScfaPDu5gs3 .image-shape .label,#mermaid-svg-5cm3AScfaPDu5gs3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-5cm3AScfaPDu5gs3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5cm3AScfaPDu5gs3 .rough-node .label,#mermaid-svg-5cm3AScfaPDu5gs3 .node .label,#mermaid-svg-5cm3AScfaPDu5gs3 .image-shape .label,#mermaid-svg-5cm3AScfaPDu5gs3 .icon-shape .label{text-align:center;}#mermaid-svg-5cm3AScfaPDu5gs3 .node.clickable{cursor:pointer;}#mermaid-svg-5cm3AScfaPDu5gs3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5cm3AScfaPDu5gs3 .arrowheadPath{fill:#333333;}#mermaid-svg-5cm3AScfaPDu5gs3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5cm3AScfaPDu5gs3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5cm3AScfaPDu5gs3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5cm3AScfaPDu5gs3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5cm3AScfaPDu5gs3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5cm3AScfaPDu5gs3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5cm3AScfaPDu5gs3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5cm3AScfaPDu5gs3 .cluster text{fill:#333;}#mermaid-svg-5cm3AScfaPDu5gs3 .cluster span{color:#333;}#mermaid-svg-5cm3AScfaPDu5gs3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5cm3AScfaPDu5gs3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5cm3AScfaPDu5gs3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-5cm3AScfaPDu5gs3 .icon-shape,#mermaid-svg-5cm3AScfaPDu5gs3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5cm3AScfaPDu5gs3 .icon-shape p,#mermaid-svg-5cm3AScfaPDu5gs3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5cm3AScfaPDu5gs3 .icon-shape .label rect,#mermaid-svg-5cm3AScfaPDu5gs3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5cm3AScfaPDu5gs3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5cm3AScfaPDu5gs3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5cm3AScfaPDu5gs3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 优化流程
慢查询日志
EXPLAIN 分析
索引检查
SQL 重写
新增/删除索引
性能验证
索引结构
InnoDB 索引
聚簇索引

主键=B+树,叶子存整行数据
辅助索引

主键=B+树,叶子存主键值
叶子节点1

完整数据行
叶子节点2

完整数据行
Page 16KB
Page 16KB

MySQL 8.0 新特性速览:

特性 说明 性能影响
Instant ADD COLUMN 秒级添加字段(不锁表) DDL 不阻塞写入
原子 DDL DDL 操作可回滚 数据安全提升
窗口函数 ROW_NUMBER / RANK / LAG SQL 简化,避免自关联
CTE WITH 子句,递归查询 代码可读性大幅提升
Hash Join 大表关联优化(替代 BNL) JOIN 性能提升 10x
不可见索引 INVISIBLE 索引(灰度验证) 线上安全调优
降序索引 idx(a DESC, b ASC) 避免 filesort

一、InnoDB 索引结构原理

1.1 B+ 树 vs B 树 vs 二叉树

#mermaid-svg-Hff9BhiDMzokqrbY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Hff9BhiDMzokqrbY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Hff9BhiDMzokqrbY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Hff9BhiDMzokqrbY .error-icon{fill:#552222;}#mermaid-svg-Hff9BhiDMzokqrbY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Hff9BhiDMzokqrbY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Hff9BhiDMzokqrbY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Hff9BhiDMzokqrbY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Hff9BhiDMzokqrbY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Hff9BhiDMzokqrbY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Hff9BhiDMzokqrbY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Hff9BhiDMzokqrbY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Hff9BhiDMzokqrbY .marker.cross{stroke:#333333;}#mermaid-svg-Hff9BhiDMzokqrbY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Hff9BhiDMzokqrbY p{margin:0;}#mermaid-svg-Hff9BhiDMzokqrbY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Hff9BhiDMzokqrbY .cluster-label text{fill:#333;}#mermaid-svg-Hff9BhiDMzokqrbY .cluster-label span{color:#333;}#mermaid-svg-Hff9BhiDMzokqrbY .cluster-label span p{background-color:transparent;}#mermaid-svg-Hff9BhiDMzokqrbY .label text,#mermaid-svg-Hff9BhiDMzokqrbY span{fill:#333;color:#333;}#mermaid-svg-Hff9BhiDMzokqrbY .node rect,#mermaid-svg-Hff9BhiDMzokqrbY .node circle,#mermaid-svg-Hff9BhiDMzokqrbY .node ellipse,#mermaid-svg-Hff9BhiDMzokqrbY .node polygon,#mermaid-svg-Hff9BhiDMzokqrbY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Hff9BhiDMzokqrbY .rough-node .label text,#mermaid-svg-Hff9BhiDMzokqrbY .node .label text,#mermaid-svg-Hff9BhiDMzokqrbY .image-shape .label,#mermaid-svg-Hff9BhiDMzokqrbY .icon-shape .label{text-anchor:middle;}#mermaid-svg-Hff9BhiDMzokqrbY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Hff9BhiDMzokqrbY .rough-node .label,#mermaid-svg-Hff9BhiDMzokqrbY .node .label,#mermaid-svg-Hff9BhiDMzokqrbY .image-shape .label,#mermaid-svg-Hff9BhiDMzokqrbY .icon-shape .label{text-align:center;}#mermaid-svg-Hff9BhiDMzokqrbY .node.clickable{cursor:pointer;}#mermaid-svg-Hff9BhiDMzokqrbY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Hff9BhiDMzokqrbY .arrowheadPath{fill:#333333;}#mermaid-svg-Hff9BhiDMzokqrbY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Hff9BhiDMzokqrbY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Hff9BhiDMzokqrbY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Hff9BhiDMzokqrbY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Hff9BhiDMzokqrbY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Hff9BhiDMzokqrbY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Hff9BhiDMzokqrbY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Hff9BhiDMzokqrbY .cluster text{fill:#333;}#mermaid-svg-Hff9BhiDMzokqrbY .cluster span{color:#333;}#mermaid-svg-Hff9BhiDMzokqrbY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Hff9BhiDMzokqrbY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Hff9BhiDMzokqrbY rect.text{fill:none;stroke-width:0;}#mermaid-svg-Hff9BhiDMzokqrbY .icon-shape,#mermaid-svg-Hff9BhiDMzokqrbY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Hff9BhiDMzokqrbY .icon-shape p,#mermaid-svg-Hff9BhiDMzokqrbY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Hff9BhiDMzokqrbY .icon-shape .label rect,#mermaid-svg-Hff9BhiDMzokqrbY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Hff9BhiDMzokqrbY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Hff9BhiDMzokqrbY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Hff9BhiDMzokqrbY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} B树 vs B+树区别
B树: 所有节点存数据
B+树: 仅叶子存数据

内部节点只存索引
B+树结构
root节点
内部节点A

30
内部节点B

60
叶子15,20,30
叶子40,45,50
叶子60,70,80
叶子90,95,100
Page 16KB
Page 16KB

sql 复制代码
-- ===== B+树 深度计算 =====

-- 一棵 3 层 B+树能存多少数据?

-- InnoDB Page 大小:16KB
-- 假设主键为 BIGINT:8字节
-- 每行数据估算:1KB(含 VARCHAR 平均长度)

-- 每页可存指针数 = 16KB / 8B = 2048 个
-- 每页可存数据行 = 16KB / 1KB = 16 行

-- 根节点:2048 个指针 → 指向 2048 个内部节点
-- 每个内部节点:2048 个指针 → 指向 2048 个叶子
-- 叶子层:2048 × 2048 × 16 = 67,108,864 行 ≈ 6700 万行!

-- 结论:3 层 B+树足以支撑千万级数据,且树高稳定为 3
-- 任何查询只需 3 次磁盘 I/O(根 → 内部 → 叶子)

-- ===== 聚簇索引 vs 辅助索引 =====

-- 聚簇索引(Clustered Index)
-- 特点:叶节点存储完整数据行,表只能有一个聚簇索引
CREATE TABLE orders (
    order_id BIGINT PRIMARY KEY,  -- 主键 → 聚簇索引
    customer_id BIGINT,
    amount DECIMAL(10,2),
    created_at DATETIME
);
-- 查询:SELECT * FROM orders WHERE order_id = 100;
-- 执行:聚簇索引树搜索 → 直接命中数据行(覆盖索引,无需回表)

-- 辅助索引(Secondary Index)
-- 特点:叶节点存储索引列 + 主键值
CREATE INDEX idx_customer ON orders(customer_id);
-- 查询:SELECT * FROM orders WHERE customer_id = 999;
-- 执行:辅助索引树搜索(idx_customer)→ 获得主键 → 回表查询聚簇索引 → 获取完整行
-- 回表:select * 会导致回表(需访问聚簇索引获取完整数据)

-- 覆盖索引优化(无需回表)
SELECT customer_id, order_id FROM orders WHERE customer_id = 999;
-- 执行:idx_customer 叶子已包含 select 所需字段,无需回表

1.2 联合索引与最左前缀原则

sql 复制代码
-- ===== 联合索引结构 =====

-- 创建联合索引
CREATE INDEX idx_user_status_time ON users(status, type, created_at);

-- 索引结构(B+树,按建索引的顺序排序):
-- 第1层:status (ENUM: 0=禁用, 1=正常, 2=冻结)
-- 第2层:type (INT: 1=免费, 2=付费, 3=企业)
-- 第3层:created_at (DATETIME)

-- 等值 + 范围组合:
-- ✅ SELECT * FROM users WHERE status = 1;
--    → 只用 status 列,走索引
--
-- ✅ SELECT * FROM users WHERE status = 1 AND type = 2;
--    → 走 status + type,索引有效
--
-- ✅ SELECT * FROM users WHERE status = 1 AND type = 2 AND created_at > '2024-01-01';
--    → 全部走索引(最左前缀 + 范围 on created_at)
--
-- ✅ SELECT * FROM users WHERE status = 1 AND created_at > '2024-01-01';
--    → 走 status(碰到 type 列时断了),created_at 无法利用索引
--
-- ❌ SELECT * FROM users WHERE type = 2;
--    → 不走索引(最左前缀不满足,跳过 status)
--
-- ❌ SELECT * FROM users WHERE created_at > '2024-01-01';
--    → 不走索引(最左列 status 完全跳过)

-- ===== 索引列顺序选择 =====

-- 原则:区分度高的列放前面
-- status(3种) vs type(3种) vs created_at(亿级)

-- ❌ 错误顺序:created_at 区分度最高,却在最后
CREATE INDEX idx_bad ON users(status, type, created_at);

-- ✅ 正确顺序:区分度高的 created_at 放最后
CREATE INDEX idx_good ON users(created_at, type, status);

-- ===== Index Condition Pushdown(ICP)=====

-- MySQL 5.6+ 支持 ICP
-- 将 WHERE 条件下推到索引层面,减少回表次数
EXPLAIN SELECT * FROM users 
WHERE status = 1 
  AND type IN (2, 3) 
  AND nickname LIKE '张%';
-- 无 ICP:先通过 (status, type) 找到主键,逐一回表,再过滤 nickname
-- 有 ICP:直接在索引中过滤 nickname,无需回表

二、慢查询分析与诊断

2.1 慢查询日志配置

sql 复制代码
-- ===== 慢查询日志配置 =====

-- 查看当前配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
SHOW VARIABLES LIKE 'log_output';

-- 临时开启(重启失效)
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;          -- 超过 1 秒记录
SET GLOBAL log_output = 'TABLE,FILE';      -- 同时输出到表和文件
SET GLOBAL slow_query_log_file = '/var/lib/mysql/mysql-slow.log';

-- 永久配置(my.cnf / my.ini)
-- [mysqld]
-- slow_query_log = 1
-- slow_query_log_file = /var/lib/mysql/mysql-slow.log
-- long_query_time = 1
-- log_queries_not_using_indexes = 1  -- 记录未使用索引的查询

-- ===== mysql.slow_log 表(MySQL 8.0)=====

-- MySQL 8.0 将慢查询记录到 slow_log 表(system_time_zone 支持)
SELECT 
    start_time,
    query_time,
    lock_time,
    rows_sent,
    rows_examined,
    db,
    sql_text
FROM mysql.slow_log
ORDER BY query_time DESC
LIMIT 10;

-- ===== pt-query-digest 分析慢查询 =====

-- pt-query-digest 是 Percona Toolkit 工具,分析慢查询日志

-- 安装
-- yum install percona-toolkit  # CentOS
-- apt install percona-toolkit  # Ubuntu

-- 分析慢查询日志
-- pt-query-digest /var/lib/mysql/mysql-slow.log

-- 分析结果示例:
-- # Profile
-- # Rank Query ID   Response time  Calls  R/Call  Item
-- # ==== =========== =============== =====  ======= ====
-- #    1 0x1234...   5.2341 45.2%     500  0.0105  SELECT orders
-- #    2 0x5678...   2.1234 18.3%     200  0.0106  UPDATE users

-- 输出优化建议:
-- Query_time > 0.5s: 考虑添加索引
-- Rows_examined > 10×Rows_sent: 考虑覆盖索引

2.2 performance_schema 诊断

sql 复制代码
-- ===== 开启 performance_schema =====

SHOW VARIABLES LIKE 'performance_schema';
-- 默认 ON,无需配置

-- ===== 监控 SQL 执行统计 =====

-- 按 SQL 文本分组统计(耗时TOP)
SELECT 
    DIGEST_TEXT AS sql_query,
    COUNT_STAR AS exec_count,
    SUM_TIMER_WAIT / 1000000000000 AS total_time_sec,
    AVG_TIMER_WAIT / 1000000000000 AS avg_time_sec,
    SUM_ROWS_EXAMINED AS rows_scanned,
    SUM_ROWS_SENT AS rows_sent,
    SUM_SORT_MERGE_PASSES AS sort_merge_passes,
    SUM_SORT_ROWS AS rows_sorted,
    SUM_NO_INDEX_USED AS no_index_count,
    SUM_NO_GOOD_INDEX_USED AS bad_index_count
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 20;

-- ===== 监控表级 I/O =====

SELECT 
    OBJECT_SCHEMA,
    OBJECT_NAME,
    COUNT_FETCH AS fetch_count,
    SUM_NUMBER_OF_BYTES_READ AS bytes_read,
    COUNT_INSERT AS insert_count,
    COUNT_UPDATE AS update_count,
    COUNT_DELETE AS delete_count
FROM performance_schema.table_io_waits_summary_by_table
WHERE OBJECT_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema')
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;

-- ===== 监控索引使用情况 =====

SELECT 
    OBJECT_SCHEMA,
    OBJECT_NAME,
    INDEX_NAME,
    COUNT_FETCH,
    COUNT_INSERT,
    COUNT_UPDATE,
    COUNT_DELETE
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE OBJECT_SCHEMA = 'shop'
ORDER BY COUNT_FETCH DESC;
-- 辅助定位:哪些索引从未被使用(可安全删除)

三、EXPLAIN 执行计划详解

3.1 各字段含义

sql 复制代码
-- ===== EXPLAIN ANALYZE(MySQL 8.0)=====

EXPLAIN ANALYZE 
SELECT u.id, u.username, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.status = 1
GROUP BY u.id
ORDER BY order_count DESC
LIMIT 10;

-- 输出示例:
-- -> Limit: 10 row(s)  (cost=1234.45 rows=10) (actual time=45.67..45.89 rows=10 loops=1)
--     -> Sort: <temporary>.<filesort>, with temporary table using <temporary>.<sort_key>
--         -> Table scan on <auto_distinct_key> using index idx_status on u  (cost=...)
--             -> Nested loop left join  (cost=...)
--                 -> Index lookup on u using idx_status (status=1)  (actual...)
--                 -> Index range scan on o using idx_user_id over (u.id)  (actual...)

-- 关键信息:
-- cost=1234.45:优化器估算成本
-- actual time=45.67..45.89:实际执行时间范围
-- rows=10 loops=1:返回10行,循环1次
-- Table scan:全表扫描(注意)
-- Index lookup:索引查找(高效)

-- ===== type 字段(访问类型,从优到劣)=====

-- system:表只有一行(系统表)
-- const:主键/唯一索引等值查询,最多匹配一行
EXPLAIN SELECT * FROM users WHERE id = 1;  -- const

-- eq_ref:关联查询,使用主键或唯一索引
EXPLAIN SELECT * FROM orders o, users u WHERE o.user_id = u.id;  -- eq_ref

-- ref:普通索引等值查询
EXPLAIN SELECT * FROM orders WHERE user_id = 999;  -- ref

-- range:索引范围扫描
EXPLAIN SELECT * FROM orders WHERE id > 100 AND id < 200;  -- range

-- index:全索引扫描(比全表扫描好,但不如 range)
EXPLAIN SELECT id, username FROM users;  -- index (覆盖索引)

-- ALL:全表扫描(最差,需优化)
EXPLAIN SELECT * FROM users WHERE username = 'zhangsan';  -- ALL

-- ===== key_len 计算 =====

-- 估算索引使用程度
EXPLAIN SELECT * FROM users WHERE status = 1 AND type = 2;
-- key_len = status(1字节 + NULL标志1字节) + type(4字节 + NULL标志1字节) = 7B
-- 如果 key_len < 索引总长度,说明只用了部分索引

-- ===== rows 字段 =====

-- 估算需要扫描的行数(不是返回行数)
-- rows 越大,扫描成本越高
EXPLAIN SELECT * FROM orders WHERE status = 0;  -- status=0 占95%,rows 很大

-- ===== Extra 字段(关键优化提示)=====

-- Using filesort:需要额外排序(内存或磁盘),尽量避免
EXPLAIN SELECT * FROM orders ORDER BY created_at DESC;  -- Using filesort

-- Using temporary:使用临时表,尽量避免
EXPLAIN SELECT username, COUNT(*) FROM orders GROUP BY username;  -- Using temporary

-- Using index:覆盖索引,无需回表
EXPLAIN SELECT id, status FROM orders WHERE status = 1;  -- Using index

-- Using index condition:索引条件下推(ICP)
EXPLAIN SELECT * FROM orders WHERE status = 1 AND amount > 100;  -- Using index condition

-- Using where:服务层额外过滤
EXPLAIN SELECT * FROM orders WHERE amount > 100;  -- Using where

-- Using join buffer (Block Nested Loop):BNL 大表关联,性能差
EXPLAIN SELECT * FROM orders, users WHERE orders.user_id = users.id;  -- BNL

-- Using MRR:Multi-Range Read,优化随机 I/O
EXPLAIN SELECT * FROM orders WHERE id IN (1, 5, 10);

3.2 常见优化案例

sql 复制代码
-- ===== 案例1: 深分页优化 =====

-- ❌ 深分页(越翻越慢)
SELECT * FROM orders 
WHERE status = 1 
ORDER BY id DESC 
LIMIT 1000000, 10;

-- 分析:LIMIT offset 越大,MySQL 需要跳过越多行
-- 解决1: 游标分页(利用主键连续性)
SELECT * FROM orders 
WHERE status = 1 AND id < 1000000
ORDER BY id DESC 
LIMIT 10;
-- 上一次查询最后一条 id=1000000,本次用 id < 1000000

-- 解决2: 延迟关联(利用覆盖索引回表)
SELECT o.* FROM orders o
INNER JOIN (
    SELECT id FROM orders 
    WHERE status = 1 
    ORDER BY id DESC 
    LIMIT 1000000, 10
) AS t ON o.id = t.id;

-- ===== 案例2: COUNT(*) 优化 =====

-- ❌ 全表 COUNT(慢)
SELECT COUNT(*) FROM orders WHERE status = 1;

-- 方案1: 添加条件索引(快速定位)
CREATE INDEX idx_status ON orders(status);
-- COUNT(*) 只扫描索引树,不扫描数据

-- 方案2: 缓存计数(Redis)
-- 写操作时同步更新 Redis 计数器

-- 方案3: 近似 COUNT(数据量要求不精确时)
SELECT TABLE_ROWS FROM information_schema.TABLES 
WHERE TABLE_SCHEMA = 'shop' AND TABLE_NAME = 'orders';

-- ===== 案例3: 字符串索引优化 =====

-- ❌ 前缀 LIKE 导致索引失效
SELECT * FROM users WHERE phone LIKE '%138%';  -- 前后都有%,索引失效

-- 方案1: 使用覆盖索引 + 全文索引
CREATE FULLTEXT INDEX ft_phone ON users(phone);
SELECT * FROM users WHERE MATCH(phone) AGAINST('138');

-- 方案2: 反向索引(适合后缀查询)
-- phone 存储为反向字符串 + 触发器维护

-- 方案3: Elasticsearch(适合亿级数据)

四、索引设计规范

4.1 索引创建原则

sql 复制代码
-- ===== 索引创建决策树 =====

/*
         创建索引
             │
     ┌─────── 字段类型 ───────┐
     │                        │
  主键/唯一    ├─ 等值查询   ├─ 排序/分组   ├─ 范围查询
     │          │   频繁      │   频繁        │   频繁
     ▼          ▼             ▼               ▼
  聚簇索引   B+树索引      B+树索引        B+树索引
              │             │ (配合DESC)       │
         区分度>20%    联合索引+排序   最左前缀+范围放最后
*/

-- ===== 高效索引设计案例 =====

-- 场景1: 用户表,按 status + type 查询,按 created_at 排序
-- 查询:WHERE status = ? AND type = ? ORDER BY created_at DESC

-- ❌ 错误设计
CREATE INDEX idx_bad ON users(status, created_at, type);
-- 问题:排序字段在中间,查询时无法同时利用索引排序

-- ✅ 正确设计(排序放最后,且符合最左前缀)
CREATE INDEX idx_good ON users(status, type, created_at DESC);

-- 场景2: 订单表,支持 customer_id + status + 时间范围查询
-- 查询:WHERE customer_id = ? AND status IN (1,2) AND created_at BETWEEN ? AND ?

-- ✅ 联合索引
CREATE INDEX idx_orders_lookup ON orders(customer_id, status, created_at);

-- 场景3: SELECT * 的索引失效场景
-- 查询:SELECT * FROM orders WHERE user_id = ? AND status = 1
-- 如果 user_id 有索引,但 status 无索引,且返回所有列
-- → 会回表,性能差

-- ✅ 覆盖索引优化
CREATE INDEX idx_cover ON orders(user_id, status);
-- 如果 select 只查 user_id, status, id 三列,全部在索引中,无需回表

-- ===== 索引下推 ICP 示例 =====

-- 查询:SELECT * FROM orders WHERE user_id = 1 AND status = 1 AND amount > 100

-- 无 ICP(MySQL 5.5):
-- 1. 在 (user_id) 索引树找到 user_id=1 的所有记录(假设1000条)
-- 2. 逐一回表,检查 status=1 AND amount>100
-- → 回表 1000 次

-- 有 ICP(MySQL 5.6+):
-- 1. 在 (user_id, status, amount) 索引树找到 user_id=1 的所有记录
-- 2. 索引层直接过滤 status=1 AND amount>100
-- → 只回表符合条件的那几十条
-- → 减少回表次数

-- ===== 不可见索引(灰度验证)=====

-- 创建不可见索引(不影响查询)
CREATE INDEX idx_test ON orders(customer_id) INVISIBLE;

-- 验证查询是否走索引
EXPLAIN SELECT * FROM orders WHERE customer_id = 1;
-- Extra: Using index condition (如果走了不可见索引)

-- 线上验证:确认性能提升后,使索引可见
ALTER INDEX idx_test VISIBLE;

-- 验证后确认有问题,使索引不可见(不影响服务)
ALTER INDEX idx_test INVISIBLE;

-- 最终删除(确认无误后)
DROP INDEX idx_test ON orders;

4.2 索引失效场景

sql 复制代码
-- ===== 索引失效清单 =====

-- 1. 函数/运算:索引列参与计算
SELECT * FROM orders WHERE YEAR(created_at) = 2024;  -- ❌
SELECT * FROM orders WHERE created_at >= '2024-01-01';  -- ✅

-- 2. 类型转换:字符串列用数字查询
EXPLAIN SELECT * FROM users WHERE phone = 13812345678;  -- ❌(隐式转换)
EXPLAIN SELECT * FROM users WHERE phone = '13812345678';  -- ✅

-- 3. 前缀通配符:LIKE '%xxx'
SELECT * FROM users WHERE name LIKE '%张三';  -- ❌
SELECT * FROM users WHERE name LIKE '张%';  -- ✅(后缀通配符可以用索引)

-- 4. OR 混用:非索引列 OR
SELECT * FROM users WHERE id = 1 OR email = 'x@x.com';  -- ❌(email无索引)
-- 改用 UNION
SELECT * FROM users WHERE id = 1
UNION
SELECT * FROM users WHERE email = 'x@x.com';

-- 5. NOT / != / <> / IS NOT NULL
SELECT * FROM users WHERE status != 1;  -- ❌(范围查询,索引部分有效)
SELECT * FROM users WHERE status IS NOT NULL;  -- ❌

-- 6. 联合索引跳过最左列
CREATE INDEX idx ON users(a, b, c);
SELECT * FROM users WHERE b = 1;  -- ❌(跳过 a)

-- 7. MySQL 优化器判断:数据量太小(全表扫描更快)
SELECT * FROM users WHERE status = 1;  -- ❌(status=1 占95%数据)
-- 解决:FORCE INDEX 或添加其他过滤条件

-- ===== optimizer_switch 调优 =====

-- 查看优化器开关
SHOW VARIABLES LIKE 'optimizer_switch';

-- 关闭 ICP(特定场景)
SET SESSION optimizer_switch = 'index_condition_pushdown=off';

-- 开启 MRR(随机 I/O 优化)
SET SESSION optimizer_switch = 'mrr_cost_based=on';

-- 使用 HASH JOIN(MySQL 8.0,默认 ON)
SET SESSION optimizer_switch = 'hash_join=on';

五、InnoDB 锁机制

5.1 锁类型与兼容矩阵

sql 复制代码
-- ===== InnoDB 锁类型 =====

-- 共享锁(S锁):允许事务读取行
SELECT * FROM orders WHERE id = 1 LOCK IN SHARE MODE;

-- 排他锁(X锁):允许事务读取或更新行
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
UPDATE orders SET amount = 100 WHERE id = 1;  -- 自动加 X 锁

-- ===== 锁兼容矩阵 =====

/*
         S锁    X锁
S锁      ✅     ❌
X锁      ❌     ❌
*/

-- ===== 记录锁(Record Lock)=====

-- 对索引记录加锁
BEGIN;
SELECT * FROM orders WHERE id = 5 LOCK IN SHARE MODE;  -- 锁定 id=5 的记录
-- 锁范围:id=5 这一行

-- ===== 间隙锁(Gap Lock)=====

-- 锁定索引记录之间的间隙(防止幻读)
BEGIN;
SELECT * FROM orders WHERE id BETWEEN 10 AND 20 LOCK IN SHARE MODE;
-- 锁定 (5, 10) 和 (20, 30) 之间的间隙,防止插入 id=15 的新记录
-- 目的:防止幻读(同一事务两次查询结果不一致)

-- ===== Next-Key Lock(记录锁 + 间隙锁)=====

-- 默认 RR 隔离级别使用 Next-Key Lock
BEGIN;
SELECT * FROM orders WHERE id >= 10 AND id <= 20 LOCK IN SHARE MODE;
-- 锁定:id=10, id=20, 以及 (10,20) 之间的间隙

-- ===== 意向锁(Intention Lock)=====

-- 表级锁,表示事务有意向对某行加锁
-- 意向共享锁(IS):事务打算给行加 S 锁
-- 意向排他锁(IX):事务打算给行加 X 锁

-- 锁兼容矩阵:
/*
          IS    IX    S     X
IS        ✅    ✅    ✅    ❌
IX        ✅    ✅    ❌    ❌
S         ✅    ❌    ✅    ❌
X         ❌    ❌    ❌    ❌
*/

-- ===== 死锁检测 =====

SHOW VARIABLES LIKE 'innodb_deadlock_detect';
-- 默认 ON:启用死锁检测(主动检测并回滚一个事务)

SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';
-- 默认 50 秒:锁等待超时(被动超时回滚)

-- ===== 模拟死锁 =====

-- T1:
BEGIN;
SELECT * FROM orders WHERE id = 1 FOR UPDATE;  -- 锁 id=1
UPDATE orders SET amount = 200 WHERE id = 2;  -- 等待 id=2

-- T2:
BEGIN;
SELECT * FROM orders WHERE id = 2 FOR UPDATE;  -- 锁 id=2
UPDATE orders SET amount = 300 WHERE id = 1;  -- 等待 id=1,触发死锁!

-- MySQL 自动选择一个事务回滚
-- ERROR 1213 (40001): Deadlock found when trying to get lock

-- ===== 锁等待排查 =====

-- 查看当前锁等待
SELECT 
    r.trx_id AS waiting_trx_id,
    r.trx_mysql_thread_id AS waiting_thread,
    r.trx_query AS waiting_query,
    b.trx_id AS blocking_trx_id,
    b.trx_mysql_thread_id AS blocking_thread,
    b.trx_query AS blocking_query
FROM information_schema.INNODB_LOCK_WAITS w
JOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_id
JOIN information_schema.INNODB_TRX r ON r.trx_id = w.requesting_trx_id;

-- 查看当前所有锁
SELECT * FROM information_schema.INNODB_LOCKS;

-- 查看当前事务
SELECT * FROM information_schema.INNODB_TRX\G;

5.2 事务隔离级别

sql 复制代码
-- ===== 隔离级别对比 =====

-- 查看当前隔离级别
SHOW VARIABLES LIKE 'transaction_isolation';
SELECT @@transaction_isolation;

-- 设置隔离级别
SET SESSION transaction_isolation = 'REPEATABLE-READ';  -- MySQL 默认

-- ===== 四种隔离级别 =====

-- 1. READ UNCOMMITTED(读未提交)
-- 脏读:可能读到其他事务未提交的数据

-- 2. READ COMMITTED(读已提交)
-- 不可重复读:同一事务两次查询结果可能不同(其他事务已提交)

-- 3. REPEATABLE READ(可重复读)------ MySQL 默认
-- 幻读:同一事务两次查询结果可能不同(其他事务插入了新行)
-- InnoDB: 使用 MVCC + Gap Lock 解决幻读

-- 4. SERIALIZABLE(串行化)
-- 完全串行执行,性能最差

-- ===== MVCC 原理 =====

-- 每一行数据有两个隐藏列:
-- DB_TRX_ID:最近修改的事务ID
-- DB_ROLL_PTR:指向 undo log 的指针

-- Read View(快照)结构:
-- m_ids:活跃事务ID列表
-- min_trx_id:最小活跃事务ID
-- max_trx_id:创建 Read View 时最大事务ID
-- creator_trx_id:当前事务ID

-- 可见性判断规则:
-- 1. DB_TRX_ID = creator_trx_id:自己的修改,可见
-- 2. DB_TRX_ID < min_trx_id:已提交,可见
-- 3. DB_TRX_ID >= max_trx_id:自己的修改之后发生的,不可见
-- 4. DB_TRX_ID in m_ids:在活跃列表中,不可见

-- RC vs RR 区别:
-- RC:每次 SELECT 创建新 Read View
-- RR:第一次 SELECT 创建 Read View,后续复用

-- ===== RC 隔离级别下的快照读 vs 当前读 =====

-- 快照读:读取快照,不加锁
SELECT * FROM orders;  -- 快照读

-- 当前读:读取最新数据,加锁
SELECT * FROM orders LOCK IN SHARE MODE;  -- 加 S 锁
SELECT * FROM orders FOR UPDATE;  -- 加 X 锁
INSERT / UPDATE / DELETE  -- 当前读

六、SQL 优化技巧

6.1 JOIN 优化

sql 复制代码
-- ===== 小表驱动大表 =====

-- MySQL JOIN 算法:
-- NL(Nested Loop Join):小表驱动大表,循环次数 = 小表行数
-- BNL(Block Nested Loop):无索引时,大表放入 join buffer
-- Hash Join:MySQL 8.0+,大表等值关联优化

-- ✅ 小表在前
SELECT * FROM orders o
INNER JOIN users u ON o.user_id = u.id
WHERE u.status = 1;  -- users 有 status=1 索引

-- 分析:users 先过滤,剩下 1000 行,orders 用主键关联快

-- ===== 避免 SELECT * =====

-- ❌ SELECT *,回表次数多
SELECT * FROM orders o
INNER JOIN users u ON o.user_id = u.id;

-- ✅ 只查需要的列
SELECT o.id, o.amount, o.created_at, u.username
FROM orders o
INNER JOIN users u ON o.user_id = u.id;

-- ===== 关联条件加索引 =====

-- orders.user_id 加索引(被关联字段)
CREATE INDEX idx_orders_user ON orders(user_id);
-- users.id 已有主键索引,无需额外建

-- ===== 多表 JOIN 顺序 =====

-- 原则:先过滤,后关联
-- ❌ 低效
SELECT * FROM orders o
INNER JOIN users u ON o.user_id = u.id
INNER JOIN products p ON o.product_id = p.id
WHERE u.status = 1;  -- users 过滤条件在 WHERE

-- ✅ 优化:子查询先过滤
SELECT * FROM (
    SELECT id FROM users WHERE status = 1
) u
INNER JOIN orders o ON o.user_id = u.id
INNER JOIN products p ON o.product_id = p.id;

-- ===== UNION vs UNION ALL =====

-- UNION:去重,会对合并结果排序
SELECT username FROM users WHERE status = 1
UNION
SELECT username FROM admins WHERE status = 1;

-- UNION ALL:不去重,不排序,性能更好
SELECT username FROM users WHERE status = 1
UNION ALL
SELECT username FROM admins WHERE status = 1;

-- ===== 批量插入优化 =====

-- ❌ 逐条插入(1000次 I/O)
INSERT INTO orders (user_id, amount) VALUES (1, 100);
INSERT INTO orders (user_id, amount) VALUES (2, 200);
...

-- ✅ 批量插入(1次 I/O)
INSERT INTO orders (user_id, amount) VALUES 
    (1, 100), (2, 200), (3, 300), (4, 400), (5, 500);

-- ✅ LOAD DATA(最快,文件导入)
LOAD DATA INFILE '/tmp/orders.csv'
INTO TABLE orders
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n'
(user_id, amount, created_at);

-- ===== 千万级数据分页 =====

-- 分页查询优化:延迟关联
SELECT o.*, u.username FROM orders o
INNER JOIN (
    SELECT id, username FROM users WHERE status = 1
) u ON o.user_id = u.id
ORDER BY o.created_at DESC
LIMIT 1000000, 10;

6.2 分区表与分库分表

sql 复制代码
-- ===== 分区表(MySQL 原生)=====

-- 按时间分区(适合日志/订单)
CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    user_id BIGINT,
    amount DECIMAL(10,2),
    created_at DATETIME
) PARTITION BY RANGE (YEAR(created_at)) (
    PARTITION p2022 VALUES LESS THAN (2023),
    PARTITION p2023 VALUES LESS THAN (2024),
    PARTITION p2024 VALUES LESS THAN (2025),
    PARTITION pmax VALUES LESS THAN MAXVALUE
);

-- 按月分区
CREATE TABLE logs (
    id BIGINT PRIMARY KEY,
    level VARCHAR(20),
    message TEXT,
    created_at DATETIME
) PARTITION BY RANGE (TO_DAYS(created_at)) (
    PARTITION p2024_01 VALUES LESS THAN (TO_DAYS('2024-02-01')),
    PARTITION p2024_02 VALUES LESS THAN (TO_DAYS('2024-03-01')),
    PARTITION pmax VALUES LESS THAN MAXVALUE
);

-- ===== 分区裁剪(Partition Pruning)=====

-- 查询自动跳过不相关的分区
EXPLAIN SELECT * FROM orders WHERE created_at BETWEEN '2024-01-01' AND '2024-03-31';
-- Extra: Using partition (p2024) only(只扫描 p2024 分区)

-- ===== 分库分表(ShardingSphere)=====

-- ShardingSphere-JDBC 配置示例
-- application.yml
-- sharding:
--   tables:
--     orders:
--       actual-data-nodes: ds_${0..1}.orders_${0..15}
--       table-strategy:
--         standard:
--           sharding-column: user_id
--           sharding-algorithm-name: orders_mod
--       key-generate-strategy:
--         column: id
--         key-generator-name: snowflake
--   binding-tables:
--     - orders

七、配置优化与运维

7.1 核心参数调优

ini 复制代码
# ===== my.cnf / my.ini 核心参数 =====

[mysqld]

# === 连接配置 ===
max_connections = 2000          # 最大连接数(默认151,根据内存估算:max_connections × 4MB)
wait_timeout = 600              # 空闲连接超时(秒)
interactive_timeout = 600       # 交互式连接超时
thread_cache_size = 64          # 线程缓存大小(一般设置为 max_connections 的 10%)

# === 缓冲池配置(InnoDB)===
innodb_buffer_pool_size = 12G   # 缓冲池大小(建议为物理内存的 60-80%)
innodb_buffer_pool_instances = 4  # 缓冲池分区数(减少锁竞争,建议等于 CPU 核数)
innodb_buffer_pool_load_at_startup = ON  # 启动时加载热点数据
innodb_buffer_pool_dump_at_shutdown = ON  # 关闭时保存热点数据

# === 日志配置 ===
innodb_log_file_size = 2G       # 日志文件大小(太大恢复慢,太小频繁切换)
innodb_log_buffer_size = 64M    # 日志缓冲区
innodb_flush_log_at_trx_commit = 1  # 事务提交刷盘策略(1=安全,2=性能,0=最快)
# 1(默认):每次提交刷盘,最安全
# 2:提交到 OS 缓存,OS 负责刷盘
# 0:每秒刷盘,可能丢失 1 秒数据

# === I/O 配置 ===
innodb_file_per_table = ON     # 每个表独立表空间(5.6+ 默认 ON)
innodb_flush_method = O_DIRECT # 绕过 OS 文件缓存,直接刷盘
innodb_io_capacity = 2000      # I/O 能力(SSD 设置 2000+,HDD 设置 200)
innodb_io_capacity_max = 4000 # 最大 I/O 能力
innodb_read_io_threads = 8     # 读 I/O 线程数
innodb_write_io_threads = 8    # 写 I/O 线程数

# === 临时表与排序 ===
tmp_table_size = 256M          # 内存临时表大小
max_heap_table_size = 256M     # MEMORY 表最大大小
sort_buffer_size = 4M          # 排序缓冲区
join_buffer_size = 4M          # JOIN 缓冲区
read_buffer_size = 2M          # 顺序读缓冲区

# === 慢查询 ===
slow_query_log = 1
slow_query_log_file = /var/lib/mysql/mysql-slow.log
long_query_time = 1
log_queries_not_using_indexes = 1

7.2 监控与巡检

sql 复制代码
-- ===== 日常巡检 SQL =====

-- 1. 连接数使用情况
SHOW STATUS LIKE 'Threads_connected';       -- 当前连接数
SHOW STATUS LIKE 'Max_used_connections';    -- 历史最大连接数
SHOW STATUS LIKE 'Aborted_connects';        -- 失败连接数

-- 2. 查询缓存命中率(MySQL 8.0 已移除)
-- SHOW STATUS LIKE 'Qcache%';  -- 8.0 已无此功能

-- 3. 缓冲池状态
SHOW ENGINE INNODB STATUS\G
-- 关键信息:
-- Buffer pool size: 786432 pages  → 缓冲池总页数
-- Free pages: 1024               → 空闲页数(应接近 0,说明充分利用)
-- Database pages: 775308         → 已用页数
-- Modified db pages: 0           → 脏页数(太多会拖慢 checkpoint)

-- 4. 事务与锁
SHOW ENGINE INNODB STATUS\G
-- 查看当前锁等待、死锁信息
-- Trx id: 12345  → 事务ID
-- Lock wait: 3   → 等待锁数量

-- 5. 主从延迟
SHOW SLAVE STATUS\G
-- Seconds_Behind_Master: 0  → 无延迟
-- Seconds_Behind_Master: 30 → 延迟 30 秒,需排查

-- 6. 慢查询 Top 10
SELECT 
    DIGEST_TEXT,
    COUNT_STAR,
    SUM_TIMER_WAIT / 1000000000000 AS total_sec,
    AVG_TIMER_WAIT / 1000000000000 AS avg_sec,
    SUM_ROWS_EXAMINED,
    SUM_ROWS_SENT
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;

-- ===== pt-stalk 自动化采集 =====

-- Percona Toolkit 的 pt-stalk 可在故障时自动采集
-- yum install percona-toolkit
-- pt-stalk --daemonize -- --user=root --password=xxx --iterations=10 --interval=1 --sleep=60

-- 触发条件:Query_time > 10s
-- 采集内容:processlist, vars, status, vars, logs, vars

八、总结

技术全景

核心概念 关键点
B+树结构 聚簇/辅助索引 16KB Page,3 层撑千万数据
最左前缀 联合索引顺序 区分度高的列放后面
慢查询 pt-query-digest 耗时 Top SQL 定位
EXPLAIN type/key/Extra const/eq_ref/ref > range > ALL
索引设计 覆盖索引 + ICP 减少回表次数
锁机制 Gap Lock + Next-Key RR 级别防幻读
MVCC Read View RC/RR 隔离级别区别
SQL 优化 小表驱动大表 UNION ALL vs UNION
分区表 Partition Pruning 减少扫描范围

最佳实践

实践 说明
覆盖索引 select 字段全在索引中,避免回表
最左前缀 联合索引严格按照顺序使用
ICP 索引条件下推,减少回表
小表驱动 小表放 JOIN 左边,减少 NL 循环
深分页 游标分页或延迟关联
COUNT 用条件索引或 Redis 缓存
隔离级别 读多写少用 RC,财务系统用 RR
缓冲池 设置为物理内存 60-80%,多实例分区

本文涵盖 MySQL 8.0 性能优化完整知识:B+树索引原理、慢查询诊断、EXPLAIN 执行计划解读、索引设计规范、InnoDB 锁机制、事务隔离级别与 MVCC、SQL 优化技巧、分区表、生产配置参数与运维巡检。

相关推荐
feifeigo1231 小时前
C# ADB 安卓设备数据传输工具
android·adb·c#
AC赳赳老秦1 小时前
OpenClaw + 华为云自动化:批量管理云资源、生成月度云账单分析与成本优化报告
java·开发语言·javascript·人工智能·python·mysql·openclaw
飞猿_SIR1 小时前
RK3288 Android11平台移植RTL8733BU-WiFi模组
android·嵌入式硬件
通信侠1 小时前
android相机热启动缓存帧解决方案(任务快照)
android·缓存·blur·tasksnapshot·mtkcam
BreezeDove1 小时前
【Android】Flutter命令超时无响应问题
android·flutter
我是一颗柠檬1 小时前
【Java项目技术亮点】读写分离+主从延迟处理:MySQL高并发下的性能优化方案
java·分布式·mysql·性能优化
霸道流氓气质2 小时前
MySQL 大数据量场景下的表结构与索引设计指南
数据库·mysql
Kapaseker2 小时前
Android 线程发展shi
android·kotlin
lsyeei2 小时前
MySQL常用索引
数据库·mysql