MySQL索引道理以及优化
前言
本文是美团一名大佬写的,还不错拿出来和大家分享下,代码中嵌套在html中sql语句是java框架的写法,了解其sql要施行的语句即可。
配景
MySQL凭借着精彩的机能、低廉的老本、丰硕的资源,已经成为绝大多数互联网企业的首选关系型数据库。虽然机能精彩,但所谓“好马配好鞍”,怎样能够更好的运用它,已经成为开发工程师的必修课,我们时常会从职位描述上看到诸如“精通MySQL”、“SQL语句优化”、“理解数据库道理”等请求。我们晓得个别的利用系统,读写比例在10:1摆布,并且插入操纵和个别的更新操纵很少涌现机能题目,碰到最多的,也是最容易出题目的,还是一些复杂的查询操纵,所以查询语句的优化显然是重中之重。
自己从13年7月份起,不断在美团中心业务系统部做慢查询的优化工作,共计十余个系统,累计解决和积攒了上百个慢查询案例。随着业务的复杂性提拔,碰到的题目离奇曲折,多种多样,匪夷所思。本文旨在以开发工程师的角度来解释数据库索引的道理和怎样优化慢查询。
select
count(*) from
task
where
status=2
and operator_id=20839
and operate_time>1371169729
and operate_time<1371174603
and type=2;
系统运用者反馈有一个功能越来越慢,于是工程师寻到了上面的SQL。
而且兴致冲冲的寻到了我,“这个SQL需要优化,给我把每个字段都加上索引”。
我很惊叹,问道:“为何需要每个字段都加上索引?”
“把查询的字段都加上索引会更快”,工程师信念满满。
“这种状况完全可以建一个结合索引,由于是最左前缀匹配,所以operate_time需要放到最后,并且还需要把其他相干的查询都拿来,需要做一个综合评估。”
“结合索引?最左前缀匹配?综合评估?”工程师不禁堕入了沉思。
多数状况下,我们晓得索引能够提高查询效率,但应当怎样创立索引?索引的次序怎样?很多人却只晓得大约。其实了解这些概念并不难,并且索引的道理远没有想象的那么复杂。
索引目的
索引的目的在于提高查询效率,可以类比字典,要是要查“mysql”这个单词,我们确定需要定位到m字母,然后从下往下寻到y字母,再寻到剩下的sql。要是没有索引,那么你可能需要把所有单词看一遍才干寻到你想要的,要是我想寻到m开头的单词呢?或者ze开头的单词呢?是不是觉得要是没有索引,这个事情基本没法完成?
索引道理
除了词典,生活中随处可见索引的例子,如火车站的车次表、图书的名目等。它们的道理都是同样的,通过一直的缩小想要获得数据的范畴来筛选出终究想要的效果,同时把随机的事件酿成次序的事件,也就是我们总是通过统一种查寻方式来锁定数据。
数据库也是同样,但显然要复杂很多,由于不仅面对着等值查询,还有范畴查询(>、<、between、in)、模糊查询(like)、并集查询(or)等等。数据库应该选择怎么样的方式来应对所有的问题呢?我们回想字典的例子,能不能把数据分成段,然后分段查询呢?最简单的如果1000条数据,1到100分成第一段,101到200分成第二段,201到300分成第三段……这样查第250条数据,只要找第三段就可以了,一下子去除了90%的无效数据。但如果是1千万的记录呢,分成几段比较好?稍有算法基础的同学会想到搜索树,其平均复杂度是lgN,具有不错的查询性能。但这里我们忽略了一个关键的问题,复杂度模型是基于每次相同的操作成本来考虑的,数据库实现比较复杂,数据保存在磁盘上,而为了提高性能,每次又可以把部分数据读入内存来计算,因为我们知道访问磁盘的成本大概是访问内存的十万倍左右,所以简单的搜索树难以满足复杂的应用场景。
磁盘IO与预读
前面提到了拜访磁盘,那么这里先简略介绍一下磁盘IO和预读,磁盘读取数据靠的是机械运动,每次读取数据破费的工夫可以分为找道工夫、扭转推迟、传输工夫三个局部,找道工夫指的是磁臂挪移到指定磁道所需要的工夫,主流磁盘个别在5ms下列;扭转推迟就是我们时常据说的磁盘转速,比方一个磁盘7200转,表示每分钟能转7200次,也就是说1秒钟能转120次,扭转推迟就是1/120/2 = 4.17ms;传输工夫指的是从磁盘读出或将数据写入磁盘的工夫,个别在零点几毫秒,相关于前两个工夫可以忽略不计。那么拜访一次磁盘的工夫,即一次磁盘IO的工夫约等于5+4.17 = 9ms摆布,听起来还挺不错的,但要晓得一台500 -MIPS的机器每秒可以施行5亿条指令,由于指令依托的是电的性质,换句话说施行一次IO的工夫可以施行40万条指令,数据库动辄十万百万以致千万级数据,每次9毫秒的工夫,显然是个苦难。下图是盘算机硬件推迟的对照图,供大家参照 :
b+树
如上图,是一颗b+树,对于b+树的定义可以拜见B+树,这里只说一些重点,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包括几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包括数据项17和35,包括指针P1、P2、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非叶子节点只不存储真实的数据,只存储指引搜寻标的目的的数据项,如17、35并不真实存在于数据表中。
b+树的查寻历程
如图所示,要是要查寻数据项29,那么第一会把磁盘块1由磁盘加载到内存,此时产生一次IO,在内存中用二分查寻肯定29在17和35之间,锁定磁盘块1的P2指针,内存工夫由于非常短(比拟磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,产生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,产生第三次IO,同时内存中做二分查寻寻到29,完毕查询,总计三次IO。真实的状况是,3层的b+树可以表示上百万的数据,要是上百万的数据查寻只需要三次IO,机能提高将是庞大的,要是没有索引,每个数据项都要产生一次IO,那么总共需要百万次的IO,显然老本非常非常高。
b+树性质
通过上面的剖析,我们晓得IO次数取决于b+数的高度h,假如目前数据表的数据为N,每个磁盘块的数据项的数目是m,则有h=㏒(m+1)N,当数据量N一定的状况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是牢固的,要是数据项占的空间越小,数据项的数目越多,树的高度越低。这就是为何每个数据项,即索引字段要尽量的小,比方int占4字节,要比bigint8字节少一半。这也是为何b+树请求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下落,致使树增高。当数据项等于1时将会退化成线性表。
当b+树的数据项是复合的数据构造,比方(name,age,sex)的时候,b+数是按照从左到右的次序来创立搜寻树的,比方当(张三,20,F)这样的数据来检索的时候,b+树会优先比拼name来肯定下一步的所搜标的目的,要是name雷同再顺次比拼age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不晓得下一步该查哪个节点,由于创立搜寻树的时候name就是首先个比拼因子,必需要先依据name来搜寻才干晓得下一步去哪里查询。比方当(张三,F)这样的数据来检索时,b+树可以用name来指定搜寻标的目的,但下一个字段age的缺失,所以只能把名字等于张三的数据都寻到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。
对于MySQL索引道理是比拼干燥的东西,大家只需要有一个感性的相识,并不需要了解得非常透辟和深入。我们回首来看看一开端我们说的慢查询,理解完索引道理之后,大家是不是有什么设法呢?先总结一下索引的几大根本准则:
建索引的几大准则
- 最左前缀匹配准则,非常重要的准则,mysql会不断向右匹配直到碰到范畴查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 要是创立(a,b,c,d)次序的索引,d是用不到索引的,要是创立(a,b,d,c)的索引则都可以用到,a,b,d的次序可以任意调整。
- =和in可以乱序,比方a = 1 and b = 2 and c = 3 创立(a,b,c)索引可以任意次序,mysql的查询优化器会帮你优化成索引可以辨认的情势。
- 尽量选中区分度高的列作为索引,区分度的公式是count(distinct col)/count(*),表示字段不反复的比例,比例越大我们扫描的记载数越少,独一键的区分度是1,而一些状态、性别字段可能在大数据眼前区分度就是0,那可能有人会问,这个比例有什么经验值吗?运用场景不一样,这个值也很难肯定,个别需要join的字段我们都请求是0.1以上,即均匀1条扫描10笔记录。
- 索引列不克不及参与盘算,维持列“洁净”,比方from_unixtime(create_time) = ’2014-05-29’就不克不及运用到索引,缘由很简略,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都利用函数才干比拼,显然老本太大。所以语句应当写成create_time = unix_timestamp(’2014-05-29’)。
- 尽量的扩展索引,不要创建索引。比方表中已经有a的索引,此刻要加(a,b)的索引,那么只需要修改本来的索引即可。
回到开端的慢查询
依据最左匹配准则,最开端的sql语句的索引应当是status、operator_id、type、operate_time的结合索引;其中status、operator_id、type的次序可以颠倒,所以我才会说,把这个表的所有相干查询都寻到,会综合剖析; 比方还有如下查询:
select * from task where status = 0 and type = 12 limit 10;
select count(*) from task where status = 0 ;
那么索引创立成(status,type,operator_id,operate_time)就是非常准确的,由于可以遮盖到所有状况。这个就是应用了索引的最左匹配的准则
查询优化神器 – explain下令
对于explain下令信赖大家并不生疏,概括用途和字段含义可以参照 官网explain-output,这里需要强调rows是中心目标,绝大局部rows小的语句施行一定很快(有例外,下面会讲到)。所以优化语句根本上都是在优化rows。
慢查询优化根本步骤
- 先运转看看可否真的很慢,注意设定SQL_NO_CACHE
- where前提单表查,锁定最小返回记载表。这句话的意思是把查询语句的where都利用到表中返回的记载数最小的表开端查起,单表每个字段离别查询,看哪个字段的区分度最高
- explain查看施行规划,可否与1预测一致(从锁定记载较少的表开端查询)
- order by limit 情势的sql语句让排序的表优先查
- 理解业务方运用场景
- 加索引时参照建索引的几大准则
- 调查效果,不相符预测继续从0剖析
几个慢查询案例
下面几个例子细致解释了怎样剖析和优化慢查询。
复杂语句写法
许多状况下,我们写SQL只是为了实现功能,这只是首先步,不一样的语句书写方式关于效率往往有本质的差别,这请求我们对mysql的施行规划和索引准则有非常分明的相识,请看下面的语句:
select
distinct cert.emp_id
from
cm_log cl
inner join
(
select
emp.id as emp_id,
emp_cert.id as cert_id
from
employee emp
left join
emp_certificate emp_cert
on emp.id = emp_cert.emp_id
where
emp.is_deleted=0
) cert
on (
cl.ref_table='Employee'
and cl.ref_oid= cert.emp_id
)
or (
cl.ref_table='EmpCertificate'
and cl.ref_oid= cert.cert_id
)
where
cl.last_upd_date >='2013-11-07 15:03:00'
and cl.last_upd_date<='2013-11-08 16:00:00';
- 先运转一下,53笔记录 1.87秒,又没实用聚合语句,比拼慢
53 rows in set (1.87 sec)
- explain
+----+-------------+------------+-------+---------------------------------+-----------------------+---------+-------------------+-------+--------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+-------+---------------------------------+-----------------------+---------+-------------------+-------+--------------------------------+
| 1 | PRIMARY | cl | range | cm_log_cls_id,idx_last_upd_date | idx_last_upd_date | 8 | NULL | 379 | Using where; Using temporary |
| 1 | PRIMARY | | ALL | NULL | NULL | NULL | NULL | 63727 | Using where; Using join buffer |
| 2 | DERIVED | emp | ALL | NULL | NULL | NULL | NULL | 13317 | Using where |
| 2 | DERIVED | emp_cert | ref | emp_certificate_empid | emp_certificate_empid | 4 | meituanorg.emp.id | 1 | Using index |
+----+-------------+------------+-------+---------------------------------+-----------------------+---------+-------------------+-------+--------------------------------+
简述一下施行规划,第一mysql依据idx_last_upd_date索引扫描cm_log表获得379笔记录;然后查表扫描了63727笔记录,分为两局部,derived表示结构表,也就是不存在的表,可以简略了解成是一个语句构成的效果集,背面的数字表示语句的ID。derived2表示的是ID = 2的查询结构了虚拟表,而且返回了63727笔记录。我们再来看看ID = 2的语句毕竟做了写什么返回了这么批量的数据,第一全表扫描employee表13317笔记录,然后依据索引emp_certificate_empid关联emp_certificate表,rows = 1表示,每个关联都只锁定了一笔记录,效率比拼高。获得后,再和cm_log的379笔记录依据法则关联。从施行历程上可以看出返回了太多的数据,返回的数据绝大局部cm_log都用不到,由于cm_log只锁定了379笔记录。
怎样优化呢?可以看到我们在运转完后还是要和cm_log做join,那么我们能不克不及以前和cm_log做join呢?细心剖析语句不难发明,其根本思想是要是cm_log的ref_table是EmpCertificate就关联emp_certificate表,要是ref_table是Employee就关联employee表,我们完全可以拆成两局部,并用union连贯起来,注意这里用union,而不消union all是由于原语句有“distinct”来得到独一的记载,而union刚好具备了这种功能。要是原语句中没有distinct不需要去重,我们就可以直接运用union all了,由于运用union需要去重的行动,会影响SQL机能。
优化过的语句如下:
select
emp.id
from
cm_log cl
inner join
employee emp
on cl.ref_table = 'Employee'
and cl.ref_oid = emp.id
where
cl.last_upd_date >='2013-11-07 15:03:00'
and cl.last_upd_date<='2013-11-08 16:00:00'
and emp.is_deleted = 0
union
select
emp.id
from
cm_log cl
inner join
emp_certificate ec
on cl.ref_table = 'EmpCertificate'
and cl.ref_oid = ec.id
inner join
employee emp
on emp.id = ec.emp_id
where
cl.last_upd_date >='2013-11-07 15:03:00'
and cl.last_upd_date<='2013-11-08 16:00:00'
and emp.is_deleted = 0
不需要理解业务场景,只需要革新的语句和革新以前的语句维持效果一致
现有索引可以知足,不需要建索引
用革新后的语句实验一下,只需要10ms 落低了近200倍!
+----+--------------+------------+--------+---------------------------------+-------------------+---------+-----------------------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+--------------+------------+--------+---------------------------------+-------------------+---------+-----------------------+------+-------------+ | 1 | PRIMARY | cl | range | cm_log_cls_id,idx_last_upd_date | idx_last_upd_date | 8 | NULL | 379 | Using where | | 1 | PRIMARY | emp | eq_ref | PRIMARY | PRIMARY | 4 | meituanorg.cl.ref_oid | 1 | Using where | | 2 | UNION | cl | range | cm_log_cls_id,idx_last_upd_date | idx_last_upd_date | 8 | NULL | 379 | Using where | | 2 | UNION | ec | eq_ref | PRIMARY,emp_certificate_empid | PRIMARY | 4 | meituanorg.cl.ref_oid | 1 | | | 2 | UNION | emp | eq_ref | PRIMARY | PRIMARY | 4 | meituanorg.ec.emp_id | 1 | Using where | | NULL | UNION RESULT |
| ALL | NULL | NULL | NULL | NULL | NULL | | +----+--------------+------------+--------+---------------------------------+-------------------+---------+-----------------------+------+-------------+ 53 rows in set (0.01 sec)
明白利用场景
举这个例子的目的在于颠覆我们对列的区分度的认知,个别上我们以为区分度越高的列,越容易锁定更少的记载,但在一些特别的状况下,这种理论是有局限性的。
select
*
from
stage_poi sp
where
sp.accurate_result=1
and (
sp.sync_status=0
or sp.sync_status=2
or sp.sync_status=4
);
先看看运转多长工夫,951条数据6.22秒,真的很慢。
951 rows in set (6.22 sec)
先explain,rows达到了361万,type = ALL表白是全表扫描。
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+---------+-------------+ | 1 | SIMPLE | sp | ALL | NULL | NULL | NULL | NULL | 3613155 | Using where | +----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
所有字段都利用查询返回记载数,由于是单表查询 0已经做过了951条。
让explain的rows 尽量亲近951。
看一下accurate_result = 1的记载数:
select count(*),accurate_result from stage_poi group by accurate_result;
+----------+-----------------+
| count(*) | accurate_result |
+----------+-----------------+
| 1023 | -1 |
| 2114655 | 0 |
| 972815 | 1 |
+----------+-----------------+
我们看到accurate_result这个字段的区分度非常低,整个表只要-1,0,1三个值,加上索引也没法锁定特殊少量的数据。
再看一下sync_status字段的状况:
select count(*),sync_status from stage_poi group by sync_status;
+----------+-------------+
| count(*) | sync_status |
+----------+-------------+
| 3080 | 0 |
| 3085413 | 3 |
+----------+-------------+
一样的区分度也很低,依据理论,也不适合创立索引。
题目剖析到这,宛如得出了这个表没法优化的结论,两个列的区分度都很低,即使加上索引也只能顺应这种状况,很难做普遍性的优化,比方当sync_status 0、3散布的很均匀,那么锁定记载也是百万级另外。
寻业务方去沟通,看看运用场景。业务方是这么来运用这个SQL语句的,每隔五分钟会扫描相符前提的数据,处置完成后把sync_status这个字段酿成1,五分钟相符前提的记载数并不会太多,1000个摆布。理解了业务方的运用场景后,优化这个SQL就变得简略了,由于业务方保障了数据的不服衡,要是加上索引可以过滤掉绝大局部不需要的数据。
依据创立索引法则,运用如下语句创立索引
alter table stage_poi add index idx_acc_status(accurate_result,sync_status);
调查预测效果,发明只需要200ms,快了30多倍。
952 rows in set (0.20 sec)
我们再来回忆一下剖析题目的历程,单表查询相对来说比拼好优化,大局部时候只需要把where前提里面的字段遵照法则加上索引就好,要是只是这种“无脑”优化的话,显然一些区分度非常低的列,不该该加索引的列也会被加上索引,这样会对插入、更新机能造成重大的影响,同时也有可能影响其它的查询语句。所以我们第4步伐差SQL的运用场景非常关键,我们只要晓得这个业务场景,才干更好地辅助我们更好的剖析和优化查询语句。
没法优化的语句
select
c.id,
c.name,
c.position,
c.sex,
c.phone,
c.office_phone,
c.feature_info,
c.birthday,
c.creator_id,
c.is_keyperson,
c.giveup_reason,
c.status,
c.data_source,
from_unixtime(c.created_time) as created_time,
from_unixtime(c.last_modified) as last_modified,
c.last_modified_user_id
from
contact c
inner join
contact_branch cb
on c.id = cb.contact_id
inner join
branch_user bu
on cb.branch_id = bu.branch_id
and bu.status in (
1,
2)
inner join
org_emp_info oei
on oei.data_id = bu.user_id
and oei.node_left >= 2875
and&llt;/span> oei.node_right <= 10802
and oei.org_category = - 1
order by
c.created_time desc limit 0 ,
10;
还是几个步骤。
- 先看语句运转多长工夫,10笔记录取了13秒,已经不成忍耐。
10 rows in set (13.06 sec)
- explain
+----+-------------+-------+--------+-------------------------------------+-------------------------+---------+--------------------------+------+----------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+--------+-------------------------------------+-------------------------+---------+--------------------------+------+----------------------------------------------+ | 1 | SIMPLE | oei | ref | idx_category_left_right,idx_data_id | idx_category_left_right | 5 | const | 8849 | Using where; Using temporary; Using filesort | | 1 | SIMPLE | bu | ref | PRIMARY,idx_userid_status | idx_userid_status | 4 | meituancrm.oei.data_id | 76 | Using where; Using index | | 1 | SIMPLE | cb | ref | idx_branch_id,idx_contact_branch_id | idx_branch_id | 4 | meituancrm.bu.branch_id | 1 | | | 1 | SIMPLE | c | eq_ref | PRIMARY | PRIMARY | 108 | meituancrm.cb.contact_id | 1 | | +----+-------------+-------+--------+-------------------------------------+-------------------------+---------+--------------------------+------+----------------------------------------------+
从施行规划上看,mysql先查org_emp_info表扫描8849记载,再用索引idx_userid_status关联branch_user表,再用索引idx_branch_id关联contact_branch表,最后主键关联contact表。
rows返回的都非常少,看不到有什么异样状况。我们在看一下语句,发明背面有order by + limit组合,会不会是排序量太大搞的?于是我们简化SQL,去除背面的order by 和 limit,看看到底用了多少记载来排序。
select
count(*)
from
contact c
inner join
contact_branch cb
on c.id = cb.contact_id
inner join
branch_user bu
on cb.branch_id = bu.branch_id
and bu.status in (
1,
2)
inner join
org_emp_info oei
on oei.data_id = bu.user_id
and oei.node_left >= 2875
and oei.node_right <= 10802
and oei.org_category = - 1
+----------+
| count(*) |
+----------+
| 778878 |
+----------+
1 row in set (5.19 sec)
发明排序以前竟然锁定了778878笔记录,要是针对70万的效果集排序,将是苦难性的,怪不得这么慢,那我们能不克不及换个思绪,先依据contact的created_time排序,再来join会不会比拼快呢?
于是革新成下面的语句,也可以用straight_join来优化:
select c.id, c.name, c.position, c.sex, c.phone, c.office_phone, c.feature_info, c.birthday, c.creator_id, c.is_keyperson, c.giveup_reason, c.status, c.data_source, from_unixtime(c.created_time) as created_time, from_unixtime(c.last_modified) as last_modified, c.last_modified_user_id from contact c where exists ( select 1 from contact_branch cb inner join branch_user bu on cb.branch_id = bu.branch_id and bu.status in ( 1, 2) inner join org_emp_info oei on oei.data_id = bu.user_id and oei.node_left >= 2875 and oei.node_right <= 10802 and oei.org_category = – 1 where c.id = cb.contact_id ) order by c.created_time desc limit 0 , 10;
验证一下结果 估计在
1ms内,提拔了13000多倍!
sql
10 rows in set (0.00 sec)
本认为至此大工告成,但我们在前面的剖析中漏了一个细节,先排序再join和先join再排序理论上开销是同样的,为什么提拔这么多是由于有一个limit!大致施行历程是:mysql先按索引排序得到前10笔记录,然后再去join过滤,当发明不足10条的时候,再次去10条,再次join,这显然在内层join过滤的数据非常多的时候,将是苦难的,极度状况,内层一条数据都寻不到,mysql还傻乎乎的每次取10条,险些遍历了这个数据表!
用不一样参数的SQL试验下:
select
sql_no_cache c.id,
c.name,
c.position,
c.sex,
c.phone,
c.office_phone,
c.feature_info,
c.birthday,
c.creator_id,
c.is_keyperson,
c.giveup_reason,
c.status,
c.data_source,
from_unixtime(c.created_time) as created_time,
from_unixtime(c.last_modified) as last_modified,
c.last_modified_user_id
from
contact c
where
exists (
select
1
from
contact_branch cb
inner join
branch_user bu
on cb.branch_id = bu.branch_id
and bu.status in (
1,
2)
inner join
org_emp_info oei
on oei.data_id = bu.user_id
and oei.node_left >= 2875
and oei.node_right <= 2875
and oei.org_category = - 1
where
c.id = cb.contact_id
)
order by
c.created_time desc limit 0 ,
10;
Empty set (2 min 18.99 sec)
2 min 18.99 sec!比以前的状况还糟糕许多。因为mysql的nested loop机制,碰到这种状况,根本是没法优化的。这条语句终究也只能交给利用系统去优化本人的逻辑了。 通过这个例子我们可以看到,并不是所有语句都能优化,而往往我们优化时,因为SQL用例回来时降掉一些极度状况,会造成比本来还重大的后果。所以,首先:不要指望所有语句都能通过SQL优化,第二:不要过于自信,只针对概括case来优化,而忽略了更复杂的状况。
慢查询的案例就剖析到这儿,以上只是一些比拼典型的案例。我们在优化历程中碰到过超过1000行,波及到16个表join的“垃圾SQL”,也碰到过线上线下数据库悬殊致使利用直接被慢查询拖死,也碰到过varchar等值比拼没有写单引号,还碰到过笛卡尔积查询直接把从库搞死。再多的案例其实也只是一些经验的积攒,要是我们熟知查询优化器、索引的内部道理,那么剖析这些案例就变得特殊简略了。
本文以一个慢查询案例引入了MySQL索引道理、优化慢查询的一些办法论;并针对碰到的典型案例做了细致的剖析。其实做了这么长工夫的语句优化后才发明,任何数据库层面的优化都抵不上利用系统的优化,一样是MySQL,可以用来支持Google/FaceBook/Taobao利用,但可能连你的个人网站都撑不住。套用比来比拼流行的话:“查询容易,优化不易,且写且珍爱!”
参照 文献:
1.《高机能MySQL》
2.《数据构造与算法剖析》
更多MySQL相干技术文章,请拜访MySQL教程栏目进行学习!
以上就是MySQL索引道理以及优化的细致内容,更多请关注 百分百源码网 其它相干文章!