MySQL性能优化

掌握查询优化、索引优化和服务器配置技巧

MySQL性能优化概述

MySQL性能优化是一个系统性的工作,涉及查询优化、索引优化、服务器配置优化、架构设计优化等多个方面。

性能优化的目标

  • 提高查询响应速度
  • 减少服务器资源消耗
  • 提高系统并发处理能力
  • 确保系统稳定性和可扩展性

查询优化

使用EXPLAIN分析查询

SQL示例
EXPLAIN SELECT * FROM orders 
WHERE customer_id = 123 AND order_date > '2024-01-01';

EXPLAIN输出格式(FORMAT选项)

SQL示例
-- 传统表格格式
EXPLAIN SELECT ...;

-- JSON格式,提供更详细信息
EXPLAIN FORMAT=JSON SELECT ...;

-- TREE格式,展示执行计划树(MySQL 8.0.16+)
EXPLAIN FORMAT=TREE SELECT ...;

-- 查看实际执行统计(MySQL 8.0.18+)
EXPLAIN ANALYZE SELECT ...;

EXPLAIN输出关键字段

字段 描述 优化建议
type连接类型避免ALL(全表扫描),追求const、eq_ref、ref
key实际使用的索引确保查询使用了合适的索引
rows预估扫描行数行数越少越好
Extra额外信息避免Using filesort、Using temporary
filtered过滤比例值越高越好,表示WHERE条件过滤掉更多行

使用Optimizer Trace分析优化器决策

SQL示例
SET optimizer_trace = "enabled=on";
SELECT * FROM employees WHERE last_name = 'Smith';
SELECT * FROM information_schema.optimizer_trace;
SET optimizer_trace = "enabled=off";

避免SELECT *

SQL示例
-- 不推荐的写法
SELECT * FROM customers;

-- 推荐的写法
SELECT customer_id, first_name, last_name, email 
FROM customers;

使用LIMIT限制结果集

SQL示例
-- 限制返回的记录数
SELECT * FROM products 
ORDER BY created_at DESC
LIMIT 10;

避免在WHERE子句中使用函数

SQL示例
-- 不推荐的写法(无法使用索引)
SELECT * FROM orders 
WHERE YEAR(order_date) = 2024;

-- 推荐的写法
SELECT * FROM orders 
WHERE order_date >= '2024-01-01' AND order_date < '2025-01-01';

查询重写技巧

SQL示例
-- 将OR改写为UNION(可能更高效)
-- 原查询
SELECT * FROM employees WHERE department = 'IT' OR department = 'HR';

-- 改写为UNION
SELECT * FROM employees WHERE department = 'IT'
UNION ALL
SELECT * FROM employees WHERE department = 'HR';

-- 使用COUNT替代EXISTS(在特定场景下)
-- 原查询
SELECT IF(EXISTS(SELECT 1 FROM orders WHERE customer_id=100), 1, 0);

-- 改写
SELECT COUNT(1) FROM orders WHERE customer_id=100 LIMIT 1;

索引优化

选择合适的索引列

  • WHERE子句中的列
  • JOIN连接条件的列
  • ORDER BY和GROUP BY中的列
  • 选择性高的列(唯一值多的列)

索引下推(Index Condition Pushdown)

SQL示例
-- 启用索引下推(默认启用)
SET optimizer_switch = 'index_condition_pushdown=on';

-- 查看是否使用索引下推(Extra列显示Using index condition)
EXPLAIN SELECT * FROM employees WHERE last_name = 'Smith' AND first_name LIKE 'J%';

多范围读取(Multi-Range Read)

SQL示例
-- 启用MRR
SET optimizer_switch = 'mrr=on,mrr_cost_based=off';

-- MRR可以优化二级索引回表操作,减少随机IO

函数索引(MySQL 8.0.13+)

SQL示例
-- 创建函数索引
CREATE INDEX idx_year_order_date ON orders ((YEAR(order_date)));

-- 查询可以使用该索引
SELECT * FROM orders WHERE YEAR(order_date) = 2024;

-- 函数索引也支持JSON字段
CREATE INDEX idx_json_data ON products ((data->'$.price'));

生成列索引

SQL示例
-- 创建生成列
ALTER TABLE employees ADD COLUMN full_name VARCHAR(200) 
GENERATED ALWAYS AS (CONCAT(first_name, ' ', last_name)) STORED;

-- 在生成列上创建索引
CREATE INDEX idx_full_name ON employees(full_name);

复合索引的最左前缀原则

SQL示例
-- 创建复合索引
CREATE INDEX idx_last_first ON customers(last_name, first_name);

-- 可以使用索引的查询
SELECT * FROM customers WHERE last_name = 'Smith';
SELECT * FROM customers WHERE last_name = 'Smith' AND first_name = 'John';

-- 不能使用索引的查询
SELECT * FROM customers WHERE first_name = 'John';

覆盖索引

SQL示例
-- 创建覆盖索引
CREATE INDEX idx_customer_info ON customers(last_name, first_name, email);

-- 查询可以直接从索引中获取数据,无需访问表
SELECT last_name, first_name, email FROM customers 
WHERE last_name = 'Smith';

索引合并(Index Merge)

SQL示例
-- MySQL可能使用索引合并来组合多个索引
CREATE INDEX idx_age ON employees(age);
CREATE INDEX idx_salary ON employees(salary);

-- 查询可能同时使用两个索引(Extra显示Using union)
EXPLAIN SELECT * FROM employees WHERE age = 30 OR salary > 50000;

表结构优化

选择合适的数据类型

  • 使用最小的数据类型
  • 避免使用TEXT/BLOB类型,除非必要
  • 使用INT而不是VARCHAR存储数字
  • 使用ENUM代替VARCHAR存储固定值

规范化与反规范化的平衡

规范化:减少数据冗余,提高数据一致性,但可能增加查询复杂度。
反规范化:增加数据冗余,减少表连接,提高查询性能。

分区表

SQL示例
-- 按范围分区
CREATE TABLE sales (
    sale_id INT,
    sale_date DATE,
    amount DECIMAL(10,2)
)
PARTITION BY RANGE (YEAR(sale_date)) (
    PARTITION p2020 VALUES LESS THAN (2021),
    PARTITION p2021 VALUES LESS THAN (2022),
    PARTITION p2022 VALUES LESS THAN (2023),
    PARTITION p2023 VALUES LESS THAN (2024)
);

-- 分区修剪:查询只访问相关分区
EXPLAIN SELECT * FROM sales WHERE sale_date >= '2023-01-01';

服务器配置优化

关键配置参数

参数 描述 建议值
innodb_buffer_pool_sizeInnoDB缓冲池大小物理内存的50-80%
innodb_log_file_size重做日志文件大小1-4GB
max_connections最大连接数根据应用需求调整
innodb_flush_log_at_trx_commit日志刷新策略1(最安全)或2(高性能)
innodb_io_capacityIO能力设置根据磁盘性能设置(如SSD设为2000)

InnoDB缓冲池调优

SQL示例
-- 查看缓冲池状态
SHOW ENGINE INNODB STATUS\G
SELECT * FROM information_schema.INNODB_BUFFER_POOL_STATS;

-- 查看缓冲池命中率
SHOW STATUS LIKE 'Innodb_buffer_pool_read%';
-- 命中率 = Innodb_buffer_pool_read_requests / (Innodb_buffer_pool_read_requests + Innodb_buffer_pool_reads)

-- 动态调整缓冲池大小(MySQL 5.7+)
SET GLOBAL innodb_buffer_pool_size = 2 * 1024 * 1024 * 1024;

redo log和undo log优化

配置示例
[mysqld]
# redo log配置
innodb_log_file_size = 2G
innodb_log_files_in_group = 3
innodb_log_buffer_size = 64M

# undo log配置(MySQL 8.0)
innodb_undo_tablespaces = 2
innodb_undo_log_truncate = ON
innodb_max_undo_log_size = 2G

监控服务器状态

SQL示例
-- 查看服务器状态
SHOW STATUS LIKE 'Threads_connected';
SHOW STATUS LIKE 'Innodb_buffer_pool_reads';

-- 查看进程列表
SHOW PROCESSLIST;

-- 查看慢查询
SHOW VARIABLES LIKE 'slow_query_log';
SHOW VARIABLES LIKE 'long_query_time';

使用Performance Schema进行性能分析

SQL示例
-- 启用Performance Schema(需要配置)
[mysqld]
performance_schema = ON

-- 查询最耗时的语句
SELECT DIGEST_TEXT, COUNT_STAR, SUM_TIMER_WAIT/1e12 AS total_sec
FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC LIMIT 10;

SQL提示(Optimizer Hints)

SQL示例
-- 强制使用特定索引
SELECT * FROM employees USE INDEX (idx_last_name) WHERE last_name = 'Smith';

-- 忽略索引
SELECT * FROM employees IGNORE INDEX (idx_last_name) WHERE last_name = 'Smith';

-- 强制使用索引
SELECT * FROM employees FORCE INDEX (idx_last_name) WHERE last_name = 'Smith';

-- 设置JOIN顺序(MySQL 8.0+)
SELECT /*+ JOIN_ORDER(customers, orders) */ * 
FROM customers JOIN orders ON customers.id = orders.customer_id;

批量操作优化

SQL示例
-- 使用批量INSERT提高插入性能
INSERT INTO employees (name, department) VALUES
    ('Alice', 'IT'), ('Bob', 'HR'), ('Charlie', 'Finance');

-- 使用LOAD DATA INFILE批量导入
LOAD DATA INFILE '/tmp/data.csv' INTO TABLE employees
FIELDS TERMINATED BY ',' ENCLOSED BY '"'
LINES TERMINATED BY '\n';

-- 批量UPDATE时使用CASE WHEN
UPDATE products SET price = CASE id
    WHEN 1 THEN 19.99
    WHEN 2 THEN 29.99
    ELSE price
END WHERE id IN (1,2);

临时表优化

SQL示例
-- 调整临时表参数
[mysqld]
tmp_table_size = 64M
max_heap_table_size = 64M
internal_tmp_mem_storage_engine = TempTable  -- MySQL 8.0默认使用TempTable引擎

-- 查看临时表使用情况
SHOW STATUS LIKE 'Created_tmp%';

架构优化

读写分离

将读操作和写操作分发到不同的数据库服务器:

  • 主服务器处理写操作
  • 从服务器处理读操作
  • 通过主从复制保持数据同步

分库分表

当单表数据量过大时,可以考虑分库分表:

  • 水平分表:按数据行拆分
  • 垂直分表:按列拆分
  • 分库:将不同业务的数据分布到不同数据库

使用缓存

  • 应用层缓存(Redis、Memcached)
  • 数据库查询缓存(MySQL 8.0之前)
  • CDN缓存静态资源
优化原则:性能优化是一个持续的过程,需要根据实际业务需求和数据特征进行调整。测量、分析、优化、再测量。

性能监控工具

使用专业的监控工具可以帮助识别性能瓶颈:

MySQL内置工具

  • SHOW STATUS - 查看服务器状态变量
  • SHOW PROCESSLIST - 查看当前连接和查询
  • EXPLAIN - 分析查询执行计划
  • Performance Schema - 性能模式,收集数据库性能数据
  • Information Schema - 信息模式,查看数据库元数据

外部监控工具

  • MySQL Enterprise Monitor - MySQL官方企业级监控工具
  • Percona Monitoring and Management - Percona监控管理工具
  • Prometheus + Grafana - 开源监控解决方案
  • pt-query-digest - Percona Toolkit中的查询分析工具

慢查询日志分析

慢查询日志是识别性能问题的重要工具:

启用慢查询日志

my.cnf 配置
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
log_queries_not_using_indexes = 1
log_slow_admin_statements = 1
min_examined_row_limit = 100

分析慢查询日志

终端命令
# 使用mysqldumpslow分析慢查询日志
mysqldumpslow /var/log/mysql/slow.log

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

下一步学习

掌握了性能优化知识后,您可以继续学习: