一、索引
1、索引的定义:排好序的用于查询数据库数据的数据结构,这些数据结构以某种方式指向数据。
影响where和order by后面的操作。
2、索引的优势:(1)提高数据检索效率,减少磁盘IO(磁盘到内存)。(2)减小数据排序的成本,减少CPU资源消耗
索引的劣势:(1)索引也是以文件的方式保存,也会占用空间;(2)索引提高了查询速度,同时却会降低更新表的速度。更新表的时候不仅要保存数据还要保存一下索引文件每次更新添加的索引列。
3、索引的分类:单值索引(单个列),唯一索引(唯一,可以为空),复合索引(多个列)
4、mysql索引结构:B树索引(主要学),哈希索引,全文索引,R树索引
5、B树索引:
B+树:
6、哪些情况下需要建索引:
(1)主键自动建立唯一索引(2)频繁作为查询条件的字段(3)查询中与其他表关联的字段,外键关系建立索引(4)频繁更新的字段不作为索引(5)where条件里用不到的不建索引(6)高并发下倾向于组合索引。(7)查询中排序的字段。排序字段若通过索引访问将大大提高排序速度。(8)查询中统计或分组的字段group by
哪些情况不要建索引:
(1)表记录太少;(2)经常删改的表;(3)某个数据列包含很多重复的内容,为其建索引没有太大效果。
二、性能分析
Explain
1、Explain关键字:模拟优化器执行SQL语句,从而知道mysql是怎么执行你的SQL语句的。我们可以知道表的读取顺序,数据读取的操作类型,哪些索引可以用,实际用了哪些索引
2、使用:explain+SQL语句
3、
各个字段的意义:每一行都表示一个操作
(1)id表示操作执行的顺序。id大先执行,id相同的从上到下顺序执行。
(2)select_type :查询的类型:SIMPLE简单查询(不包含子查询或UNION)、PRIMARY最外层查询、SUBQUERY子查询(在select或where后面出现子查询)、DERIVE衍生(在from后面出现的子查询,会产生一个临时表)、UNION(若第二个子查询出现在UNION之后,则被标记为UNION,若UNION包含在FROM子句的子查询中,则外层select被标记为Derived)、UNION RESULT(从UNION表获取结果的select)
(3)table表示操作的表
(4)type:访问类型,显示查询使用了何种类型。有8种类型。性能从高到低是:system>const>eq_ref>ref>range>index>ALL,一般来说得能保证到range,争取ref.
system :如果表只有一行记录,相当于系统表,这是system类型
const:表示通过一次索引就找到了,只定位表中一条记录。如主键作为where条件时。select * from stu where id=1;
eq_ref:唯一性索引扫描,对每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描。select * from t1,t2 where t1.id=t2.id
ref:非唯一性索引扫描,返回匹配某个单值的所有行select* from stu where class_id=1
range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引。一般就是在where字句中between、<、>、in等的查询。
index:全索引扫描。查询的是索引字段,select id from stu
(5)possible key 和key :可能应用在这张表上的索引,实际用到的索引key,如果为NULL,则没有使用索引,查询中若使用了覆盖索引,则该索引仅出现在key列表中。覆盖索引,查询的列正好是建的索引列。select * from su where name ='a',select * from su where name ='a' and id =1
(6)key_len:表示索引中使用的字节数,可通过该列计算查询时使用的索引的长度,在不失精度的条件下,长度越短越好。这是计算得到的长度,并非实际使用的长度
(7)ref:显示索引的哪一列被使用了,如果可能的话是一个常数。哪些列或常量被用于查找索引列上的值。
(8)rows:有多少行被优化器查询。越少越好
(9)extra:包含不适合在其他列显示但十分重要的信息。有哪些额外的信息呢。比如
Using filesort:说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取,mysql无法利用索引完成的拍序操作称为文件排序。
Using temporary:产生了临时表保存中间结果,mysql在对查询结果排序时使用临时表。常见于order by 和group by
Using index:表明相应的select操作使用了覆盖索引,避免了访问表的数据行,效率不错。如果同时出现using where ,表明索引被用来执行索引键值的查找;如果没有同时出现using where ,表明索引用来读取数据而非查找操作
覆盖索引:select的数据列只用从索引中就可以获得了,不必读取数据行,Mysql可以利用索引来返回select列表中的字段,而不必再根据索引文件读取数据文件,即查询的列被所建的索引覆盖(比索引的列少)。
注意:如果要使用覆盖索引,一定要注意select列表只取出需要的列,不可select*,因为如果将所有的字段一起做索引会导致索引文件过大,查询性能下降。
Using where表明使用了where过滤
Using join buffer使用了连接缓存
impossible where :where条件总是false
三、索引优化案例
1、单表
建立一张article表,初始只有主键索引
查询category_id为1且comments大于1的情况下,views最多的article_id.
sql 为SELECT id,author_id from article WHERE category_id=1 AND comments>1 ORDER BY views DESC LIMIT 1
性能分析的结果为
使用了全表扫描,并且出现了外部的索引排序,需要优化。
优化方法:尝试。建立查询相关的列,category_id,comments,views建立联合索引。
之后查询
解决了全表扫描的问题,但依然出现了using filesort,原因是在三个联合索引中,第二个comments的条件使用的是范围条件,导致后面的索引失效了。
如果comments条件改为等于1,则
然后继续优化,如果只建立category_id和views联合索引,再次测试
检索和排序都用到了索引。
2、两表
新建book和class表
测试两个表的查询SELECT * FROM class LEFT JOIN book on class.card=book.card.
现在应该在哪个字段添加索引?
在class的card字段加索引,结果为
反过来只在book的card字段添加索引,结果为
在左连接里面分别设置左表和右表的索引对于查询的优化效果并不相同。可以看到对右表的相关字段做索引之后在type和rows上均有优化。
这是由左连接的特性决定的,LEFT JOIN条件用于确定如何从右表搜索行,左边的行一定有。所以在右表建索引收益更大。
如果索引不变,可以修改SQL语句尽量使用到索引。
总结就是左连接在右表上建索引,右连接在左表建立索引。
3、三表
新建表phone
三表的连接查询怎么写,索引应该怎么建?
SELECT * FROM class LEFT JOIN book on class.card=book.card LEFT JOIN phone ON book.card=phone.card
现在在book和phone表建立索引
4、结论
join语句的优化
尽可能减少语句中的NestedLoop循环总次数;永远用小结果集驱动大结果集;优先保证NestedLoop的内层循环;保证Join语句中被驱动表上Join的条件字段已经被索引。
在无法保证被驱动表的Join条件字段被索引且内存资源充足的前提下,可以设置更大的JoinBuffer
5、避免索引失效
创建表staffs
创建联合索引(name,age,pos)
1、全值匹配最好,就是完全按照建的联合索引(顺序)进行查询
SELECT *FROM staffs WHERE `name`='july'
SELECT *FROM staffs WHERE `name`='july' AND age=23
SELECT *FROM staffs WHERE `name`='july' AND age=23 AND pos='dev'
三种查询均能使用到索引,但使用的长度增加,精度增加
SELECT * FROM staffs WHERE age=23 AND pos='dev'
2、最佳左前缀法则:查询从索引的最左边开始,并且不能跳过列,否则造成后面的索引列失效,导致只使用了部分索引。(如果最左边的索引列在,但没有按顺序,mysql的优化器会帮我们优化顺序,使用检索)
3、不在索引列上做任何计算(计算、函数、(自动或手动)类型转换)会导致索引失效转向全表扫描。
SELECT *FROM staffs WHERE `name`='July'+'aa'
4、存储引擎不能使用索引中查找范围条件右边的列,那样会造成后面的索引列失效
5、尽量使用覆盖索引,减少select *
6、mysql在使用不等于(!=或<>)的时候也不能使用索引
SELECT * FROM staffs WHERE `name`='July' AND age<>23 AND pos='dev'
7、like以通配符开头like(‘%dev’)会导致当前索引列失效。
SELECT * FROM staffs WHERE `name` ='July' AND age=23 AND pos='dev'
SELECT * FROM staffs WHERE `name` like 'July%' AND age=23 AND pos='dev'(索引都用到了))
SELECT * FROM staffs WHERE `name` like '%July' AND age=23 AND pos='dev'
解决like‘%字符%’时索引不被使用的方法:
使用覆盖索引
SELECT `NAME` FROM tb_user WHERE `NAME` LIKE('%a%')
8、字符串不加单引号索引失效(可以查,但是中间发生了整数转字符串的类型转换,这是mysql自动转换的,所以索引失效)
9、少用 or,用or连接时索引会失效
四、试题练习
1、联合索引(c1,c2,c3,c4)
(1)where c1='a' and c2='b' and c3='c' and c4='d':索引都用到
(2)where c1='a' and c2='b' and c4='c' and c3='d':都用到了,mysql的优化器会重新调整顺序
(3)where c1='a' and c2='b' and c4='d':只用到c1,c2
order by
(4)where c1='a' and c2='b' order by c3: c1,c2用于查找,c3用于排序
(5)where c1='a' and c2='b' order by c4: c1,c2用于查找,出现filesort
(6)where c1='a' and c2='b' order by c4,c3 : c1,c2用于查找,出现filesort,优化器是不会优化order by后面排序的前后位置的
(7)where c1='a' and c2='b' and name ='aa' order by c3,c4 :c1.c2用于查找,c3,c4用于排序
(8)where c1='a' and c2='b' order by c2,c3: c1,c2用于查找,c2,c3用于排序
(9)where c1='a' and c2='b' order by c3,c2: 后面c2的排序条件是无效的,c2是一个常量了,已经不用排序了,不会出现filesort
group by
group by 使用索引的情况与order by 相同,因为分组之前必排序
group by基本上都需要排序,会有临时表产生。
五、查询截取分析
1、查询优化
1、永远小表驱动大表
select * from A where id in (select id from B)
B表比A小时,用in优于exist
select * from A where exists (select 1 from B where A.id=B.id)
A是小表时,用exists优于in
注意:A表与B表的id字段应建立索引。
exists(子查询)只返回TRUE或FALSE,因此子查询中的select* 也可以是select 1或select ‘x’,实际查询是会忽略select清单
2、order by关键字排序优化
建表tblA,只有两个字段,age和birth,建立复合索引(age,birth)
Mysql支持两种方式排序,Filesort和index,index 效率高,它值mysql扫描索引本身完成排序,filesort方式效率低。如果排序的部分没有完全使用索引就会产生filesort
(1)SELECT * FROM tbla WHERE age>20 ORDER BY age,birth 可以用到索引
(2)SELECT * FROM tblaWHERE age>20 ORDER BY birth 排序没有用到birth索引。产生filesort
(3)SELECT * FROM tbla WHERE age>20 ORDER BY birth,age 产生filesort
(4)select * from tblA order by age asc,birth desc
同时升序或降序是可以用到索引排序的。
总结:order by 子句尽量使用index方式排序,避免使用filesort排序
尽可能在索引列上完成排序。如果不在索引列上,filesort有两种算法:双路排序和单路排序。
双路排序:在mysql4.1之前使用双路排序,两次扫描磁盘,最终得到数据。读取行指针累和order by 列,对他们排序,然后扫描已经排好序的列表,按照列表中的值重新从列表中读取对应的数据输出。从磁盘读取排序字段,在buffer进行排序,再从磁盘读取其他字段。取一次数据要经历两次磁盘扫描,IO耗时。
单路排序:改进的算法。从磁盘读取查询需要的列,按照order by 列在buffer进行排序,然后扫描排序后的列表输出,避免第二次IO,但是会使用更多的空间,因为排序后的结果是保存在内存里。
单路排序的问题:
如果单路排序时在buffer里面,如果结果数据过多,导致buffer空间不足,每次只能对buffer大小的数据进行排序,反而导致多次IO.
如何优化?增大sort_buffer的size,增大max_lenth_for_sort_data参数
提高order by 的速度:
(1)最好不要用select * ,出于buffer空间和max_length_for_sort_data的考虑。
(2)提高sort_buffer_size:根据系统的情况调整
(3)提高max_length_for_sort_data:
2、慢查询日志
运行时间超过确定阈值的SQL记录到慢查询日志。默认阈值是10秒(大于),long_query_time.
默认慢查询日志关闭。
set global show_query_log=1开启慢查询日志
SELECT SLEEP(4) 模拟超时SQL,可以把超时时间设为3秒
Mysql日志分析工具:mysqldumpslow
3、批量数据脚本
使用存储过程
插入一千万数据
4、show profile
是mysql提供可以用来分析当前会话中语句执行的资源消耗情况,用于SQL调优的测量
开启
执行sql
查看结果show profiles
诊断sql ,show profile cpu,block io for query 3(查询编号)
显示cpu,io的情况,还可以查memory内存的使用情况
5、全局查询日志
测试环境
六、Mysql锁机制
按照对数据的操作分为读锁和写锁。按照锁的粒度来分可分为表锁和行锁。
读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响
写锁(排他锁):当前写操作没有完成之前,会阻断其他写锁和读锁。
1、表锁
偏向MyISAM存储引擎,开销小,加锁快;无死锁,发生锁冲突的概率最高,并发度低
show OPEN TABLES查看表的锁情况。