MYSQL 调优学习笔记
“ 阅读完本文需要 5 分钟。”
最近在看《深入浅出 MYSQL》,将自己所学的知识点做个记录整理下来,尤其是 MYSQL 调优这一块是重点。1、SQL 优化1.1 show status 命令查看各种 SQL 的执行效率
mysql> show status like 'Com_%';
主要关心 Com_select、Com_insert、Com_update、Com_delete 的数值,分别代表了不同操作次数,对于 Innodb 引擎而言,参数发生了变化:Innodb_rows_read、Innodb_rows_inserted、Innodb_rows_updated、
Innodb_rows_deleted,通过查看参数可以了解当前数据库的应用是以插入更新为主还是以查询操作为主,以及各种类型的 SQL 大致的执行比例是多少。
1.1.2 定位执行效率较低的 SQL 语句一是通过慢查询日志定位执行效率较低的 SQL 语句,用 --log-slow-queries[= file_name] ;二是使用 mysql> show processlist; 实时命令查看。1.1.3 通过 EXPLAIN 分析低效 SQL 的执行计划
mysql> explain select sum(amount) from customer a, payment b
where 1=1 and a.customer_id = b.customer_id and
email = 'JANE.BENNET@sakilacustomer.org';
执行结果:
+----+-------------+-------+------------+------+--------------------+--------------------+---------+----------------------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+--------------------+--------------------+---------+----------------------+------+----------+-------------+ | 1 | SIMPLE | a | NULL | ref | PRIMARY,idx_email | idx_email | 153 | const | 1 | 100.00 | Using index | | 1 | SIMPLE | b | NULL | ref | idx_fk_customer_id | idx_fk_customer_id | 2 | sakila.a.customer_id | 26 | 100.00 | NULL | +----+-------------+-------+------------+------+--------------------+--------------------+---------+----------------------+------+----------+-------------+ 2 rows in set (0.10 sec)
注意 explain extended 已经在新的 MYSQL 版本中被取消了,可以使用 show warnings;查看具体执行的 CODE,同时书中在命令后面加上 \G 这个功能和具体的工具有关,Navicat 就不支持这种格式化功能,https://q.cnblogs.com/q/109910/。1.1.4 通过 show profile 分析 SQLMYSQL 从 5.0.37 版本增加了 show profiles 和 show profiles 语句的支持,1、查看是否支持:
mysql> select @@have_profiling;
2、开启 profiling
mysql> select @@profiling; mysql> set profiling = 1;
3、执行查询语句,使用 show profiles 语句查看执行效率
mysql> select count(*) from payment; mysql> show profiles; +----------+------------+------------------------------+ | Query_ID | Duration | Query | +----------+------------+------------------------------+ | 1 | 0.00024475 | select @@profiling | | 2 | 0.00551000 | select count(*) from payment | +----------+------------+------------------------------+ 2 rows in set (0.03 sec)
4、使用 show query for id 语句查看线程中的每个状态和消耗的时间
mysql> show profile for query 2; +----------------------+----------+ | Status | Duration | +----------------------+----------+ | starting | 0.000043 | | checking permissions | 0.000006 | | Opening tables | 0.000018 | | init | 0.000015 | | System lock | 0.000008 | | optimizing | 0.000007 | | statistics | 0.000016 | | preparing | 0.000014 | | executing | 0.000003 | | Sending data | 0.005319 | | end | 0.000007 | | query end | 0.000007 | | closing tables | 0.000009 | | freeing items | 0.000024 | | cleaning up | 0.000017 | +----------------------+----------+ 15 rows in set (0.03 sec)
5、查看 CPU 的消耗时间
mysql> show profile cpu for query 2;
6、查看 MYSQL 执行运行的代码数
mysql> show profile source for query 2;
1.1.5 通过 trace 分析优化器选择优化计划
第一步、开启 trace 分析器并设置格式为 JSON:
mysql> set optimizer_trace="enabled=on",end_markers_in_json=on;
查询语句:
mysql> select rental_id from rental where 1=1 and rental_date >= '2005-05-25 04:00:00'
and rental_date <= '2005-05-25 05:00:00' and inventory_id = 4466;
通过查询 information_schema.optimizer_trace 表来查看
mysql> select * from information_schema.optimizer_trace;
1.1.6 通过以上几个步骤确定问题出现原因,采取相应的优化措施,例如某个表全表扫描导致效率低下,建立索引
mysql> create index idx_email on customer(email);
再使用 explain 查看建立索引后的查询语句
mysql> explain select sum(amount) from customer a, payment b where 1=1 and a.customer_id = b.customer_id
and email = 'JANE.BENNETT@sakilacustomer.org';
使得扫描的行数大大减少,对于数据量大的场景来说大大提升访问数据库访问速度。
1.2 索引问题1.2.1 索引分类
B-Tree 索引:常用索引
HASH 索引:只有 Memory 引擎支持
R-Tree 索引:是 MyISAM 的一个特殊索引
Full-text :MyISAM 的一个特殊索引, InnoDB 从 MYSQL 5.6 开始支持全文索引前缀索引可以大大缩小索引文件的大小,缺点是 Order By 和 Group By 无法使用。
mysql> create index idx_title on film(title(10));
1.2.2 MYSQL 如何使用索引
B-Tee 代表了一个平衡树,并不是指二叉树。
一、索引使用场景:
(1)匹配全值
(2)匹配值的范围查询(3) 匹配最左前缀
添加复合索引
mysql> alter table payment add index idx_payment_date(payment_date, amount, last_update); mysql> explain select * from payment where payment_date = '2006-02-14 15:16:03' and last_update = '2006-02-15 22:12:32'; +----+-------------+---------+------+------------------+------------------+---------+-------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+------+------------------+------------------+---------+-------+------+-----------------------+ | 1 | SIMPLE | payment | ref | idx_payment_date | idx_payment_date | 5 | const | 182 | Using index condition | +----+-------------+---------+------+------------------+------------------+---------+-------+------+-----------------------+ 1 row in set (0.01 sec)
但是仅仅使用了其中两列则不会用到索引:
mysql> explain select * from payment where amount = 3.98 and last_update = '2006-02-15 22:12:32'; +----+-------------+---------+------+---------------+------+---------+------+-------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+------+---------------+------+---------+------+-------+-------------+ | 1 | SIMPLE | payment | ALL | NULL | NULL | NULL | NULL | 16086 | Using where | +----+-------------+---------+------+---------------+------+---------+------+-------+-------------+ 1 row in set (0.01 sec)
rows 数量变成 16086 行,
create index index_a_b_c on table_name(a,b,c);
2、select * from a = 'a' and b = 'b';
3、select * from a = 'a' and c = 'c';
4、select * from b = 'b' and c = 'c'
2、3 都可以使用联合索引,4、无法使用联合索引
(4)仅仅对索引进行查询(Index only query),如果查询的字段都包含在索引里面查询速度会更快
mysql> explain select last_update from payment where payment_date = '2006-02-14 15:16:03' and amount = 3.98 ; +----+-------------+---------+------+------------------+------------------+---------+-------------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+---------+------+------------------+------------------+---------+-------------+------+-------------+ | 1 | SIMPLE | payment | ref | idx_payment_date | idx_payment_date | 8 | const,const | 8 | Using index | +----+-------------+---------+------+------------------+------------------+---------+-------------+------+-------------+ 1 row in set (0.02 sec)
(5)匹配列前缀
mysql> create index idx_title_desc_part on film_text(title(10), description(20));
(6)能够实现索引匹配部分精确而其他部分进行范围匹配。
mysql> explain select inventory_id from rental where rental_date='2006-02-14 15:16:03' and customer_id >= 300 and customer_id <= 400;
(7)如果列名是索引,使用 column_name is null 就会使用索引
mysql> explain select * from payment where rental_id is null;
(8)MYSQL 5.6 引入了 Index Condition Pushdown (ICP)特性
二、存在索引但不能使用索引的典型场景(1) 以 % 开头的 LIKE 查询不能够利用 B-Tree 索引(2) 数据格式出现隐式转换时也不会使用索引,记住 `table_name` ,对于 'value' 加上 ''
(3) 复合索引查询条件不包含最左边的部分不会使用复合索引
(4) 如果 MYSQL 使用索引会更慢则不推荐使用索引
(5) 用 or 分割开的条件1.2.3 查看索引使用情况
参数值 Handler_read_key 的值
mysql> show status like 'Handler_read%'; +-----------------------+-------+ | Variable_name | Value | +-----------------------+-------+ | Handler_read_first | 5 | | Handler_read_key | 4 | | Handler_read_last | 0 | | Handler_read_next | 32148 | | Handler_read_prev | 0 | | Handler_read_rnd | 171 | | Handler_read_rnd_next | 468 | +-----------------------+-------+ 7 rows in set (0.02 sec)
1.3 两个简单实用的优化方法
1.3.1 定期分析表和检查表
mysql> analyze table payment;mysql> check table payment;
1.3.2 定期优化表
innodb 通过 alter table 进行优化
mysql> alter table payment engine=innodb;
mysql> optimize table payment;
optimize table 命令只对 MyISAM、BDB 和 InnoDB 表起作用。
1.4 常用SQL 优化
1.4.2 优化 INSERT 语句
insert delayed 语句数据都被放在内存中反之 insert low_priority
1.4.3 优化 order by 语句
mysql> show index from customer;
一、MYSQL 有两种排序方式
第一种通过有序索引顺序扫描直接返回有序数据, explain using index
,尽量减少额外的排序,通过索引直接返回有序数据,B+Tree 排序大于 > B-tree, 插入 B-tree 更好一些。
二、Filesort 的优化
两次扫面算法:
一次扫描算法:
尽量 select 只需要用到的数据,而不是 select *
1.4.4 优化 group by 语句
可以使用 order by null 禁止排序来提高性能,避免使用进行 Filesort
1.4.5 优化嵌套查询
子查询尽量使用 join (效率更高一些)
mysql> explain select * from customer a left join payment b on a.customer_id = b.customer_id where b.customer_id is null;
1.4.6 MYSQL 优化 or 条件
独立索引使用 or 查询会用到索引,两个复合索引使用 or 条件查询则不会使用到索引
1.4.7 优化分页查询
limit 1000,20 MYSQL 会查询出 1020 条记录,仅需要返回 1001 到 1020 条记录,
mysql> explain select film_id, description from film order by title limit 50,5;
(1)第一种优化思路
尽量避免使用 Filesort 全表扫描,可以使用嵌套查询 join 来使用索引提高效率
mysql> explain select a.film_id, a.description from film a inner join (select film_id from film order by title limit 50,5) b on a.film_id = b.film_id;
(2)第二种优化思路
翻页过程中通过增加一参数 last_page_record用来记录上一页最后一行的租赁号 rental_id
1.4.8 使用 SQL 提示
1、use index
mysql> explain select count(*) from rental use index(idx_rental_date);
2、ignore index
mysql> explain select count(*) from rental ignore index(idx_rental_date);
3、force index
mysql> explain select * from rental force index(idx_fk_inventory_id) where inventory_id>1;
1.5 常用 SQL 技巧
正则表达式的使用
rand() 提取随机行
group by 的 with rollup 语句
bit group functions
2、优化数据库对象
2.1 优化表的数据类型
使用 procedure analyse() 函数进行分析
mysql> select * from payment procedure analyse(); mysql> select * from payment procedure analyse(16, 256);
2.2 通过拆分提高表的访问效率
(1)第一种方法是垂直拆分,主码和一些列放到一个表,然后把主码和另外的列放到另一个表中。特别是对于某些列常用而另外一些列不常用的场景,优点:数据行变小,查询时减少 I/O 次数。缺点是需要管理冗余列,查询所有数据需要联合 (JOIN)操作。
(2)第二种是水平拆分,即根据一列或者多列数据的值把数据行放到两个独立的表中。例如,表中的数据本来就具有独立性,表中记录了各个地区或者各个时期不同的数据,特别是有些数据常用,而另外一些数据不常用。
2.3 逆规范化
1、一般来说只设置一个主键即可,外键一般不会设置通过代码来进行管理和维护。
2、常用的反规范化技术有增加冗余列、增加派生列、重新组表和分割表
3、需要维护数据的完整性,常用方法是批处理维护、应用逻辑和触发器。
2.4 使用中间表提高统计查询速度
(1)原表查询
(2)建立一个临时表,导入原表的所有数据。
insert into select 中的坑,同事埋了个坑:Insert into select语句把生产服务器炸了
数据库的优化需要在平时项目中不断积累,总结经验绝非看几本书就可以解决问题,和 JVM 调优一样需要实践,实践方能出新知。
更多关于技术、生活、思考的文章请欢迎来骚扰我的微信公众号:stormli