sql优化一般步骤概要:
show status
命令了解各种sql的执行频率explain
分析低效sql的执行计划show profile
分析sql以下阐述每步具体操作,请详细跟进操作,会有不错的收货!
show status
命令了解各种sql的执行频率mysql客户端连接成功后,通过show[session|global] status命令,可以查看服务器的状态,如果不加默认为session(session级为当前连接的统计结果,global级为自数据库上次启动到现在的统计结果)。
Eg: show status like 'Com_%';
…图示内容还有好多,不一一展示。
com_xx表示每个xx语句的执行次数,通常着重看curd操作次数(这些参数适用于所有存储引擎);
show status like 'Innodb_rows_%';
mysql> show processlist;
+------+------+-----------+------+---------+------+-------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+------+------+-----------+------+---------+------+-------+------------------+
| 1619 | root | localhost | wq | Query | 0 | init | show processlist |
+------+------+-----------+------+---------+------+-------+------------------+
1 row in set (0.00 sec)
explain
分析低效sql的执行计划追查到效率低的sql后,可以通过explain或desc命令获取mysql如何执行select语句的信息,包括在select语句执行过程中表如何连接和连接的顺序,eg:统计某个email为租赁电影拷贝所支付的总金额,需要关联客户表customer和付款表payment,并且对付款金额amount字段做求和操作,相应sql的执行计划如下:
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'\G
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'\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: atype: ALL
possible_keys: PRIMARYkey: NULLkey_len: NULLref: NULLrows: 599Extra: Using where
*************************** 2. row ***************************id: 1select_type: SIMPLEtable: btype: ref
possible_keys: idx_fk_customer_idkey: idx_fk_customer_idkey_len: 2ref: wq.a.customer_idrows: 13Extra: NULL
2 rows in set (0.00 sec)
下面对每个列进行简单说明:
select_type:表示select的类型,常见取值 simple(简单表,即不适用表连接或者子查询),primary(主查询,即外层的查询),union(union中的第二个或者后面的查询语句),subquery(子查询中的第一个select)等。
table:输出结果集的表。
possible_keys:表示查询时可能使用的索引,不表示出现在这里的就是表的全部索引。
key:表示实际使用的索引。
key_len:使用到索引字段的长度。
rows:扫描行的数量。
extra:执行情况的说明和描述,包含不适合在其他列中显示但是对执行计划非常重要的额外信息。
type:表示mysql在表中找到所需行的方式,或者叫访问类型,常见如下(性能由左至右由最差变道最好):all 、 index 、 range 、 ref、 eq_ref 、 const,system 、 null
补充:类型type还有其他的值:
ref_or_null:和ref类似,区别在于条件中包含对null的查询
index_merge:索引合并优化
unique_subquery:in的后面是一个查询主键字段的子查询
index_subquery:与unique_subquery类似,区别在于in的后面是查询非唯一索引字段的子查询。
我们可以试下大概什么情况会取type类型的值:
(1)type=all,表示全表扫描,mysql遍历全表来找到匹配的行:
mysql> explain select * from film where rating >9\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: filmtype: ALL
possible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: 1000Extra: Using where
1 row in set (0.00 sec)
(2)type=index ,索引全表扫描,mysql遍历整个索引来查询匹配的行:
mysql> explain select title from film\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: filmtype: index
possible_keys: NULLkey: idx_titlekey_len: 767ref: NULLrows: 1000Extra: Using index
1 row in set (0.00 sec)
(3)type&#61;range,索引范围扫描&#xff0c;常见于 <、 <&#61;、 >、 >&#61; 、 between 等操作符&#xff1a;
mysql> explain select * from payment where customer_id>&#61;300 and customer_id<&#61;350\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: paymenttype: range
possible_keys: idx_fk_customer_idkey: idx_fk_customer_idkey_len: 2ref: NULLrows: 1349Extra: Using index condition
1 row in set (0.00 sec)
(4) type&#61;ref ,使用非唯一索引扫描或唯一索引的前缀扫描&#xff0c;返回匹配某个单独值的记录行&#xff0c;eg:
mysql> EXPLAIN select * from payment where customer_id&#61;350\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: paymenttype: ref
possible_keys: idx_fk_customer_idkey: idx_fk_customer_idkey_len: 2ref: constrows: 23Extra: NULL
1 row in set (0.00 sec)
&#39;idx_fk_customer_id’索引是非唯一索引&#xff0c;查询条件为等值查询条件 customer_id&#61;35,所以扫描索引的类型为ref.ref还经常出现在join操作中&#xff1a;
如&#xff1a;
mysql> explain select a.*,b.* from payment a,customer b where a.customer_id&#61;b.customer_id\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: btype: ALL
possible_keys: PRIMARYkey: NULLkey_len: NULLref: NULLrows: 599Extra: NULL
*************************** 2. row ***************************id: 1select_type: SIMPLEtable: atype: ref
possible_keys: idx_fk_customer_idkey: idx_fk_customer_idkey_len: 2ref: wq.b.customer_idrows: 13Extra: NULL
2 rows in set (0.00 sec)
(5)type&#61;eq_ref ,类似ref ,区别就在使用的索引是唯一索引&#xff0c;对于每个索引键值&#xff0c;表中只有一条记录匹配&#xff1b;简单说即多表连接中使用primary key或者unique index作为关联条件。
mysql> explain select * from film a,film_text b where a.film_id&#61;b.film_id\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: btype: ALL
possible_keys: PRIMARYkey: NULLkey_len: NULLref: NULLrows: 1000Extra: NULL
*************************** 2. row ***************************id: 1select_type: SIMPLEtable: atype: eq_ref
possible_keys: PRIMARYkey: PRIMARYkey_len: 2ref: wq.b.film_idrows: 1Extra: Using where
2 rows in set (0.00 sec)
(6)TYPE&#61;const/system ,单表中最多有一个匹配行&#xff0c;查询起来非常迅速&#xff0c;所以这个匹配行中的其他列的值可以被优化器在当前查询中当做常量来处理&#xff0c;例如&#xff0c;根据主键primary key 或者唯一索引unique index进行的查询。下面构造一个查询&#xff1a;
mysql> alter table customer add unique index uk_email(email);
Query OK, 0 rows affected (0.10 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> explain select * from (select * from customer where email&#61;&#39;AARON.SELBY&#64;SAKILACUSTOMER.ORG&#39;)a\G
*************************** 1. row ***************************id: 1select_type: PRIMARYtable:
possible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: 1Extra: NULL
*************************** 2. row ***************************id: 2select_type: DERIVEDtable: customertype: const
possible_keys: uk_emailkey: uk_emailkey_len: 153ref: constrows: 1Extra: NULL
2 rows in set (0.00 sec)
mysql> explain select * from film_text b where b.film_id&#61;22\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: btype: const
possible_keys: PRIMARYkey: PRIMARYkey_len: 2ref: constrows: 1Extra: NULL
1 row in set (0.01 sec)
以上可以看出&#xff0c;通过唯一索引 uk_email访问的时候&#xff0c;类型为type&#61;const;而从只有一条记录的a表中查询数据时&#xff0c;类型type就为system。
(7)type&#61;null &#xff0c;mysql 不用访问表或者索引&#xff0c;直接能得到结果&#xff0c;eg:
mysql> explain select 1 from dual where 1\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: NULLtype: NULL
possible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: NULLExtra: No tables used
1 row in set (0.00 sec)
mysql4.1开始引入了explain extended命令&#xff0c;通过explain extended加上show warnings,可以看到sql真正被执行之后&#xff0c;优化器做了哪些sql改写&#xff1a;
mysql> explain extended select sum(amount) from customer a,payment b where 1&#61;1 and a.customer_id&#61;b.customer_id and email&#61;&#39;JANE.BENNETT&#64;sakilacustomer.org&#39;\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: atype: const
possible_keys: PRIMARY,uk_emailkey: uk_emailkey_len: 153ref: constrows: 1filtered: 100.00Extra: Using index
*************************** 2. row ***************************id: 1select_type: SIMPLEtable: btype: ref
possible_keys: idx_fk_customer_idkey: idx_fk_customer_idkey_len: 2ref: constrows: 28filtered: 100.00Extra: NULL
2 rows in set, 1 warning (0.00 sec)mysql> show warnings\G
*************************** 1. row ***************************Level: NoteCode: 1003
Message: /* select#1 */ select sum(&#96;wq&#96;.&#96;b&#96;.&#96;amount&#96;) AS &#96;sum(amount)&#96; from &#96;wq&#96;.&#96;customer&#96; &#96;a&#96; join &#96;wq&#96;.&#96;payment&#96; &#96;b&#96; where ((&#96;wq&#96;.&#96;b&#96;.&#96;customer_id&#96; &#61; &#39;77&#39;) and (&#39;JANE.BENNETT&#64;sakilacustomer.org&#39; &#61; &#39;JANE.BENNETT&#64;sakilacustomer.org&#39;))
1 row in set (0.00 sec)
以上结果可以看到&#xff1a;
mysql 5.1开始支持分区功能&#xff0c;同时explain命令也增加了分区的功能&#xff0c; 可以通过explain partitions的命令查看sql所访问的分区。eg:创建一个hash分区的customer_part表&#xff0c;根据分区键查询的时候&#xff0c;能够看到explain partitions的输出结果中有一列partitions,其中显示了sql所需要访问的分区名字 p2:
mysql> create table customer_part(customer_id smallint(5) unsigned not null auto_increment,primary key(customer_id))partition by hash(customer_id) partitions 8;
Query OK, 0 rows affected (0.26 sec)mysql> insert into customer_part select customer_id from customer;
Query OK, 599 rows affected (0.01 sec)
Records: 599 Duplicates: 0 Warnings: 0mysql> explain partitions select * from customer_part where customer_id&#61;130\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: customer_partpartitions: p2type: const
possible_keys: PRIMARYkey: PRIMARYkey_len: 2ref: constrows: 1Extra: Using index
1 row in set (0.00 sec)
但是有时只通过explain分析执行计划并不能很快定位sql问题&#xff0c;那么如果有profile联合分析就更好了。
show profile
分析sql从5.0.37版本开始增加了对show profiles 和show profile的语句支持。通过having_profiling参数&#xff0c;可以看到sql是否支持profile&#xff1a;
mysql> select &#64;&#64;have_profiling;
&#43;------------------&#43;
| &#64;&#64;have_profiling |
&#43;------------------&#43;
| YES |
&#43;------------------&#43;
1 row in set, 1 warning (0.00 sec)
默认profiling是关闭的&#xff0c;可以通过set语句在session级别开启profiling:
mysql> select &#64;&#64;profiling;
&#43;-------------&#43;
| &#64;&#64;profiling |
&#43;-------------&#43;
| 0 |
&#43;-------------&#43;
1 row in set, 1 warning (0.00 sec)
可以直接set其值为1
mysql> set profiling&#61;1;
Query OK, 0 rows affected, 1 warning (0.00 sec)
通过profile我们可以大致了解sql执行的过程。例如myisam表有表元数据的缓存&#xff08;比如行数&#xff0c;即count(*)
值&#xff09;&#xff0c;那么对一个myisam表的count(*)
是不需要消耗太多资源的&#xff0c;而对于innodb来说&#xff0c;就没有这种元数据缓存&#xff0c;count(*)
执行的较慢。下面来做个试验验证下。
首先&#xff0c;在一个innodb引擎的付款表payment上&#xff0c;执行一个count(*)查询&#xff1a;
mysql> select count(*) from payment;
&#43;----------&#43;
| count(*) |
&#43;----------&#43;
| 16049 |
&#43;----------&#43;
1 row in set (0.00 sec)
执行完毕后&#xff0c;通过show profiles语句&#xff0c;看到当前sql的query id为1&#xff1a;
mysql> show profiles;
&#43;----------&#43;------------&#43;------------------------------&#43;
| Query_ID | Duration | Query |
&#43;----------&#43;------------&#43;------------------------------&#43;
| 1 | 0.00372550 | select count(*) from payment |
&#43;----------&#43;------------&#43;------------------------------&#43;
1 row in set, 1 warning (0.00 sec)
通过show profile for query 1;语句能够看到执行过程中线程的每个状态和消耗的时间&#xff1a;
mysql> show profile for query 1;
&#43;----------------------&#43;----------&#43;
| Status | Duration |
&#43;----------------------&#43;----------&#43;
| starting | 0.000082 |
| checking permissions | 0.000008 |
| Opening tables | 0.000022 |
| init | 0.000017 |
| System lock | 0.000008 |
| optimizing | 0.000006 |
| statistics | 0.000014 |
| preparing | 0.000012 |
| executing | 0.000003 |
| Sending data | 0.003496 |
| end | 0.000007 |
| query end | 0.000006 |
| closing tables | 0.000010 |
| freeing items | 0.000011 |
| cleaning up | 0.000025 |
&#43;----------------------&#43;----------&#43;
15 rows in set, 1 warning (0.00 sec)
状态值为sending data表示mysql线程开始访问数据行并把结果返回给客户端之前&#xff0c;mysql线程要做大良的磁盘读取操作&#xff0c;所以会导致在整个查询中耗时最长的状态。
通过执行show profile for query,可以看到在执行count(*)操作的过程中&#xff0c;时间耗费在状态值为sending data 上&#xff0c;通过查询 information_schema.profiling表并按照时间做个desc排序&#xff1a;
mysql> select state,sum(duration) as total_r,round(100*sum(duration)/(select sum(duration) from information_schema.profiling where query_id&#61;&#64;query_id),2) as pct_r,count(*) as calls,sum(duration)/count(*) as "R/Call" from information_schema.profiling where query_id&#61;&#64;query_id group by state order by total_r desc;
&#43;----------------------&#43;----------&#43;-------&#43;-------&#43;--------------&#43;
| state | total_r | pct_r | calls | R/Call |
&#43;----------------------&#43;----------&#43;-------&#43;-------&#43;--------------&#43;
| Sending data | 0.003496 | 93.80 | 1 | 0.0034960000 |
| starting | 0.000082 | 2.20 | 1 | 0.0000820000 |
| cleaning up | 0.000025 | 0.67 | 1 | 0.0000250000 |
| Opening tables | 0.000022 | 0.59 | 1 | 0.0000220000 |
| init | 0.000017 | 0.46 | 1 | 0.0000170000 |
| statistics | 0.000014 | 0.38 | 1 | 0.0000140000 |
| preparing | 0.000012 | 0.32 | 1 | 0.0000120000 |
| freeing items | 0.000011 | 0.30 | 1 | 0.0000110000 |
| closing tables | 0.000010 | 0.27 | 1 | 0.0000100000 |
| System lock | 0.000008 | 0.21 | 1 | 0.0000080000 |
| checking permissions | 0.000008 | 0.21 | 1 | 0.0000080000 |
| end | 0.000007 | 0.19 | 1 | 0.0000070000 |
| query end | 0.000006 | 0.16 | 1 | 0.0000060000 |
| optimizing | 0.000006 | 0.16 | 1 | 0.0000060000 |
| executing | 0.000003 | 0.08 | 1 | 0.0000030000 |
&#43;----------------------&#43;----------&#43;-------&#43;-------&#43;--------------&#43;
15 rows in set (0.00 sec)
获取到最消耗时间的线程状态后&#xff0c;mysql进一步选择all,cpu,block io,context switch,page faults等明细类型来查看mysql在使用什么资源上耗费了过高的时间&#xff0c;例如&#xff0c;选择查看cpu的耗费时间&#xff1a;
mysql> show profile cpu for query 1;
&#43;----------------------&#43;----------&#43;----------&#43;------------&#43;
| Status | Duration | CPU_user | CPU_system |
&#43;----------------------&#43;----------&#43;----------&#43;------------&#43;
| starting | 0.000065 | 0.000027 | 0.000028 |
| checking permissions | 0.000007 | 0.000003 | 0.000004 |
| Opening tables | 0.000021 | 0.000011 | 0.000011 |
| init | 0.000014 | 0.000007 | 0.000007 |
| System lock | 0.000009 | 0.000004 | 0.000005 |
| optimizing | 0.000007 | 0.000003 | 0.000003 |
| statistics | 0.000013 | 0.000007 | 0.000007 |
| preparing | 0.000012 | 0.000005 | 0.000006 |
| executing | 0.000003 | 0.000002 | 0.000002 |
| Sending data | 0.003683 | 0.003684 | 0.000000 |
| end | 0.000007 | 0.000006 | 0.000000 |
| query end | 0.000006 | 0.000006 | 0.000000 |
| closing tables | 0.000009 | 0.000009 | 0.000000 |
| freeing items | 0.000010 | 0.000010 | 0.000000 |
| cleaning up | 0.000014 | 0.000014 | 0.000000 |
&#43;----------------------&#43;----------&#43;----------&#43;------------&#43;
15 rows in set, 1 warning (0.00 sec)
可想而知&#xff0c;sending data状态值大的原因为时间主要耗费在cpu的操作上了&#xff0c;对比myisam表的count(*)操作&#xff0c;也创建一个同样表结构的myisam表&#xff0c;数据量也完全一致。
mysql> create table payment_myisam like payment;
Query OK, 0 rows affected (0.04 sec)mysql> alter table payment_myisam engine&#61;myisam;
Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0mysql> insert into payment_myisam select * from payment;
Query OK, 16049 rows affected (0.08 sec)
Records: 16049 Duplicates: 0 Warnings: 0
执行count(*),显示profile:
mysql> select count(*) from payment_myisam;
&#43;----------&#43;
| count(*) |
&#43;----------&#43;
| 16049 |
&#43;----------&#43;
1 row in set (0.00 sec)mysql> show profiles;
&#43;----------&#43;------------&#43;--------------------------------------------------------------------------------------------------------------------------------------&#43;
| Query_ID | Duration | Query |
&#43;----------&#43;------------&#43;--------------------------------------------------------------------------------------------------------------------------------------&#43;
| 1 | 0.00016550 | select &#64;&#64;have_profiling |
| 2 | 0.00387925 | select count(*) from payment |
| 3 | 0.00005900 | show profile context switch for query 2 |
| 4 | 0.00007175 | create table payment_myisam lkike payment |
| 5 | 0.04542150 | create table payment_myisam like payment |
| 6 | 0.03682200 | alter table payment_myisam engine&#61;myisam |
| 7 | 0.08007525 | insert into payment_myisam select * from payment |
| 8 | 0.00005425 | mysql> create table payment_myisam like payment |
| 9 | 0.00005275 | Query OK, 0 rows affected (0.04 sec)mysql> alter table payment_myisam engine&#61;myisam |
| 10 | 0.00005300 | Query OK, 0 rows affected (0.04 sec)
Records: 0 Duplicates: 0 Warnings: 0mysql> insert into payment_myisam select * from payment |
| 11 | 0.00006350 | Query OK, 16049 rows affected (0.08 sec)
Records: 16049 Duplicates: 0 Warnings: 0 |
| 12 | 0.00016450 | select count(*) from payment_myisam |
&#43;----------&#43;------------&#43;--------------------------------------------------------------------------------------------------------------------------------------&#43;
12 rows in set, 1 warning (0.00 sec)mysql> show profile for query 12;
&#43;----------------------&#43;----------&#43;
| Status | Duration |
&#43;----------------------&#43;----------&#43;
| starting | 0.000063 |
| checking permissions | 0.000007 |
| Opening tables | 0.000020 |
| init | 0.000014 |
| System lock | 0.000009 |
| optimizing | 0.000008 |
| executing | 0.000010 |
| end | 0.000004 |
| query end | 0.000004 |
| closing tables | 0.000009 |
| freeing items | 0.000008 |
| cleaning up | 0.000012 |
&#43;----------------------&#43;----------&#43;
12 rows in set, 1 warning (0.00 sec)
由上面两种引擎所建表的count(*)操作后&#xff0c;执行profile可看出&#xff0c;innodb引擎的表由sending data状态&#xff0c;存在访问数据即磁盘读取的过程&#xff0c;时间主要耗费在cpu上&#xff0c;而myisam引擎的表在executing之后就结束查询&#xff0c;意味着不需要去访问数据。
如果对mysql源码感兴趣&#xff0c;可以通过show profile source for query
查看sql解析执行过程中的每个步骤对应的源码文件、函数名称、具体源文件行数&#xff1a;
mysql> show profile source for query 1;
&#43;----------------------&#43;----------&#43;-----------------------&#43;------------------&#43;-------------&#43;
| Status | Duration | Source_function | Source_file | Source_line |
&#43;----------------------&#43;----------&#43;-----------------------&#43;------------------&#43;-------------&#43;
| starting | 0.000065 | NULL | NULL | NULL |
| checking permissions | 0.000007 | check_access | sql_parse.cc | 5350 |
| Opening tables | 0.000021 | open_tables | sql_base.cc | 5095 |
| init | 0.000014 | mysql_prepare_select | sql_select.cc | 1051 |
| System lock | 0.000009 | mysql_lock_tables | lock.cc | 304 |
| optimizing | 0.000007 | optimize | sql_optimizer.cc | 139 |
| statistics | 0.000013 | optimize | sql_optimizer.cc | 365 |
| preparing | 0.000012 | optimize | sql_optimizer.cc | 488 |
| executing | 0.000003 | exec | sql_executor.cc | 110 |
| Sending data | 0.003683 | exec | sql_executor.cc | 190 |
| end | 0.000007 | mysql_execute_select | sql_select.cc | 1106 |
| query end | 0.000006 | mysql_execute_command | sql_parse.cc | 5049 |
| closing tables | 0.000009 | mysql_execute_command | sql_parse.cc | 5097 |
| freeing items | 0.000010 | mysql_parse | sql_parse.cc | 6486 |
| cleaning up | 0.000014 | dispatch_command | sql_parse.cc | 1815 |
&#43;----------------------&#43;----------&#43;-----------------------&#43;------------------&#43;-------------&#43;
15 rows in set, 1 warning (0.00 sec)
show profile可以在sql优化时告诉我们时间主要浪费在哪了。
mysql5.6是通过trace文件进一步告诉我们优化器是如何选择执行计划的。
&#xff08;即mysql5.6提供了对sql的跟踪trace文件&#xff0c;从而得知优化器为何选择a执行计划而不选择b执行计划&#xff0c;有助于我们理解优化器的行为。&#xff09;
首先打开trace,设置格式为json,设置trace最大能够使用内存的大小&#xff0c;避免解析过程中因为默认内存过小不能完全显示&#xff1a;
mysql> set optimizer_trace&#61;"enabled&#61;on" ,end_markers_in_json&#61;on;
Query OK, 0 rows affected (0.00 sec)mysql> set optimizer_trace_max_mem_size&#61;1000000;
Query OK, 0 rows affected (0.00 sec)
然后执行下想做追踪的sql语句&#xff0c;eg:租赁表中rental的库存编号inventory_id为4466的电影拷贝 &#xff0c;出租日期rental_date 在2005-05-25 4:00:00~5:00:00范围内的出租记录&#xff0c;最后通过检查语句select * from information_schema.optimizer_trace
就可以知道mysql如何执行sql的
mysql> select rental_id from rental where 1&#61;1 and rental_date>&#61;&#39;2005-05-25 04:00:00&#39; and rental_date <&#61;&#39;2005-05-25 05:00:00&#39; and inventory_id&#61;4466;
&#43;-----------&#43;
| rental_id |
&#43;-----------&#43;
| 39 |
&#43;-----------&#43;
1 row in set (0.00 sec)mysql> select * from information_schema.optimizer_trace\G
*************************** 1. row ***************************QUERY: select rental_id from rental where 1&#61;1 and rental_date>&#61;&#39;2005-05-25 04:00:00&#39; and rental_date <&#61;&#39;2005-05-25 05:00:00&#39; and inventory_id&#61;4466TRACE: {"steps": [{"join_preparation": {"select#": 1,"steps": [{"expanded_query": "/* select#1 */ select &#96;rental&#96;.&#96;rental_id&#96; AS &#96;rental_id&#96; from &#96;rental&#96; where ((1 &#61; 1) and (&#96;rental&#96;.&#96;rental_date&#96; >&#61; &#39;2005-05-25 04:00:00&#39;) and (&#96;rental&#96;.&#96;rental_date&#96; <&#61; &#39;2005-05-25 05:00:00&#39;) and (&#96;rental&#96;.&#96;inventory_id&#96; &#61; 4466))"}] /* steps */} /* join_preparation */},{"join_optimization": {"select#": 1,"steps": [{"condition_processing": {"condition": "WHERE","original_condition": "((1 &#61; 1) and (&#96;rental&#96;.&#96;rental_date&#96; >&#61; &#39;2005-05-25 04:00:00&#39;) and (&#96;rental&#96;.&#96;rental_date&#96; <&#61; &#39;2005-05-25 05:00:00&#39;) and (&#96;rental&#96;.&#96;inventory_id&#96; &#61; 4466))","steps": [{"transformation": "equality_propagation","resulting_condition": "((1 &#61; 1) and (&#96;rental&#96;.&#96;rental_date&#96; >&#61; &#39;2005-05-25 04:00:00&#39;) and (&#96;rental&#96;.&#96;rental_date&#96; <&#61; &#39;2005-05-25 05:00:00&#39;) and multiple equal(4466, &#96;rental&#96;.&#96;inventory_id&#96;))"},{"transformation": "constant_propagation","resulting_condition": "((1 &#61; 1) and (&#96;rental&#96;.&#96;rental_date&#96; >&#61; &#39;2005-05-25 04:00:00&#39;) and (&#96;rental&#96;.&#96;rental_date&#96; <&#61; &#39;2005-05-25 05:00:00&#39;) and multiple equal(4466, &#96;rental&#96;.&#96;inventory_id&#96;))"},{"transformation": "trivial_condition_removal","resulting_condition": "((&#96;rental&#96;.&#96;rental_date&#96; >&#61; &#39;2005-05-25 04:00:00&#39;) and (&#96;rental&#96;.&#96;rental_date&#96; <&#61; &#39;2005-05-25 05:00:00&#39;) and multiple equal(4466, &#96;rental&#96;.&#96;inventory_id&#96;))"}] /* steps */} /* condition_processing */},{"table_dependencies": [{"table": "&#96;rental&#96;","row_may_be_null": false,"map_bit": 0,"depends_on_map_bits": [] /* depends_on_map_bits */}] /* table_dependencies */},{"ref_optimizer_key_uses": [{"table": "&#96;rental&#96;","field": "inventory_id","equals": "4466","null_rejecting": false}] /* ref_optimizer_key_uses */},{"rows_estimation": [{"table": "&#96;rental&#96;","range_analysis": {"table_scan": {"rows": 16008,"cost": 3300.7} /* table_scan */,"potential_range_indices": [{"index": "PRIMARY","usable": false,"cause": "not_applicable"},{"index": "rental_date","usable": true,"key_parts": ["rental_date","inventory_id","customer_id"] /* key_parts */},{"index": "idx_fk_inventory_id","usable": true,"key_parts": ["inventory_id","rental_id"] /* key_parts */},{"index": "idx_fk_customer_id","usable": false,"cause": "not_applicable"},{"index": "idx_fk_staff_id","usable": false,"cause": "not_applicable"}] /* potential_range_indices */,"best_covering_index_scan": {"index": "rental_date","cost": 3229.9,"chosen": true} /* best_covering_index_scan */,"setup_range_conditions": [] /* setup_range_conditions */,"group_index_range": {"chosen": false,"cause": "not_group_by_or_distinct"} /* group_index_range */,"analyzing_range_alternatives": {"range_scan_alternatives": [{"index": "rental_date","ranges": ["2005-05-25 04:00:00 <&#61; rental_date <&#61; 2005-05-25 05:00:00"] /* ranges */,"index_dives_for_eq_ranges": true,"rowid_ordered": false,"using_mrr": false,"index_only": true,"rows": 10,"cost": 3.0254,"chosen": true},{"index": "idx_fk_inventory_id","ranges": ["4466 <&#61; inventory_id <&#61; 4466"] /* ranges */,"index_dives_for_eq_ranges": true,"rowid_ordered": true,"using_mrr": false,"index_only": false,"rows": 5,"cost": 7.01,"chosen": false,"cause": "cost"}] /* range_scan_alternatives */,"analyzing_roworder_intersect": {"usable": false,"cause": "too_few_roworder_scans"} /* analyzing_roworder_intersect */} /* analyzing_range_alternatives */,"chosen_range_access_summary": {"range_access_plan": {"type": "range_scan","index": "rental_date","rows": 10,"ranges": ["2005-05-25 04:00:00 <&#61; rental_date <&#61; 2005-05-25 05:00:00"] /* ranges */} /* range_access_plan */,"rows_for_plan": 10,"cost_for_plan": 3.0254,"chosen": true} /* chosen_range_access_summary */} /* range_analysis */}] /* rows_estimation */},{"considered_execution_plans": [{"plan_prefix": [] /* plan_prefix */,"table": "&#96;rental&#96;","best_access_path": {"considered_access_paths": [{"access_type": "ref","index": "idx_fk_inventory_id","rows": 5,"cost": 6,"chosen": true},{"access_type": "range","rows": 5,"cost": 5.0254,"chosen": true}] /* considered_access_paths */} /* best_access_path */,"cost_for_plan": 5.0254,"rows_for_plan": 5,"chosen": true}] /* considered_execution_plans */},{"attaching_conditions_to_tables": {"original_condition": "((&#96;rental&#96;.&#96;inventory_id&#96; &#61; 4466) and (&#96;rental&#96;.&#96;rental_date&#96; >&#61; &#39;2005-05-25 04:00:00&#39;) and (&#96;rental&#96;.&#96;rental_date&#96; <&#61; &#39;2005-05-25 05:00:00&#39;))","attached_conditions_computation": [] /* attached_conditions_computation */,"attached_conditions_summary": [{"table": "&#96;rental&#96;","attached": "((&#96;rental&#96;.&#96;inventory_id&#96; &#61; 4466) and (&#96;rental&#96;.&#96;rental_date&#96; >&#61; &#39;2005-05-25 04:00:00&#39;) and (&#96;rental&#96;.&#96;rental_date&#96; <&#61; &#39;2005-05-25 05:00:00&#39;))"}] /* attached_conditions_summary */} /* attaching_conditions_to_tables */},{"refine_plan": [{"table": "&#96;rental&#96;","access_type": "range"}] /* refine_plan */}] /* steps */} /* join_optimization */},{"join_execution": {"select#": 1,"steps": [] /* steps */} /* join_execution */}] /* steps */
}
MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 0INSUFFICIENT_PRIVILEGES: 0
1 row in set (0.00 sec)
这里缺一段讲解&#xff0c;稍后补上
经过以上操作&#xff0c;基本可以定位问题出现的原因。此时可以根据实际情况采取相应措施&#xff0c;通过优化来提高执行的效率。比如在第三点提到的低效sql的执行计划&#xff0c;已经确认是对客户表customer的权标扫描导致效率不理想&#xff0c;我们可以通过对email创建索引来提高效率。
mysql> create index idx_email on customer(email);
Query OK, 0 rows affected, 1 warning (0.05 sec)
Records: 0 Duplicates: 0 Warnings: 1mysql> explain select sum(amount) from customer a ,payment b where 1&#61;1 and a.customer_id&#61;b.customer_id and email &#61;&#39;JANE.BENNETT&#64;SAKILACUSTOMER.ORG&#39;\G
*************************** 1. row ***************************id: 1select_type: SIMPLEtable: atype: const
possible_keys: PRIMARY,uk_email,idex_email,idx_emailkey: uk_emailkey_len: 153ref: constrows: 1Extra: Using index
*************************** 2. row ***************************id: 1select_type: SIMPLEtable: btype: ref
possible_keys: idx_fk_customer_idkey: idx_fk_customer_idkey_len: 2ref: constrows: 28Extra: NULL
2 rows in set (0.00 sec)
对比第3点&#xff0c;可以看出建立索引对customer表需要扫描的行数rows变少,性能有所提升&#xff08;根据类型判断性能&#xff1a;由all变为const&#xff09;,可见索引的使用可以大大提高数据库的访问速度&#xff0c;尤其表越大效率越明显。