Appearance
性能优化
MySQL性能优化是数据库管理中的重要环节。本章将介绍查询优化、索引优化、服务器配置优化等方面的知识,帮助你提升MySQL的运行效率。
性能优化概述
性能优化的目标
sql
-- 性能优化的主要目标:
-- 1. 缩短查询响应时间
-- 2. 提高系统吞吐量
-- 3. 降低资源消耗
-- 4. 提升用户体验
-- 性能优化的原则:
-- 1. 先诊断问题,再进行优化
-- 2. 优化要有针对性,避免过度优化
-- 3. 优化后要验证效果
-- 4. 记录优化过程和结果性能优化的层次
sql
-- MySQL性能优化可以分为多个层次:
-- 1. SQL语句层优化
-- 优化查询语句、使用索引
-- 2. 数据库结构层优化
-- 表设计、索引设计、分区
-- 3. MySQL服务器层优化
-- 配置参数、缓存设置
-- 4. 操作系统层优化
-- 文件系统、内核参数
-- 5. 硬件层优化
-- CPU、内存、磁盘、网络查询优化
使用EXPLAIN分析查询
sql
-- EXPLAIN是分析SQL执行计划的重要工具
-- 创建示例表
CREATE TABLE employees (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
department_id INT,
salary DECIMAL(10,2),
hire_date DATE,
INDEX idx_dept (department_id),
INDEX idx_salary (salary)
);
CREATE TABLE departments (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50)
);
-- 使用EXPLAIN分析查询
EXPLAIN SELECT * FROM employees WHERE department_id = 1;
-- 使用EXPLAIN ANALYZE(MySQL 8.0.18+)获取更详细的信息
EXPLAIN ANALYZE SELECT * FROM employees WHERE department_id = 1;
-- EXPLAIN输出列说明:
-- id:查询标识符
-- select_type:查询类型
-- table:访问的表
-- partitions:匹配的分区
-- type:访问类型(重要)
-- possible_keys:可能使用的索引
-- key:实际使用的索引
-- key_len:使用的索引长度
-- ref:索引比较的列
-- rows:预估扫描的行数
-- filtered:条件过滤的行百分比
-- Extra:额外信息
-- type列(访问类型)从好到坏:
-- system:系统表,只有一行
-- const:主键或唯一索引查询,最多一行
-- eq_ref:join时使用主键或唯一索引
-- ref:使用非唯一索引
-- range:索引范围扫描
-- index:全索引扫描
-- ALL:全表扫描(最差)EXPLAIN示例分析
sql
-- 示例1:全表扫描
EXPLAIN SELECT * FROM employees WHERE salary + 1000 > 5000;
-- type: ALL
-- 问题:在索引列上使用函数,导致索引失效
-- 优化:改写条件
EXPLAIN SELECT * FROM employees WHERE salary > 4000;
-- type: range
-- key: idx_salary
-- 示例2:索引失效
EXPLAIN SELECT * FROM employees WHERE name LIKE '%张%';
-- type: ALL
-- 问题:前置通配符导致索引失效
-- 优化:使用前缀匹配
EXPLAIN SELECT * FROM employees WHERE name LIKE '张%';
-- type: range
-- 示例3:复合索引使用
CREATE INDEX idx_dept_salary ON employees(department_id, salary);
EXPLAIN SELECT * FROM employees WHERE department_id = 1 AND salary > 5000;
-- type: range
-- key: idx_dept_salary
-- 正确使用了复合索引
EXPLAIN SELECT * FROM employees WHERE salary > 5000;
-- type: ALL
-- key: NULL
-- 问题:跳过了复合索引的前导列,索引失效
-- 示例4:覆盖索引
EXPLAIN SELECT department_id, salary FROM employees WHERE department_id = 1;
-- Extra: Using index
-- 使用了覆盖索引,不需要回表慢查询日志
sql
-- 开启慢查询日志
-- 在my.cnf中配置:
-- slow_query_log = 1
-- slow_query_log_file = /var/log/mysql/mysql-slow.log
-- long_query_time = 2 -- 超过2秒的查询记录
-- 动态开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2;
SET GLOBAL slow_query_log_file = '/var/log/mysql/mysql-slow.log';
-- 查看慢查询日志状态
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
-- 记录没有使用索引的查询
SET GLOBAL log_queries_not_using_indexes = 'ON';
-- 使用mysqldumpslow分析慢查询日志
-- mysqldumpslow -s t -t 10 /var/log/mysql/mysql-slow.log
-- -s t: 按查询时间排序
-- -t 10: 显示前10条
-- 查看慢查询数量
SHOW GLOBAL STATUS LIKE 'Slow_queries';查询优化技巧
sql
-- 1. 避免SELECT *
-- 不推荐
SELECT * FROM employees;
-- 推荐:只查询需要的列
SELECT id, name, salary FROM employees;
-- 2. 使用LIMIT限制结果集
-- 分页查询
SELECT * FROM employees LIMIT 0, 10; -- 第一页
SELECT * FROM employees LIMIT 10, 10; -- 第二页
-- 大数据量分页优化
-- 不推荐:偏移量大会很慢
SELECT * FROM employees LIMIT 1000000, 10;
-- 推荐:使用子查询优化
SELECT * FROM employees
WHERE id >= (SELECT id FROM employees ORDER BY id LIMIT 1000000, 1)
LIMIT 10;
-- 3. 避免在WHERE子句中对列进行函数操作
-- 不推荐:索引失效
SELECT * FROM employees WHERE YEAR(hire_date) = 2024;
-- 推荐:使用范围查询
SELECT * FROM employees
WHERE hire_date >= '2024-01-01' AND hire_date < '2025-01-01';
-- 4. 避免隐式类型转换
-- 假设id是INT类型
-- 不推荐:字符串与数字比较
SELECT * FROM employees WHERE id = '1'; -- 隐式转换,可能影响索引
-- 推荐:类型匹配
SELECT * FROM employees WHERE id = 1;
-- 5. 使用UNION ALL代替UNION(如果不需要去重)
-- UNION会进行去重排序,性能较低
SELECT id FROM employees WHERE department_id = 1
UNION
SELECT id FROM employees WHERE salary > 5000;
-- UNION ALL不去重,性能更好
SELECT id FROM employees WHERE department_id = 1
UNION ALL
SELECT id FROM employees WHERE salary > 5000;
-- 6. 使用EXISTS代替IN(大数据量时)
-- 当子查询结果集很大时,IN效率较低
SELECT * FROM employees
WHERE department_id IN (SELECT id FROM departments WHERE name LIKE '%部');
-- 使用EXISTS可能更高效
SELECT * FROM employees e
WHERE EXISTS (
SELECT 1 FROM departments d
WHERE d.id = e.department_id AND d.name LIKE '%部'
);
-- 7. 批量插入优化
-- 不推荐:循环单条插入
INSERT INTO employees (name) VALUES ('张三');
INSERT INTO employees (name) VALUES ('李四');
INSERT INTO employees (name) VALUES ('王五');
-- 推荐:批量插入
INSERT INTO employees (name) VALUES ('张三'), ('李四'), ('王五');
-- 8. 使用JOIN代替子查询
-- 不推荐:相关子查询
SELECT * FROM employees e
WHERE salary > (SELECT AVG(salary) FROM employees WHERE department_id = e.department_id);
-- 推荐:使用JOIN
SELECT e.*
FROM employees e
JOIN (
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
) avg ON e.department_id = avg.department_id
WHERE e.salary > avg.avg_salary;索引优化
索引设计原则
sql
-- 索引设计的基本原则:
-- 1. 为经常用于WHERE、JOIN、ORDER BY、GROUP BY的列创建索引
-- 为WHERE条件列创建索引
CREATE INDEX idx_status ON orders(status);
-- 为JOIN关联列创建索引
CREATE INDEX idx_user_id ON orders(user_id);
-- 为ORDER BY排序列创建索引
CREATE INDEX idx_created_at ON orders(created_at);
-- 2. 选择性高的列更适合建索引
-- 选择性 = 不同值的数量 / 总行数
-- 选择性越接近1,索引效果越好
-- 查看列的选择性
SELECT
COUNT(DISTINCT department_id) / COUNT(*) AS selectivity
FROM employees;
-- 3. 复合索引遵循最左前缀原则
-- 创建复合索引
CREATE INDEX idx_dept_salary_hire ON employees(department_id, salary, hire_date);
-- 可以使用索引的查询
WHERE department_id = 1
WHERE department_id = 1 AND salary > 5000
WHERE department_id = 1 AND salary > 5000 AND hire_date > '2024-01-01'
-- 不能使用索引的查询
WHERE salary > 5000 -- 跳过了department_id
WHERE hire_date > '2024-01-01' -- 跳过了前两列
-- 4. 避免在索引列上使用函数或计算
-- 不推荐
WHERE YEAR(created_at) = 2024
-- 推荐
WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01'
-- 5. 避免过多的索引
-- 索引会占用存储空间,降低写入性能
-- 查看表的索引
SHOW INDEX FROM employees;
-- 查看索引使用情况
SELECT
OBJECT_SCHEMA,
OBJECT_NAME,
INDEX_NAME,
COUNT_READ,
COUNT_FETCH
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE OBJECT_SCHEMA = 'mydb';索引优化示例
sql
-- 示例表
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
product_id INT,
status VARCHAR(20),
total_amount DECIMAL(10,2),
created_at DATETIME,
INDEX idx_user (user_id),
INDEX idx_status (status)
);
-- 场景1:多条件查询
-- 查询:某用户的已完成订单
SELECT * FROM orders WHERE user_id = 100 AND status = 'completed';
-- 分析:两个单列索引,MySQL只能使用一个
EXPLAIN SELECT * FROM orders WHERE user_id = 100 AND status = 'completed';
-- 优化:创建复合索引
CREATE INDEX idx_user_status ON orders(user_id, status);
-- 场景2:排序查询
-- 查询:按创建时间倒序获取订单
SELECT * FROM orders ORDER BY created_at DESC LIMIT 10;
-- 分析:需要文件排序
EXPLAIN SELECT * FROM orders ORDER BY created_at DESC LIMIT 10;
-- 优化:为排序列创建索引
CREATE INDEX idx_created_at ON orders(created_at);
-- 场景3:覆盖索引
-- 查询:只需要索引列
SELECT user_id, status FROM orders WHERE user_id = 100;
-- 分析:使用覆盖索引,不需要回表
EXPLAIN SELECT user_id, status FROM orders WHERE user_id = 100;
-- Extra: Using index
-- 场景4:索引下推
-- 查询:使用复合索引的部分列
SELECT * FROM orders WHERE user_id = 100 AND status LIKE 'comp%';
-- MySQL 5.6+支持索引下推(ICP)
-- 在索引扫描时就过滤status,减少回表次数
EXPLAIN SELECT * FROM orders WHERE user_id = 100 AND status LIKE 'comp%';
-- Extra: Using index condition索引维护
sql
-- 查看索引碎片
-- 分析表
ANALYZE TABLE orders;
-- 查看表状态
SHOW TABLE STATUS LIKE 'orders';
-- 查看索引统计信息
SHOW INDEX FROM orders;
-- 重建索引(减少碎片)
ALTER TABLE orders ENGINE=InnoDB;
-- 或使用OPTIMIZE TABLE
OPTIMIZE TABLE orders;
-- 注意:OPTIMIZE TABLE会锁表,大表操作需谨慎
-- 删除未使用的索引
-- 查询未使用的索引
SELECT
OBJECT_SCHEMA AS database_name,
OBJECT_NAME AS table_name,
INDEX_NAME AS index_name
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE INDEX_NAME IS NOT NULL
AND COUNT_STAR = 0
AND OBJECT_SCHEMA = 'mydb'
ORDER BY OBJECT_SCHEMA, OBJECT_NAME;
-- 删除不需要的索引
DROP INDEX idx_unused ON orders;表结构优化
选择合适的数据类型
sql
-- 1. 使用最小的数据类型
-- 不推荐:存储小数字使用大类型
CREATE TABLE bad_example (
status BIGINT, -- 只需要0-10的值
name CHAR(100) -- 名字通常不超过20字符
);
-- 推荐:使用合适的类型
CREATE TABLE good_example (
status TINYINT, -- 存储-128到127
name VARCHAR(50) -- 可变长度
);
-- 2. 使用整数类型存储IP地址
-- 不推荐:使用VARCHAR存储IP
CREATE TABLE bad_ip (
ip VARCHAR(15)
);
-- 推荐:使用INT存储
CREATE TABLE good_ip (
ip INT UNSIGNED
);
-- IP转换函数
SELECT INET_ATON('192.168.1.1'); -- 字符串转整数
SELECT INET_NTOA(3232235777); -- 整数转字符串
-- 3. 使用DECIMAL存储精确数值
-- 不推荐:使用FLOAT存储金额
CREATE TABLE bad_money (
amount FLOAT
);
-- 推荐:使用DECIMAL
CREATE TABLE good_money (
amount DECIMAL(10, 2)
);
-- 4. 使用合适的时间类型
-- DATE:只存日期
-- TIME:只存时间
-- DATETIME:日期和时间(8字节)
-- TIMESTAMP:时间戳(4字节,带时区转换)
-- 推荐使用TIMESTAMP(如果不需要存储1970年之前或2038年之后的时间)
CREATE TABLE events (
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);表设计优化
sql
-- 1. 范式化与反范式化
-- 第三范式(3NF):消除传递依赖
-- 优点:减少数据冗余,更新效率高
-- 缺点:查询需要多表JOIN
-- 反范式化:适度冗余
-- 优点:减少JOIN,查询效率高
-- 缺点:数据冗余,更新需要维护一致性
-- 示例:订单表存储用户名(反范式化)
CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT,
user_name VARCHAR(50), -- 冗余存储,避免JOIN
total_amount DECIMAL(10,2)
);
-- 2. 垂直拆分
-- 将大表拆分为多个小表
-- 将不常用的大字段单独存储
-- 主表
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50),
email VARCHAR(100)
);
-- 扩展信息表
CREATE TABLE user_profiles (
user_id INT PRIMARY KEY,
avatar BLOB, -- 大字段单独存储
bio TEXT,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- 3. 水平拆分
-- 按规则将数据分散到多个表
-- 按时间分表
CREATE TABLE orders_2024_01 (LIKE orders);
CREATE TABLE orders_2024_02 (LIKE orders);
-- 按用户ID取模分表
-- user_id % 10 = 0 -> users_0
-- user_id % 10 = 1 -> users_1
-- ...
-- 4. 使用分区表
-- 按范围分区
CREATE TABLE orders_partitioned (
id INT,
user_id INT,
created_at DATE
)
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 users_by_region (
id INT,
name VARCHAR(50),
region_id INT
)
PARTITION BY LIST (region_id) (
PARTITION p_north VALUES IN (1, 2, 3),
PARTITION p_south VALUES IN (4, 5, 6),
PARTITION p_east VALUES IN (7, 8, 9),
PARTITION p_west VALUES IN (10, 11, 12)
);
-- 查看分区信息
SELECT * FROM information_schema.PARTITIONS
WHERE TABLE_NAME = 'orders_partitioned';服务器配置优化
内存配置
ini
# my.cnf 内存相关配置
[mysqld]
# InnoDB缓冲池大小(最重要的配置)
# 建议设置为物理内存的60%-80%
innodb_buffer_pool_size = 4G
# 缓冲池实例数(大内存时设置)
innodb_buffer_pool_instances = 4
# 日志缓冲区大小
innodb_log_buffer_size = 16M
# 排序缓冲区(每个连接)
sort_buffer_size = 2M
# 连接缓冲区(每个连接)
join_buffer_size = 2M
# 读缓冲区
read_buffer_size = 1M
read_rnd_buffer_size = 1M
# 表缓存
table_open_cache = 2000
# 线程缓存
thread_cache_size = 100
# 最大连接数
max_connections = 500InnoDB配置
ini
# InnoDB相关配置
[mysqld]
# 缓冲池大小
innodb_buffer_pool_size = 4G
# 日志文件大小
innodb_log_file_size = 512M
# 日志文件数量
innodb_log_files_in_group = 2
# 刷新日志策略
# 0:每秒刷新(性能最好,可能丢1秒数据)
# 1:每次提交刷新(最安全,默认)
# 2:每次提交写入OS缓存,每秒刷新
innodb_flush_log_at_trx_commit = 1
# 刷新方法
innodb_flush_method = O_DIRECT
# 文件每表(每个表一个独立文件)
innodb_file_per_table = 1
# 并发线程数
innodb_thread_concurrency = 0 # 0表示自动
# IO能力
innodb_io_capacity = 2000
innodb_io_capacity_max = 4000
# 脏页刷新比例
innodb_max_dirty_pages_pct = 75查询缓存(MySQL 8.0已移除)
ini
# MySQL 5.7及以下版本的查询缓存配置
# 注意:MySQL 8.0已移除查询缓存功能
[mysqld]
# 查询缓存类型
# 0:关闭
# 1:开启,缓存所有SELECT结果
# 2:只缓存显式声明SQL_CACHE的查询
query_cache_type = 0
# 查询缓存大小
query_cache_size = 0
# 建议:对于写入频繁的数据库,关闭查询缓存
# 因为每次写入都要更新缓存,反而降低性能监控与诊断
性能监控指标
sql
-- 1. 查看服务器状态
-- 查看所有状态变量
SHOW GLOBAL STATUS;
-- 查看特定状态变量
SHOW GLOBAL STATUS LIKE 'Connections';
SHOW GLOBAL STATUS LIKE 'Slow_queries';
SHOW GLOBAL STATUS LIKE 'Innodb%';
-- 重要状态变量:
-- Connections:连接次数
-- Uptime:运行时间
-- Slow_queries:慢查询数量
-- Queries:查询总数
-- Innodb_row_read:InnoDB读取行数
-- Innodb_row_inserted:InnoDB插入行数
-- Innodb_row_updated:InnoDB更新行数
-- Innodb_row_deleted:InnoDB删除行数
-- 2. 查看系统变量
SHOW VARIABLES LIKE 'innodb%';
SHOW VARIABLES LIKE 'max_connections';
SHOW VARIABLES LIKE 'thread%';
-- 3. 查看进程列表
SHOW PROCESSLIST;
SHOW FULL PROCESSLIST;
-- 查看长时间运行的查询
SELECT * FROM information_schema.PROCESSLIST
WHERE TIME > 10
ORDER BY TIME DESC;
-- 4. 查看InnoDB状态
SHOW ENGINE INNODB STATUS;使用Performance Schema
sql
-- Performance Schema是MySQL的性能监控工具
-- 查看是否启用
SHOW VARIABLES LIKE 'performance_schema';
-- 查看等待事件
SELECT * FROM performance_schema.events_waits_summary_global_by_event_name
ORDER BY COUNT_STAR DESC LIMIT 10;
-- 查看语句执行统计
SELECT
DIGEST_TEXT AS query,
COUNT_STAR AS executions,
AVG_TIMER_WAIT/1000000000 AS avg_latency_ms
FROM performance_schema.events_statements_summary_by_digest
ORDER BY AVG_TIMER_WAIT DESC
LIMIT 10;
-- 查看表IO统计
SELECT
OBJECT_SCHEMA,
OBJECT_NAME,
COUNT_READ,
COUNT_WRITE
FROM performance_schema.table_io_waits_summary_by_table
WHERE OBJECT_SCHEMA = 'mydb'
ORDER BY COUNT_READ + COUNT_WRITE DESC;
-- 查看索引使用统计
SELECT
OBJECT_SCHEMA,
OBJECT_NAME,
INDEX_NAME,
COUNT_READ
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE INDEX_NAME IS NOT NULL
ORDER BY COUNT_READ DESC;使用Sys Schema
sql
-- Sys Schema提供了更友好的性能视图(MySQL 5.7+)
-- 查看执行最慢的SQL
SELECT * FROM sys.statements_with_runtimes_in_95th_percentile LIMIT 10;
-- 查看全表扫描的SQL
SELECT * FROM sys.statements_with_full_table_scans LIMIT 10;
-- 查看使用临时表的SQL
SELECT * FROM sys.statements_with_temp_tables LIMIT 10;
-- 查看冗余索引
SELECT * FROM sys.schema_redundant_indexes;
-- 查看未使用的索引
SELECT * FROM sys.schema_unused_indexes;
-- 查看表统计信息
SELECT * FROM sys.schema_table_statistics;
-- 查看InnoDB锁等待
SELECT * FROM sys.innodb_lock_waits;
-- 查看内存使用
SELECT * FROM sys.memory_global_by_current_bytes;常见性能问题
连接数过多
sql
-- 查看当前连接数
SHOW STATUS LIKE 'Threads_connected';
-- 查看最大连接数
SHOW VARIABLES LIKE 'max_connections';
-- 查看各用户的连接数
SELECT USER, COUNT(*) as connections
FROM information_schema.PROCESSLIST
GROUP BY USER;
-- 解决方案:
-- 1. 增加最大连接数
SET GLOBAL max_connections = 1000;
-- 2. 使用连接池
-- 3. 设置连接超时
SET GLOBAL wait_timeout = 28800;
SET GLOBAL interactive_timeout = 28800;
-- 4. 杀掉空闲连接
-- 查看空闲连接
SELECT * FROM information_schema.PROCESSLIST WHERE COMMAND = 'Sleep' AND TIME > 3600;
-- 杀掉特定连接
KILL CONNECTION <id>;慢查询问题
sql
-- 诊断慢查询的步骤:
-- 1. 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
-- 2. 找出慢查询
-- 使用mysqldumpslow或pt-query-digest分析
-- 3. 使用EXPLAIN分析执行计划
EXPLAIN SELECT ...;
-- 4. 检查是否使用了索引
-- 确保type不是ALL
-- 5. 优化查询或添加索引
-- 常见慢查询原因:
-- - 没有使用索引
-- - 索引失效(函数、类型转换等)
-- - 返回数据量过大
-- - 复杂的JOIN操作
-- - 大量的子查询锁等待问题
sql
-- 查看锁等待
SELECT * FROM sys.innodb_lock_waits;
-- 查看当前锁
SELECT * FROM performance_schema.data_locks;
-- 查看锁等待超时设置
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';
-- 解决方案:
-- 1. 优化事务,减少锁持有时间
-- 2. 按相同顺序访问资源,避免死锁
-- 3. 使用合适的隔离级别
-- 4. 增加锁等待超时时间(不推荐)本章小结
本章详细介绍了MySQL性能优化的相关知识:
- 查询优化:学会使用EXPLAIN分析执行计划,掌握查询优化技巧
- 索引优化:理解索引设计原则,学会索引维护
- 表结构优化:掌握数据类型选择、范式化与反范式化、分表分区等技巧
- 服务器配置:了解内存、InnoDB等重要配置参数
- 监控诊断:学会使用Performance Schema和Sys Schema监控性能
- 常见问题:掌握连接数、慢查询、锁等待等问题的诊断和解决方法
性能优化是一个持续的过程,需要不断监控、分析、优化。建议在生产环境中建立完善的监控体系,及时发现和解决性能问题。
至此,MySQL教程全部完成。希望本教程能帮助你掌握MySQL数据库的核心知识和技能!
