MYSQL性能优化
索引合并优化
索引合并访问方法检索多个range扫描的行并将其结果合并为一个。索引合并只合并单个表的索引扫描,而不合并跨多个表的扫描。
可以使用索引合并的示例
SELECT * FROM tbl_name WHERE key1 = 10 OR key2 = 20;SELECT * FROM tbl_nameWHERE (key1 = 10 OR key2 = 20) AND non_key = 30;SELECT * FROM t1, t2WHERE (t1.key1 IN (1,2) OR t1.key2 LIKE 'value%')AND t2.key1 = t1.some_col;SELECT * FROM t1, t2WHERE t1.key1 = 1AND (t2.key1 = t1.some_col OR t2.key2 = t1.some_col2);
join_buffer_size
join_buffer_size 是 MySQL 中的一个系统变量,它定义了在执行连接操作时 MySQL 为每个连接分配的缓冲区的大小。
当 MySQL 执行连接操作时,如果连接的表没有使用索引,那么它会使用join_buffer_size定义的缓冲区来暂存一个表的数据,然后逐行与另一个表中的数据进行比较以找到匹配的行。减少对磁盘的访问次数,提高查询性能
这个缓冲区主要用于优化连接操作的性能,特别是在使用嵌套循环(Nested-Loop)连接算法时
SHOW VARIABLES LIKE 'join_buffer_size'; -- 常见默认值为256/512k
索引条件下推
Index Condition Pushdown(ICP) 允许MySQL查询优化器将部分WHERE条件下推到存储引擎层进行评估,从而减少存储引擎访问基表的次数,提高查询效率 。
在未启用 ICP 时,存储引擎会根据索引找到满足条件的记录的主键值,然后读取完整的行记录并返回给 MySQL 服务器层,由服务器层去检测这些记录是否满足 WHERE 条件。启用 ICP 后,如果 WHERE 条件的某些部分可以用索引中的列来评估,那么这部分条件会被下推到存储引擎。存储引擎在索引扫描过程中,会使用索引项评估这些条件,并且只有当条件满足时,才会从表中读取相应的行。
索引下推条件:
- 对于InnoDB表,ICP 仅用于二级索引。ICP 的目标是减少整行读取的次数,从而减少 I/O 操作。对于 InnoDB聚簇索引,完整记录已经读入InnoDB 缓冲区。在这种情况下使用 ICP 不会减少 I/O。
- ICP 不支持在虚拟生成列上创建的二级索引。InnoDB 支持在虚拟生成列上创建的二级索引。
- 引用子查询的条件无法下推。
- 引用存储函数的条件无法下推。存储引擎无法调用存储函数。
- 触发器条件无法下推。
- 条件不能被推送到包含对系统变量的引用的派生表。
extra Using index condition 表示使用了索引下推。
Multi-Range Read Optimization (MRR)
MRR 是MySQL数据库中的一种优化技术,旨在通过减少磁盘I/O操作的次数和提高I/O操作的效率,从而加速查询过程。
当表很大且未存储在存储引擎的缓存中时,使用二级索引上的范围扫描读取行会导致对基表进行许多随机磁盘访问。借助磁盘扫描多范围读取 (MRR) 优化,MySQL 尝试通过首先仅扫描索引并收集相关行的键来减少范围扫描的随机磁盘访问次数。然后对键进行排序,最后使用主键的顺序从基表中检索行。磁盘扫描 MRR 的动机是减少随机磁盘访问的次数,而是实现对基表数据的更连续的扫描。
它特别适用于范围查询和包含多个范围条件的查询。MRR在MySQL5.6及更高的版本中默认开启。
当使用 MRR 时,输出Extra中的列 EXPLAIN显示 Using MRR
ORDER BY
MYSQL8.3及更早的版本,GROUP BY 在某些条件下会隐式排序。在 MySQL 8.4 中,这种情况不再发生,因此不再需要在末尾加上ORDER BY NULL以抑制隐式排序(如以前所做的那样)
如果MYSQL不能使用索引排序,将使用filesort进行排序。filesort操作会在内存中进行,如果内存不足,可能需要使用磁盘临时文件完成排序。为了获取filesort操作所需的内存,优化器会根据需要逐步分配内存缓冲区,直至达到 sort_buffer_size系统变量设置的大小。
因此可以设置 sort_buffer_size更大的值来加快较大的排序速度,而不必担心小排序会占用过多的内存
read_rnd_buffer_size 是另一个系统变量,用于定义随机读取操作的缓冲区大小。增加这个值可以使得每次读取更多的行,从而减少磁盘 I/O 操作的次数。
列表尽量设置为NOT NULL
尽量将列设置为NOT NULL,这样可以更好的利用索引,优化器可以做出更好的判断,并且应用程序不需要判断NULL值,还可以节省存储空间(每个允许 NULL 值的列会额外消耗一个比特位来存储 NULL 信息)。
尽量将不常被检索的列拆分为单独的表
当 MySQL 从一行中检索任何值时,它会读取包含该行所有列(以及可能的其他相邻行)的数据块。保持每行较小,仅包含最常用的列,这样可以让每个数据块容纳更多行。这种紧凑的表可以减少常见查询的磁盘 I/O 和内存使用量。
避免插入、更新或删除大量行后执行回滚
执行大事物回滚的性能可能是原始操作的几倍,终止数据库进程也没用,因为回滚会在服务器启动时再次开始。
为了减少发生次问题的可能性:
- 增加buffer pool的大小,以便所有数据变化都可以被缓存而不是立即写入磁盘
SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; -- 查看buffer pool大小
SHOW ENGINE INNODB STATUS; -- 查看buffer pool使用情況
- 设置 innodb_change_buffering=all 以便除了插入之外,更新和删除操作也会被缓冲。
- 建议将大量数据变更操作分解为下批次,定期commit。
批量加载数据
将数据导入InnoDB时,关闭自动提交模式,因为它会在每次插入时将日志刷新到磁盘。
SET autocommit=0;
… SQL import statements …
COMMIT;
使用mysqldump --opt(通常默认启用)创建的转储文件,可以快速导入到表中,而不需要使用额外的事物控制语句
如果有唯一性约束、外键约束检查,在导入期间可以暂时关闭这些约束,对于大表,这可以节省大量的磁盘I/O。
SET unique_checks=0;
... SQL import statements ...
SET unique_checks=1;SET foreign_key_checks=0;
... SQL import statements ...
SET foreign_key_checks=1;
INSERT 如果需要插入多行, 请使用多行语法来减少客户端和服务器之间的通信开销
INSERT INTO yourtable VALUES (1,2), (5,5), ...;
执行计划
type
按最佳到最差类排序如下:
- system 该表只有一行(= 系统表)。这是 const连接类型的特殊情况。
- const 表最多有一行匹配
SELECT * FROM tbl_name WHERE primary_key=1;SELECT * FROM tbl_nameWHERE primary_key_part1=1 AND primary_key_part2=2;
- eq_ref 对每个索引键值值返回一行,非常高效,通常用于主键或唯一索引的查找
SELECT * FROM ref_table,other_tableWHERE ref_table.key_column=other_table.column;SELECT * FROM ref_table,other_tableWHERE ref_table.key_column_part1=other_table.columnAND ref_table.key_column_part2=1;
- ref 通过索引查找,返回匹配某个特定值的所有行,适用于非唯一索引的查找(连接无法根据键值选择单个行)
SELECT * FROM ref_table WHERE key_column=expr;SELECT * FROM ref_table,other_tableWHERE ref_table.key_column=other_table.column;SELECT * FROM ref_table,other_tableWHERE ref_table.key_column_part1=other_table.columnAND ref_table.key_column_part2=1;
- fulltext 连接是使用FULLTEXT 索引执行的。
- ref or null 类似于ref,但MySQL还会对包含NULL值的行进行额外的检索
SELECT * FROM ref_tableWHERE key_column=expr OR key_column IS NULL;
- index merge 表示使用了索引合并优化,key输出行中的列包含所用索引的列表
- unique subquery 表示查询中使用了子查询,并且子查询的结果可以通过唯一索引快速确定
value IN (SELECT primary_key FROM single_table WHERE some_expr)
- index subquery 类似unique_subquery,但是是非唯一索引
value IN (SELECT key_column FROM single_table WHERE some_expr)
- range 仅检索给定范围内的行,使用索引选择行。key 输出行中的列指示使用了哪个索引。key_len包含使用的最长键部分。
SELECT * FROM tbl_nameWHERE key_column = 10;SELECT * FROM tbl_nameWHERE key_column BETWEEN 10 and 20;SELECT * FROM tbl_nameWHERE key_column IN (10,20,30);SELECT * FROM tbl_nameWHERE key_part1 = 10 AND key_part2 IN (10,20,30);
- index 连接类型index与 相同 ALL,只是扫描索引树(全索引扫描而不是全表扫描)
- ALL 全表扫描
Extra (部分)
- Full scan on NULL 优化器决定对一个空的索引进行全表扫描,这通常意味着查询条件与索引不匹配,或者索引没有被正确使用。
- Using where 表明MySQL需要在服务器层面对索引返回的结果集应用WHERE子句中的条件进行额外的过滤,可能意味着查询没有有效使用索引,或者索引不是最优的。
- Using temporary 表明MYSQL需要创建一个临时表来保存结果,通常发生在包含GROUP BY,ORDER BY的语句,但它们所列出的列不相同的情况。可能会带来额外的磁盘I/O。
- Using filesort 表示MySQL需要对结果进行排序,但是无法利用索引,因此必须执行额外的排序操作。这也是一个性能瓶颈,因为它通常涉及到大量的数据移动和比较。
- Using index 通常意味着查询优化器决定使用索引来检索记录,而不是全表扫描。
- Using index condition 表明MySQL正在使用索引条件下推(ICP)来优化查询。
- impossible where WHERE子句中的条件不可能选择任何行,即查询结果将为空。
- select tables optimized away 表明某些表被识别为不必要,因此在执行查询时被省略。