Featured image of post MySQL深度调优实战:从SQL到架构的全面优化
数据库

MySQL深度调优实战:从SQL到架构的全面优化

MySQL深度调优实战:从SQL到架构的全面优化

前言

MySQL 是最流行的关系型数据库之一,但在生产环境中,性能问题往往是开发和运维面临的最大挑战。本文将从 SQL 优化、索引设计、配置调优、架构优化四个维度,全面介绍 MySQL 性能调优的实战技巧。

一、SQL 优化

1.1 EXPLAIN 执行计划分析

EXPLAIN SELECT * FROM users WHERE age > 25 AND city = 'Beijing';

关键字段说明:

字段说明优化目标
type访问类型至少达到 range 级别
key使用的索引避免 NULL
rows扫描行数越小越好
Extra额外信息避免 Using filesort/temporary

type 访问类型(从好到差):

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

1.2 索引优化技巧

最左前缀原则

-- 联合索引 (a, b, c)
-- ✅ 命中索引
SELECT * FROM table WHERE a = 1;
SELECT * FROM table WHERE a = 1 AND b = 2;
SELECT * FROM table WHERE a = 1 AND b = 2 AND c = 3;

-- ❌ 无法使用索引
SELECT * FROM table WHERE b = 2;
SELECT * FROM table WHERE c = 3;
SELECT * FROM table WHERE b = 2 AND c = 3;

覆盖索引

-- 创建覆盖索引
ALTER TABLE users ADD INDEX idx_name_age (name, age);

-- 查询只需要索引字段,无需回表
SELECT name, age FROM users WHERE name = 'John';

索引失效场景

-- ❌ 函数操作
SELECT * FROM users WHERE YEAR(create_time) = 2024;
-- ✅ 改为范围查询
SELECT * FROM users WHERE create_time >= '2024-01-01' 
    AND create_time < '2025-01-01';

-- ❌ 隐式类型转换
SELECT * FROM users WHERE phone = 13800138000;
-- ✅ 类型匹配
SELECT * FROM users WHERE phone = '13800138000';

-- ❌ LIKE 左模糊
SELECT * FROM users WHERE name LIKE '%John';
-- ✅ 前缀匹配
SELECT * FROM users WHERE name LIKE 'John%';

-- ❌ OR 条件
SELECT * FROM users WHERE age = 25 OR name = 'John';
-- ✅ 使用 UNION
SELECT * FROM users WHERE age = 25
UNION
SELECT * FROM users WHERE name = 'John';

1.3 查询优化

分页查询优化

-- ❌ 慢查询(深分页)
SELECT * FROM orders ORDER BY id LIMIT 1000000, 10;

-- ✅ 延迟关联
SELECT o.* FROM orders o
INNER JOIN (
    SELECT id FROM orders ORDER BY id LIMIT 1000000, 10
) t ON o.id = t.id;

-- ✅ 游标分页
SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 10;

JOIN 优化

-- 确保关联字段有索引
ALTER TABLE orders ADD INDEX idx_user_id (user_id);

-- 小表驱动大表
SELECT o.* FROM orders o
INNER JOIN users u ON o.user_id = u.id
WHERE u.status = 1;

子查询优化

-- ❌ 子查询
SELECT * FROM orders WHERE user_id IN (
    SELECT id FROM users WHERE status = 1
);

-- ✅ JOIN 优化
SELECT o.* FROM orders o
INNER JOIN users u ON o.user_id = u.id
WHERE u.status = 1;

二、索引设计原则

2.1 索引设计规范

-- 1. 选择区分度高的列
SELECT 
    COUNT(DISTINCT city) / COUNT(*) AS selectivity
FROM users;
-- 区分度 > 0.1 才考虑建索引

-- 2. 联合索引字段顺序
-- 区分度高的字段在前
-- 查询频率高的字段在前
-- 排序字段在最后

-- 3. 避免过多索引
-- 每张表索引不超过 5 个
-- 单个索引字段不超过 5 个

2.2 索引监控

-- 查看索引使用情况
SELECT 
    object_schema,
    object_name,
    index_name,
    count_read,
    count_fetch
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE object_schema = 'your_database'
ORDER BY count_read DESC;

-- 查看未使用的索引
SELECT 
    object_schema,
    object_name,
    index_name
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE object_schema = 'your_database'
    AND index_name IS NOT NULL
    AND count_star = 0;

三、配置调优

3.1 InnoDB Buffer Pool

# my.cnf
[mysqld]
# Buffer Pool 大小,建议物理内存的 70-80%
innodb_buffer_pool_size = 8G

# Buffer Pool 实例数,减少锁竞争
innodb_buffer_pool_instances = 8

# Buffer Pool 预热
innodb_buffer_pool_dump_at_shutdown = ON
innodb_buffer_pool_load_at_startup = ON

3.2 日志配置

[mysqld]
# Redo Log 大小
innodb_log_file_size = 1G
innodb_log_buffer_size = 64M

# 刷盘策略
# 1:每次提交都刷盘(最安全)
# 2:每次提交写入OS缓存
# 0:每秒刷盘
innodb_flush_log_at_trx_commit = 1

# 双1配置(生产推荐)
sync_binlog = 1

3.3 连接配置

[mysqld]
# 最大连接数
max_connections = 500

# 连接超时
wait_timeout = 600
interactive_timeout = 600

# 线程缓存
thread_cache_size = 64

# 表缓存
table_open_cache = 4096
table_definition_cache = 2048

3.4 查询缓存(MySQL 8.0 已移除)

# MySQL 5.7 及以下
[mysqld]
query_cache_type = 0  # 建议关闭
query_cache_size = 0

3.5 排序和临时表

[mysqld]
# 排序缓冲区
sort_buffer_size = 4M

# JOIN 缓冲区
join_buffer_size = 4M

# 临时表大小
tmp_table_size = 64M
max_heap_table_size = 64M

# 读缓冲区
read_buffer_size = 2M
read_rnd_buffer_size = 8M

四、架构优化

4.1 读写分离

                    ┌─────────────┐
                    │   应用程序   │
                    └──────┬──────┘
                    ┌──────▼──────┐
                    │   代理层    │
                    │ (ProxySQL)  │
                    └──────┬──────┘
            ┌──────────────┼──────────────┐
            │              │              │
     ┌──────▼──────┐ ┌────▼────┐ ┌───────▼──────┐
     │   Master    │ │ Slave 1 │ │   Slave 2    │
     │   (写)     │ │  (读)   │ │   (读)       │
     └─────────────┘ └─────────┘ └──────────────┘

ProxySQL 配置示例:

-- 添加后端MySQL服务器
INSERT INTO mysql_servers (hostgroup_id, hostname, port) VALUES
(10, '10.0.0.1', 3306),  -- 写组
(20, '10.0.0.2', 3306),  -- 读组
(20, '10.0.0.3', 3306);  -- 读组

-- 配置读写分离规则
INSERT INTO mysql_query_rules (rule_id, match_pattern, destination_hostgroup) VALUES
(1, '^SELECT.*FOR UPDATE', 10),  -- 写操作
(2, '^SELECT', 20);               -- 读操作

4.2 分库分表

垂直分库

用户库 (user_db)
├── users
├── user_profiles
└── user_settings

订单库 (order_db)
├── orders
├── order_items
└── payments

商品库 (product_db)
├── products
├── categories
└── inventory

水平分表

-- 按用户ID分表
orders_0  -- user_id % 4 = 0
orders_1  -- user_id % 4 = 1
orders_2  -- user_id % 4 = 2
orders_3  -- user_id % 4 = 3

-- ShardingSphere 配置
spring.shardingsphere.datasource.names=ds0,ds1
spring.shardingsphere.sharding.tables.orders.actual-data-nodes=ds$->{0..1}.orders_$->{0..3}
spring.shardingsphere.sharding.tables.orders.table-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.tables.orders.table-strategy.inline.algorithm-expression=orders_$->{user_id % 4}

4.3 缓存策略

┌──────────┐    ┌──────────┐    ┌──────────┐
│  应用程序 │───▶│  Redis   │───▶│  MySQL   │
└──────────┘    └──────────┘    └──────────┘
     │               │               │
     │    1.查询缓存  │               │
     │──────────────▶│               │
     │    2.返回缓存  │               │
     │◀──────────────│               │
     │               │               │
     │    3.缓存未命中│               │
     │──────────────────────────────▶│
     │    4.查询数据库│               │
     │◀──────────────────────────────│
     │               │               │
     │    5.写入缓存  │               │
     │──────────────▶│               │

缓存更新策略:

// Cache Aside Pattern
public User getUser(Long userId) {
    // 1. 先查缓存
    String key = "user:" + userId;
    User user = redis.get(key);
    if (user != null) {
        return user;
    }
    
    // 2. 缓存未命中,查数据库
    user = userMapper.selectById(userId);
    if (user != null) {
        // 3. 写入缓存
        redis.setex(key, 3600, user);
    }
    return user;
}

4.4 表设计优化

-- 1. 选择合适的数据类型
-- ❌ 不推荐
CREATE TABLE users (
    id BIGINT,
    name VARCHAR(1000),
    age INT,
    status VARCHAR(20)
);

-- ✅ 推荐
CREATE TABLE users (
    id BIGINT UNSIGNED AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL,
    age TINYINT UNSIGNED,
    status TINYINT NOT NULL DEFAULT 1,
    PRIMARY KEY (id),
    INDEX idx_name (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 2. 避免NULL
-- 使用 NOT NULL + DEFAULT

-- 3. 主键设计
-- 使用自增ID作为主键
-- 避免使用UUID作为主键(无序,影响插入性能)

五、监控与诊断

5.1 慢查询日志

[mysqld]
# 开启慢查询日志
slow_query_log = ON
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1  # 超过1秒记录
log_queries_not_using_indexes = ON
# 分析慢查询日志
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log

# 使用 pt-query-digest
pt-query-digest /var/log/mysql/slow.log > slow_report.txt

5.2 性能监控SQL

-- 查看当前连接
SHOW PROCESSLIST;

-- 查看InnoDB状态
SHOW ENGINE INNODB STATUS;

-- 查看表大小
SELECT 
    table_schema,
    table_name,
    ROUND(data_length / 1024 / 1024, 2) AS data_mb,
    ROUND(index_length / 1024 / 1024, 2) AS index_mb
FROM information_schema.tables
WHERE table_schema = 'your_database'
ORDER BY data_length DESC;

-- 查看索引使用统计
SELECT 
    object_schema,
    object_name,
    index_name,
    count_star,
    count_read,
    count_fetch
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE object_schema = 'your_database';

5.3 诊断工具

# Percona Toolkit
# 安装
apt install percona-toolkit

# 分析慢查询
pt-query-digest /var/log/mysql/slow.log

# 检查重复/冗余索引
pt-duplicate-key-checker --host=localhost --user=root --password=xxx

# 查看索引使用情况
pt-index-usage --host=localhost --user=root --password=xxx /var/log/mysql/slow.log

六、调优清单

6.1 SQL 层面

  • 所有查询都使用了合适的索引
  • 避免了 SELECT *,只查询需要的字段
  • 分页查询使用了延迟关联
  • 避免了深分页(LIMIT offset 过大)
  • JOIN 查询关联字段有索引
  • 避免了索引失效的写法

6.2 索引层面

  • 高频查询字段都有索引
  • 联合索引遵循最左前缀原则
  • 区分度低的字段不建索引
  • 定期清理未使用的索引
  • 使用覆盖索引减少回表

6.3 配置层面

  • Buffer Pool 设置为物理内存的 70-80%
  • 开启慢查询日志
  • 设置合理的连接数
  • 配置合适的日志刷盘策略

6.4 架构层面

  • 读写分离(读多写少场景)
  • 分库分表(数据量大场景)
  • 引入缓存层(热点数据)
  • 表设计规范化

总结

MySQL 调优是一个系统工程,需要从多个维度综合考虑:

  1. SQL 优化是基础:写好 SQL 是最直接的优化手段
  2. 索引设计是核心:合理的索引能提升查询性能数倍
  3. 配置调优是保障:正确的配置能最大化硬件利用率
  4. 架构优化是根本:当单机瓶颈时,架构扩展是必由之路

建议按照本文的调优清单逐步检查,结合监控数据持续优化。

使用 Hugo 构建
主题 StackJimmy 设计