MySQL Optimization

一、Slow Query

-- 查看当前配置
SHOW VARIABLES LIKE 'slow_query_log'; -- 查看慢查询日志状态,默认关闭
SHOW VARIABLES LIKE 'long_query_time'; -- 查看当前慢查询阈值,默认 10,不含 10
SHOW VARIABLES LIKE 'min_examined_row_limit'; -- 判定条件(满足任一):1. 执行时间 > long_query_time 2. 扫描行数 > min_examined_row_limit
SHOW VARIABLES LIKE 'log_queries_not_using_indexes'; -- 记录未使用索引的查询
SHOW VARIABLES LIKE 'slow_query_log_file'; -- 查看日志文件路径
-- 临时开启慢查询日志(无需重启MySQL)
SET GLOBAL slow_query_log = 'ON';  -- 开启慢查询日志
SET GLOBAL long_query_time = 0.1;  -- 时间阈值(单位:秒,建议从0.1开始)
SET GLOBAL min_examined_row_limit = 1000; -- 扫描行数阈值(需SYSTEM_VARIABLES_ADMIN权限)
SET GLOBAL log_output = 'FILE'; -- 确保输出到文件
FLUSH SLOW LOGS; -- 刷新慢查询日志
-- 捕获慢SQL(保持业务运行)
tail -f /var/lib/mysql/your_hostname-slow.log -- 查看实时日志(替换实际路径)
-- -a : 显示完整SQL(默认抽象化SQL)
-- -s 排序方式:t : 按总时间排序(默认)、l : 按锁定时间排序、r : 按返回行数排序、c : 按执行次数排序
-- -t : 显示前N条记录
mysqldumpslow -a -s t -t 5 /var/lib/mysql/user-slow.log -- 慢查询日志分析
SELECT * FROM mysql.slow_log WHERE start_time > NOW() - INTERVAL 5 MINUTE; -- 或使用MySQL内置分析(需登录MySQL)
mysqladmin -uroot-p flush-logs slow -- 慢查询日志删除重建(需要旧的要事先备份)
-- 分析完成后关闭,不是调优需要,不建议启动该参数
SET GLOBAL slow_query_log = 'OFF';  -- 关闭慢查询日志

 

二、EXPLAIN(DESCRIBE)

id:在一个大的查询语句中每个 SELECT 关键字都对应一个唯一的 id,表示操作表的顺序,id 越大优先加载

select_type:小查询在大查询中扮演什么角色

table:表名,查询的每一行记录都对应着一个单表

partitions:代表分区表中的命中情况,非分区表,该项为 NULL

type:针对单表的访问方法,是较为重要的一个指标。比如,看到 type 列的值是 ref,表明 MySQL 将使用 ref 访问方法来执行对 s1 表的查询

possible_keys:可能用到的索引

key:实际用到的索引

key_len:实际用到的(联合)索引字段长度,越短越好

ref:当使用索引列等值查询时,与索引列进行等值匹配的对象信息

rows:扫描行数量

filtered:某个表经过搜索条件过滤后剩余记录条数的百分比

extra:一些额外的信息,执行情况说明与描述

https://github.com/major/MySQLTuner-perl & https://www.percona.com/percona-toolkit

-- 清理冗余数据
OPTIMIZE TABLE your_table; -- 重建表空间
ANALYZE TABLE your_table; -- 更新统计信息

 

三、索引

索引可以减少磁盘 IO 次数,索引是数据结构

1、索引结构

BTree

B+Tree

自适应 hash 索引

InnoDB 不支持 hash 索引,但有自适应 hash 索引(show variables like '%innodb_adaptive_hash_index%'),直接定位数据页

R-Tree

2、常见索引(数据存储方式)

聚簇索引

主键构建的索引(有且只有一个,尽量选单调字段做主键),只有 InnoDB 支持,数据和索引放在一起,且按一定顺序,找到索引也就找到数据

非聚簇(二级、辅助)索引

非主键字段构建的索引,需要显式创建,叶子节点不存数据,存索引字段和主键,需要回表(根据主键再去聚簇索引中查找)

非聚簇索引可以有多个

联(复、组)合索引

index-merge-optimization:https://dev.mysql.com/doc/refman/8.4/en/index-merge-optimization.html

非聚簇索引的一种,多个字段建立索引,存储时按照多个字段依次排序。二级索引的索引值默认包含主键,所以二级索引也是复合索引。

3、与 MyISAM 对比

索引结构(B+Tree)

索引(myi,B+ 树)和数据(myd,不排序,按照插入顺序)分开存储。叶子节点 data 域存放对应数据的地址

常见索引(数据存储方式)

没有聚簇索引,全部是二级索引,需要回表(从 myi 中拿到偏移地址后去 myd 拿(很快)数据)

4、索引分类

功能上:普通索引、唯一索引(不可重复)、主键索引(一张表只有一个,唯一且不为空)、全文索引

作用字段数量上:单列(值)索引、联(组)合索引

物理实现上:聚簇索引、非聚簇(二级、辅助)索引

8.0 新增了隐藏索引(Invisible Indexes)和降序索引(Descending Indexes)

5、索引的设计(使用)原则

适合加索引(频繁的,区分度高的)

  • 业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引
  • 频繁作为 WHERE 查询条件的字段
  • 经常 GROUP BY 和 ORDER BY 的列,多个字段就建立联合索引(注意先后顺序和升序降序)
  • UPDATE、DELETE 的 WHERE 条件列
  • DISTINCT 字段需要创建索引
  • 多表 JOIN 连接操作
    • 首先,连接表的数量尽量不要超过 3 张,因为每增加一张表就相当于增加了一次嵌套的循环,数量级增长会非常快,严重影响查询的效率
    • 其次,对 WHERE 条件创建索引,因为 WHERE 才是对数据条件的过滤。如果在数据量非常大的情况下,没有 WHERE 条件过滤是非常可怕的
    • 最后,对用于连接的字段创建索引,且该字段在多张表中的类型必须一致。若一个为 int 另一个为 varchar,则会发生转换,转换会使用函数,使用函数索引会失效
  • 使用列的类型小的创建索引:类型大小指的就是该类型表示的数据范围的大小,能使用 INT 就不要使用 BIGINT,能使用 MEDIUMINT 就不要使用 INT
    • 数据类型越小,在查询时进行的比较操作越快
    • 数据类型越小,索引占用的存储空间就越少,在一个数据页内就可以放下更多的记录,从而减少磁盘 I/O 带来的性能损耗,也就意味着可以把更多的数据页缓存在内存中,从而加快读写效率
    • 对于主键来说更加适用,因为不仅聚簇索引中会存储主键值,其他所有二级索引的节点处都会存储记录的主键值,如果主键使用更小的数据类型,就意味着节省更多的存储空间(高效 I/O)
  • 使用字符串前缀创建索引
    • create table shop(address varchar(120) not null)
    • alter table shop add index(address(12))
    • select count(distinct left(address, 18))/count(*) as '截取前 18 个字符的选择度', count(distinct left(address, 12))/count(*)as '截取前 12 个字符的选择度' from shop
    • 在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度
    • 索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达 90% 以上,可以使用 count(distinct left(列名, 索引长度) / count(*) 的区分度来确定
    • 使用索引列前缀的方式无法支持使用索引排序,只能使用文件排序
  • 区分度高(散列性高)的列适合作为索引
    • 使用公式 select count (distinct a) / count (*) from t1 计算区分度,越接近 1 越好,一般超过 33% 就算是比较高效的索引
    • 联合索引把区分度高(散列性高)的列放在前面
  • 使用最频繁的列放到联合索引的左侧:这样也可以较少的建立一些索引。同时,由于“最左前缀原则”,可以增加联合索引的使用率
  • 在多个字段都要创建索引的情况下,联合索引优于单值索引

不适合加索引

  • 在 where 中使用不到的字段,不要设置索引
  • 数据量小的表最好不要使用索引,例如不到 1000 行
  • 有大量重复数据的列上不要建立索引,比如高于 10% 的时候,不需要对这个字段使用索引
  • 避免对经常更新的表创建过多的索引
  • 不建议用无序的值作为索引
  • 删除不再使用或者很少使用的索引
  • 不要定义冗余或重复的索引

建议单张表索引数量不超过 6 个

  • 每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间越大
  • 索引影响 INSERT、DELETE、UPDATE 等语句性能,因为表中数据更改的同时,索引也会进行调整和更新,会造成负担
  • 优化器在优化查询时,会根据统一信息,对每个可用的索引进行评估,生成最优的执行计划,若同时有很多索引都可用,会增加优化器生成执行计划的时间,降低查询性能

 

四、性能分析

Performance Schema Lock Tables

-- 查看当前所有事务
SELECT * FROM information_schema.innodb_trx;
-- 查看正在锁的事务
SELECT * FROM performance_schema.data_locks;
-- SELECT * FROM information_schema.innodb_locks;
-- 查看等待锁的事务
SELECT * FROM performance_schema.data_lock_waits;
-- SELECT * FROM information_schema.innodb_lock_waits;

-- 查看当前连接
SHOW STATUS LIKE 'Threads%';
-- Threads_connected:打开的连接数
-- Threads_created:表示创建过的线程数
-- Threads_running:激活的连接数(并发数,一般远低于 connected)

-- 如果 Threads_created 值过大的话,表明 MySQL 服务器一直在创建线程,可以适当增加 thread_cache_size 值
SHOW VARIABLES LIKE 'thread_cache_size';

-- 查询最大连接数
SHOW VARIABLES LIKE '%max_connections%';
-- 设置最大连接数
SET GLOBAL max_connections=1000;
-- 在 /etc/my.cnf 里设置数据库最大连接数
-- [mysqld]
-- max_connections = 1000

SHOW STATUS

SHOW [GLOBAL | SESSION] STATUS LIKE '参数'
-- last_query_cost:上一条 SQL 语句所需要读取的页的数量
-- Aborted_clients:由于客户没有正确关闭连接已经死掉,已经放弃的连接数量。
-- Aborted_connects:尝试已经失败的 MySQL 服务器的连接的次数。
-- Connections:试图连接 MySQL 服务器的次数。
-- Created_tmp_tables:当执行语句时,已经被创造了的隐含临时表的数量。
-- Delayed_insert_threads:正在使用的延迟插入处理器线程的数量。
-- Delayed_writes:用 INSERT:DELAYED 写入的行数。
-- Delayed_errors:用 INSERT:DELAYED 写入的发生某些错误(可能重复键值)的行数。
-- Flush_commands:执行 FLUSH 命令的次数。
-- Handler_delete:请求从一张表中删除行的次数。
-- Handler_read_first:请求读入表中第一行的次数。
-- Handler_read_key:请求数字基于键读行。
-- Handler_read_next:请求读入基于一个键的一行的次数。
-- Handler_read_rnd:请求读入基于一个固定位置的一行的次数。
-- Handler_update:请求更新表中一行的次数。
-- Handler_write:请求向表中插入一行的次数。
-- Key_blocks_used:用于关键字缓存的块的数量。
-- Key_read_requests:请求从缓存读入一个键值的次数。
-- Key_reads:从磁盘物理读入一个键值的次数。
-- Key_write_requests:请求将一个关键字块写入缓存次数。
-- Key_writes:将一个键值块物理写入磁盘的次数。
-- Max_used_connections:同时使用的连接的最大数目。
-- Not_flushed_key_blocks:在键缓存中已经改变但是还没被清空到磁盘上的键块。
-- Not_flushed_delayed_rows:在 INSERT:DELAY 队列中等待写入的行的数量。
-- Open_tables:打开表的数量。
-- Open_files:打开文件的数量。
-- Open_streams:打开流的数量(主要用于日志记载)
-- Opened_tables:已经打开的表的数量。
-- Questions:发往服务器的查询的数量。
-- Slow_queries:要花超过 long_query_time 时间的查询数量。
-- Threads_connected:当前打开的连接的数量。
-- Threads_running:不在睡眠的线程数量。
-- Uptime:服务器工作了多长时间,单位秒。

SHOW PROFILE

-- 查看 profile 是否开启
show variables like '%profiling%';
-- 开启 profile
set profiling=1;

-- 查看最近一次 sql 的执行周期
show profile;
-- 查看最近的几次
show profiles;
-- 根据 Query_ID 来查看 sql 的具体执行步骤
show profile cpu,block io for query Query_id;

索引失效的几种情况

-- where 条件有多个字段时,会优先选择多个字段的联合索引,而不会选择多个单个索引
-- 最左前缀:使用联合索引有顺序要求,换言之联合索引的创建有字段顺序之分
-- 主键最好使用递增序列
-- name like ‘a%’ 和 LEFT(name,1) = 'a',函数放在左边会使索引失效,类似还有计算和隐式类型转换等
-- 范围条件右侧的列索引失效:age=30 and id > 10 and name='zs',使用 age,id,name 联合索引会使 name 失效,使用 age,name,id 联合索引就没问题
-- 不等于会使索引失效
-- is null(相当于等于某值)可以使用索引,is not null(相当于不等于某值)不能使用索引,同理 not like 也不能使用索引
-- like '%xxx' 索引失效
-- or 前后存在非索引列,索引失效,必须 or 前后都有索引才会使用索引去查找
-- 库和表字符集不同,索引失效,相当于有隐式转换

JOIN

-- 驱动表(作为外循环,最好用结果集小(表行数*每行大小)的作为驱动表,也就是用小表驱动大表)是主表,被驱动表(作为内循环)是从表、非驱动表。EXPLAIN 结果中上面是驱动表,下面是被驱动表
-- 表连接的实现方式有三种:Simple Nested-Loop Join 简单嵌套循环连接、Index Nested-Loop Join 索引嵌套循环连接、Block Nested-Loop Join 块索引嵌套连接。整体效率:INLJ > BNLJ > SNLJ
-- Block Nested-Loop Join 设置:通过 show variables like '%optimizer_switch%' 查看 block_nested_loop(默认开启)。驱动表能否被一次加载完,要看 join_buffer_size,默认 256K。最大值在 32 位系统为 4G,在 64 位可大于 4G(但 Windows 只能为 4G)
-- 从 MySQL 8.0.20 版本开始将弃用 BNLJ,引入 Hash Join,默认会使用 Hash Join
-- left join 以左表为驱动表,right join 反之,STRAIGHT_JOIN 用在内连接中,强制使用左表当驱动表,改变 mysql 优化器选择的执行计划
-- 尽量用 join 替代子查询

排序、分组、分页

-- where 使用索引是为了避免全表扫描,order by 使用索引是为了避免 FileSort,而使用索引排序。where 和 order by 字段不同时可以建立联合索引。
-- 分页可以影响是否走索引,例如:select * from user order by age 不走索引(因为要全部回表),select * from user order by age limit 3 走索引(回表数据少)
-- order by 多个字段顺序不一致时索引会失效
-- where a in (...) order by b,c 不能使用 a,b,c 联合索引
-- sort_buffer_size 可提高效率
-- FileSort 分单路排序和双路排序,mysql 使用单路排序的前提是排序的字段大小要小于 max_length_for_sort_data
-- order by 时不要使用 select *
-- group by 中 where 效率高于 having

覆盖索引:查询的字段正好是建立索引的字段,这时 != 或 like ‘%xxx’ 条件在不回表的情况下也可能使用索引

索引条件下推(Index Condition Pushdown,简称 ICP):在回表之前进行过滤,没有回表也就不存在索引下推

 


https://dev.mysql.com/doc/refman/8.4/en/optimization.html

posted @ 2019-09-26 14:13  江湖小小白  阅读(378)  评论(0)    收藏  举报