针对偶尔很慢的情况
当我们要往数据库插入一条数据、或者要更新一条数据的时候,我们知道数据库会在内存中把对应字段的数据更新了,但是更新之后,这些更新的字段并不会马上同步持久化到磁盘中去,而是把这些更新的记录写入到 redo log 日记中去,等到空闲的时候,在通过 redo log 里的日记把最新的数据同步到磁盘中去。
不过,redo log 里的容量是有限的,如果数据库一直很忙,更新又很频繁,这个时候 redo log 很快就会被写满了,这个时候就没办法等到空闲的时候再把数据同步到磁盘的,只能暂停其他操作,全身心来把数据同步到磁盘中去的,而这个时候,就会导致我们平时正常的SQL语句突然执行的很慢,所以说,数据库在在同步数据到磁盘的时候,就有可能导致我们的SQL语句执行的很慢了。
这个就比较容易想到了,我们要执行的这条语句,刚好这条语句涉及到的表,别人在用,并且加锁了,我们拿不到锁,只能慢慢等待别人释放锁了。或者,表没有加锁,但要使用到的某个一行被加锁了,这个时候,我也没办法啊。
如果要判断是否真的在等待锁,我们可以用 show processlist这个命令来查看当前的状态哦
针对一直都这么慢的情况
如果在数据量一样大的情况下,这条 SQL 语句每次都执行的这么慢,那就就要好好考虑下你的 SQL 书写了,下面我们来分析下哪些原因会导致我们的 SQL 语句执行的很不理想。
我们先来假设我们有一个表,表里有下面两个字段,分别是主键 id,和两个普通字段 c 和 d。
mysql> CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
扎心了,没用到索引
没有用上索引,我觉得这个原因是很多人都能想到的,例如你要查询这条语句
select * from t where 100
刚好你的 c 字段上没有索引,那么抱歉,只能走全表扫描了,这导致这条查询语句很慢。
好吧,这个时候你给 c 这个字段加上了索引,然后又查询了一条语句
select * from t where c - 1 = 1000;
我想问大家一个问题,这样子在查询的时候会用索引查询吗?
答是不会,如果我们在字段的左边做了运算,那么很抱歉,在查询的时候,就不会用上索引了,所以呢,大家要注意这种字段上有索引,但由于自己的疏忽,导致系统没有使用索引的情况了。
正确的查询应该如下
select * from t where c = 1000 + 1;
有人可能会说,右边有运算就能用上索引?难道数据库就不会自动帮我们优化一下,自动把 c - 1=1000 自动转换为 c = 1000+1。
不好意思,确实不会帮你,所以,你要注意了。
如果我们在查询的时候,对字段进行了函数操作,也是会导致没有用上索引的,例如
select * from t where pow(c,2) = 1000;
这里我只是做一个例子,假设函数 pow 是求 c 的 n 次方,实际上可能并没有 pow(c,2)这个函数。其实这个和上面在左边做运算也是很类似的。
所以呢,一条语句执行都很慢的时候,可能是该语句没有用上索引了,不过具体是啥原因导致没有用上索引的呢,你就要会分析了,我上面列举的三个原因,应该是出现的比较多的吧。
呵呵,数据库自己选错索引了
我们在进行查询操作的时候,例如
select * from t where 100 < c and c < 100000;
我们知道,主键索引和非主键索引是有区别的,主键索引存放的值是整行字段的数据,而非主键索引上存放的值不是整行字段的数据,而且存放主键字段的值。不大懂的可以看我这篇文章:面试小知识:MySQL索引相关 里面有说到主键索引和非主键索引的区别
也就是说,我们如果走 c 这个字段的索引的话,最后会查询到对应主键的值,然后,再根据主键的值走主键索引,查询到整行数据返回。
就算你在 c 字段上有索引,系统也并不一定会走 c 这个字段上的索引,而是有可能会直接扫描扫描全表,找出所有符合 100 < c and c < 100000 的数据。
为什么会这样呢?
其实是这样的,系统在执行这条语句的时候,会进行预测:究竟是走 c 索引扫描的行数少,还是直接扫描全表扫描的行数少呢?显然,扫描行数越少当然越好了,因为扫描行数越少,意味着I/O操作的次数越少。
如果是扫描全表的话,那么扫描的次数就是这个表的总行数了,假设为 n;而如果走索引 c 的话,我们通过索引 c 找到主键之后,还得再通过主键索引来找我们整行的数据,也就是说,需要走两次索引。而且,我们也不知道符合 100 c < and c < 10000 这个条件的数据有多少行,万一这个表是全部数据都符合呢?这个时候意味着,走 c 索引不仅扫描的行数是 n,同时还得每行数据走两次索引。
所以呢,系统是有可能走全表扫描而不走索引的。那系统是怎么判断呢?
判断来源于系统的预测,也就是说,如果要走 c 字段索引的话,系统会预测走 c 字段索引大概需要扫描多少行。如果预测到要扫描的行数很多,它可能就不走索引而直接扫描全表了。
那么问题来了,系统是怎么预测判断的呢?这里我给你讲下系统是怎么判断的吧,虽然这个时候我已经写到脖子有点酸了。
系统是通过索引的区分度来判断的,一个索引上不同的值越多,意味着出现相同数值的索引越少,意味着索引的区分度越高。我们也把区分度称之为基数,即区分度越高,基数越大。所以呢,基数越大,意味着符合 100 < c and c < 10000 这个条件的行数越少。
所以呢,一个索引的基数越大,意味着走索引查询越有优势。
那么问题来了,怎么知道这个索引的基数呢?
系统当然是不会遍历全部来获得一个索引的基数的,代价太大了,索引系统是通过遍历部分数据,也就是通过采样的方式,来预测索引的基数的。
扯了这么多,重点的来了,居然是采样,那就有可能出现失误的情况,也就是说,c 这个索引的基数实际上是很大的,但是采样的时候,却很不幸,把这个索引的基数预测成很小。例如你采样的那一部分数据刚好基数很小,然后就误以为索引的基数很小。然后就呵呵,系统就不走 c 索引了,直接走全部扫描了。
所以呢,说了这么多,得出结论:由于统计的失误,导致系统没有走索引,而是走了全表扫描,而这,也是导致我们 SQL 语句执行的很慢的原因。
这里我声明一下,系统判断是否走索引,扫描行数的预测其实只是原因之一,这条查询语句是否需要使用使用临时表、是否需要排序等也是会影响系统的选择的。
不过呢,我们有时候也可以通过强制走索引的方式来查询,例如
select * from t force index(a) where c < 100 and c < 100000;
我们也可以通过
show index from t;
来查询索引的基数和实际是否符合,如果和实际很不符合的话,我们可以重新来统计索引的基数,可以用这条命令
analyze table t;
来重新统计分析。
既然会预测错索引的基数,这也意味着,当我们的查询语句有多个索引的时候,系统有可能也会选错索引哦,这也可能是 SQL 执行的很慢的一个原因。
好吧,就先扯这么多了,你到时候能扯出这么多,我觉得已经很棒了,下面做一个总结。
总结
以上是我的总结与理解,最后一个部分,我怕很多人不大懂数据库居然会选错索引,所以我详细解释了一下,下面我对以上做一个总结。
一个 SQL 执行的很慢,我们要分两种情况讨论:
1、大多数情况下很正常,偶尔很慢,则有如下原因
(1) 数据库在刷新脏页,例如 redo log 写满了需要同步到磁盘。
(2) 执行的时候,遇到锁,如表锁、行锁。
2、这条 SQL 语句一直执行的很慢,则有如下原因。
(1) 没有用上索引:例如该字段没有索引;由于对字段进行运算、函数操作导致无法用索引。
(2) 数据库选错了索引。
官方:幻读是指当事务不是独立执行时发生的一种现象,
例如第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的“全部数据行”。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入“一行新数据”。那么,以后就会发生操作第一个事务的用户发现表中还存在没有修改的数据行,就好象发生了幻觉一样。
一般解决幻读的方法是增加范围锁RangeS,锁定检索范围为只读,这样就避免了幻读。
个人解读:举个栗子,A查询ID(唯一索引)>6的数据,查询结果为空,此时B插入一条ID=6的数据,因为当前A的隔离级别是可重复读,那么当A第二次查询ID>6时,还是空,此时A插入ID=6的数据,会出现ID冲突不能插入。A就是不明白为什么ID=6不存在但是自己就是插入不了,但我们知道为什么。因为A出现了幻读。
事务在插入已经检查过不存在的记录时,惊奇的发现这些数据已经存在了,之前的检测获取到的数据如同鬼影一般。
https://blog.csdn.net/v123411739/article/details/39298127
另外,Mysql的存储引擎接口定义良好。有兴趣的开发者通过阅读文档编写自己的存储引擎。
http://baike.baidu.com/item/存储引擎
使用悲观锁 悲观锁本质是当前只有一个线程执行操作,结束了唤醒其他线程进行处理。
也可以缓存队列中锁定主键。
然后,我们现在解决了锁的问题,全部请求采用“先进先出”的队列方式来处理。那么新的问题来了,高并发的场景下,因为请求很多,很可能一瞬间将队列内存“撑爆”,然后系统又陷入到了异常状态。或者设计一个极大的内存队列,也是一种方案,但是,系统处理完一个队列内请求的速度根本无法和疯狂涌入队列中的数目相比。也就是说,队列内的请求会越积累越多,最终Web系统平均响应时候还是会大幅下降,系统还是陷入异常。
♀ 使用乐观锁:这个时候,我们就可以讨论一下“乐观锁”的思路了。乐观锁,是相对于“悲观锁”采用更为宽松的加锁机制,大都是采用带版本号(Version)更新。实现就是,这个数据所有请求都有资格去修改,但会获得一个该数据的版本号,只有版本号符合的才能更新成功,其他的返回抢购失败。这样的话,我们就不需要考虑队列的问题,不过,它会增大CPU的计算开销。但是,综合来说,这是一个比较好的解决方案。(具体参考5)
http://blog.csdn.net/hsd2012/article/details/51106285
MySql索引的原理:
1)通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,我们可以总是用同一种查找方式来锁定数据。
2)索引是通过复杂的算法,提高数据查询性能的手段。从磁盘io到内存io的转变。
MySql索引原理参考博客:
https://blog.csdn.net/u013235478/article/details/50625677
MySql索引的类型:
1)普通索引index:加速查找
2)唯一索引: ① 主键索引:primary key:加速查找+主键唯一约束且不为空。
② 唯一索引:unique:加速查找+主键唯一约束。
3)联合索引: ① primary key(id,name):联合主键索引。
② unique(id,name):联合唯一索引。
③ unique(id,name):联合普通索引。
4)全文索引fulltext:用于搜索很长一篇文章的时候,效果最好。
5)空间索引spatial:了解就好,几乎不用
创建索引的原则:
1)最左前缀匹配原则,非常重要的原则,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的顺序可以任意调整。
2)=和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式。
3)尽量选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条记录。
4)索引列不能参与计算,保持列“干净”,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’)。
5)尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可
https://blog.csdn.net/zhengzhaoyang122/article/details/79463583
https://blog.csdn.net/zhengzhaoyang122/article/details/82183977
http://tech.meituan.com/mysql-index.html
http://www.cnblogs.com/cq-home/p/3482101.html
聚集索引和非聚集索引的区别
“聚簇”就是索引和记录紧密在一起。
非聚簇索引 索引文件和数据文件分开存放,索引文件的叶子页只保存了主键值,要定位记录还要去查找相应的数据块。
select for update 语句是我们经常使用手工加锁语句。
借助for update子句,我们可以在应用程序的层面手工实现数据加锁保护操作。
属于并发行锁,这个我们上面在悲观锁的时候也有介绍。
https://www.cnblogs.com/micrari/p/8029710.html
https://blog.csdn.net/endlu/article/details/51720299
http://www.cainiaoxueyuan.com/sjk/5178.html
https://blog.csdn.net/qq_37221991/article/details/87693639
https://blog.csdn.net/chen1280436393/article/details/80493088
由于复合索引的组合索引,类似多个木板拼接在一起,如果中间断了就无法用了,所以要能用到复合索引,首先开头(第一列)要用上,比如index(a,b) 这种,
我们可以select table tname where a=XX 用到第一列索引 如果想用第二列 可以 and b=XX 或者and b like‘TTT%’
(1)设置主键自增为何不可取
这样的话,数据库本身是单点,不可拆库,因为id会重复。
(2)依赖数据库自增机制达到全局ID唯一
使用如下语句:
REPLACE INTO Tickets64 (stub) VALUES ('a');
SELECT LAST_INSERT_ID();
这样可以保证全局ID唯一,但这个Tickets64表依旧是个单点。
(3)依赖数据库自增机制达到全局ID唯一并消除单点
在2的基础上,部署两个(多个)数据库实例,
设置自增步长为2(多个则为实例数),即auto-increment-increment = 2
设置auto-increment-offset分别为1,2.....
这样第一台数据库服务器的自增id为 1 3 5 7 9
第二台为2 4 6 8 10
(4)解决每次请求全局ID都读库写库压力过大的问题
比如第一次启动业务服务,会请求一个唯一id为3559
如果是2、3的方法,则id为3559,这样每次都请求数据库,对数据库压力比较大
可以用3559 * 65536(举个例子,并不一定是65536)+ 内存自增变量来作为id
当内存自增变量到达65535时,从数据库重新获取一个自增id
这样即使有多台业务服务器,id也不会重复:
第一台 3559 * 65536 + 1,2,3.....65535
第二台 3560 * 65536 + 1,2,3.....65535
然后第一台到65535了,换一个数据库自增id,这时候可能是3561 * 65536 + 1,2,3....
MVCC:Multi-Version Concurrency Control 多版本并发控制,
MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问;在编程语言中实现事务内存。
MVCC最大的好处 读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能,这也是为什么现阶段,几乎所有的RDBMS,都支持了MVCC。
https://blog.csdn.net/faye0412/article/details/78351414
char(n) :固定长度类型,比如订阅cha(10),当你输入"abe"三个字符的时候,它们占的空间还是10个字节,其他7个是空字节。
char优点:效率高;缺点:占用空间;
适用场量:存储密码的nd5值,固定长度的,使用char非常合适。
varchar(n) :可变长度,存储的值是每个值占用的字节再加上一个用来记录其长度的字节的长度。
所以,从空间上考虑varcahr比较合适;从效率上考虑char比较合适,二者使用需要权衡。
MyISAM只支持表锁, InnoDB支持表锁和行锁,默认为行锁。
count(1)和count(*)都会对全表进行扫描,统计所有记录的条数,包括那些为null的记录,因此,它们的效率可以说是相差无几。而count(字段)则与前两者不同,它会统计该字段不为null的记录条数。
下面它们之间的一些对比:
在表上创建索引后,如何使用到这些索引需要注意的问题。
1.索引列上不能使用表达式或函数。
例如:select * from users where upper(name)=’ABC';
改成:select * from users where name=’ABC';
2.前缀索引和索引列的选择性
索引的选择性越高索引效率越高。
3.联合索引
如何选择索引列的顺序
1.经常会被使用到的列优先
联合索引的列索引顺序是从左到右使用的。
2.选择性高的列优先
选择性很差的列不宜放到最左边。比如状态列。
3.宽度小的列优先
4.覆盖索引
索引中包含了所有查询的字段的情况的索引。
优点:
可以优化缓存,减少磁盘IO操作。
可以减少随机IO 变随机IO为顺序IO操作。
避免对INNODB主键索引的二次查询
可以避免myisam表进行系统调用
无法使用覆盖索引的情况:
1.存储引擎不支持覆盖索引
2.查询中使用了太多的列。
3.使用了双%号的like查询
5.使用索引扫描优化排序
索引的列顺序和order by 子句的顺序完全一致
索引中所有列的方向(升序和降序)和order by 子句完全一致
order by 中 的字段全部在关联表中的第一张表中。
6.利用索引优化锁
1.索引可以减少锁定的行数
2.索引可以加快处理速度,同时加快锁的释放。
7.删除重复和冗余的索引
重复的索引:
比如:primary key(id),unique key (id) ,index (id);
这样索引就重复了
冗余的索引:
index(name) ,index(name,age) 联合索引,那么 index(name) 就冗余了。
可以使用工具
pt-duplicate-key-checker –h=localhost 检查重复索引。
查找未被使用过的索引:
更新索引统计信息及减少索引碎片
analyze table table_name;
索引并不是时时都会生效的,比如以下几种情况,将导致索引失效:
注意:要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引
交代一下背景,这算是一次项目经验吧,属于公司一个已上线平台的功能,这算是离职人员挖下的坑,随着数据越来越多,原本的SQL查询变得越来越慢,用户体验特别差,因此SQL优化任务交到了我手上。
这个SQL查询关联两个数据表,一个是攻击IP用户表主要是记录IP的信息,如第一次攻击时间,地址,IP等等,一个是IP攻击次数表主要是记录每天IP攻击次数。而需求是获取某天攻击IP信息和次数。(以下SQL语句测试均在测试服务器上上,正式服务器的性能好,查询时间快不少。)
查看表的行数:
未优化前SQL语句为:
SELECT
attack_ip,
country,
province,
city,
line,
info_update_time AS attack_time,
sum( attack_count ) AS attack_times
FROM
`blacklist_attack_ip`
INNER JOIN `blacklist_ip_count_date` ON `blacklist_attack_ip`.`attack_ip` = `blacklist_ip_count_date`.`ip`
WHERE
`attack_count` > 0
AND `date` BETWEEN '2017-10-13 00:00:00'
AND '2017-10-13 23:59:59'
GROUP BY
`ip`
LIMIT 10 OFFSET 1000
AIN分析一下:
这里看到索引是有的,但是IP攻击次数表blacklist_ip_count_data也用上了临时表。那么这SQL不优化直接第一次执行需要多久(这里强调第一次是因为MYSQL带有缓存功能,执行过一次的同样SQL,第二次会快很多。)
实际查询时间为300+秒,这完全不能接受呀,这还是没有其他搜索条件下的。
那么我们怎么优化呢,索引既然走了,我尝试一下避免临时表,这时我们先了解一下临时表跟group by的使联系:
查找了网上一些博客分析GROUP BY 与临时表的关系 :
1. 如果GROUP BY 的列没有索引,产生临时表.
2. 如果GROUP BY时,SELECT的列不止GROUP BY列一个,并且GROUP BY的列不是主键 ,产生临时表.
3. 如果GROUP BY的列有索引,ORDER BY的列没索引.产生临时表.
4. 如果GROUP BY的列和ORDER BY的列不一样,即使都有索引也会产生临时表.
5. 如果GROUP BY或ORDER BY的列不是来自JOIN语句第一个表.会产生临时表.
6. 如果DISTINCT 和 ORDER BY的列没有索引,产生临时表.
其实,9W的临时表并不算多,那么为什么导致会这么久的查询呢?我们想想这没优化的SQL的执行过程是怎么样的呢?
网上搜索得知内联表查询一般的执行过程是:
1、执行FROM语句
2、执行ON过滤
3、添加外部行
4、执行where条件过滤
5、执行group by分组语句
6、执行having
7、select列表
8、执行distinct去重复数据
9、执行order by字句
10、执行limit字句
第一种优化:Mysql 是先执行内联表然后再进行条件查询的最后再分组,那么想想这SQL的条件查询和分组都只是一个表的,内联后数据就变得臃肿了,这时候再进行条件查询和分组是否太吃亏了,
我们可以尝试一下提前进行分组和条件查询,实现方法就是子查询联合内联查询。
这里EXPLAIN看来,只是多了子查询,ROWS和临时表都没有变化。那么我们看看实际的效果呢?
可见,取出来的数据完全一模一样,可是优化后效率从原来的330秒变成了0.28秒,这里足足提升了1000多倍的速度。这也基本满足了我们的优化需求。
第二种优化:
这里用的是内联表查询,大家都是知道子查询完全是可以代替内联表查询的,只不过SQL语句复杂了不少,那么我们分析一下这SQL,两个表分表提供了什么?
1、IP攻击次数表blacklist_ip_count_data主要提供的指定时间条件查询,攻击次数条件查询后的IP和每个IP符合条件下的具体攻击次数。
2、攻击IP用户表blacklist_attack_ip主要是具体IP的信息,如第一次攻击时间,地址,IP等等。
那么我们一步步来:
1、IP攻击次数表blacklist_ip_count_data获取符合时间条件和攻击次数的IP并且以IP分组:
2、攻击IP用户表blacklist_attack_ip指定具体的IP获取信息:
然后结合在一起:
可见,取出来的数据完全一模一样,可是优化后效率从原来的330秒变成了0.28秒,这里足足提升了1000多倍的速度。这也基本满足了我们的优化需求。
我们EXPLAIN了解一下情况:
根本区别
聚集索引和非聚集索引的根本区别是表记录的排列顺序和与索引的排列顺序是否一致。
聚集索引
聚集索引表记录的排列顺序和索引的排列顺序一致,所以查询效率快,只要找到第一个索引值记录,其余就连续性的记录在物理也一样连续存放。聚集索引对应的缺点就是修改慢,因为为了保证表中记录的物理和索引顺序一致,在记录插入的时候,会对数据页重新排序。
非聚集索引
非聚集索引制定了表中记录的逻辑顺序,但是记录的物理和索引不一定一致,两种索引都采用B+树结构,非聚集索引的叶子层并不和实际数据页相重叠,而采用叶子层包含一个指向表中的记录在数据页中的指针方式。非聚集索引层次多,不会造成数据重排。
例子对比两种索引
聚集索引就类似新华字典中的拼音排序索引,都是按顺序进行,例如找到字典中的“爱”,就里面顺序执行找到“癌”。而非聚集索引则类似于笔画排序,索引顺序和物理顺序并不是按顺序存放的。
索引创建Demo
create database IndexDemo
go
use IndexDemo
go
create table ABC
(
A int not null,
B char(10),
C varchar(10)
)
go
insert into ABC select 1,'B','C'
union select 5,'B','C'
union select 7,'B','C'
union select 9,'B','C'
go select * from abc
这个时候查看表记录,如图一显示
这个时候插入一条数据,
insert into abc values('6','B','C')
此时的查询记录为图二展示
添加聚集索引,再查询数据显示为图三,此时发现表的顺序发生了变化,此时的排序按A字段的递增排序。
create clustered index CLU_ABC on abc(A)
删除聚集索引,会发现表的顺序不会发生改变。
drop index abc.CLU_ABC
添加非聚集索引,添加新的记录,查看表顺序,如图四,并没有影响表的顺序。
create nonclustered index NONCLU_ABC on abc(A)
insert into abc values('4','B','C')
【总结】
关于聚集索引和非聚集索引小编也就知道这么多了,感觉一个小小的索引用法还要很多种啊,要想真正的用好还是不怎么容易的。理论和简单的demo都做了,剩下的就是在项目中实战了!相信未来会更美好的,O(∩_∩)O哈哈~。
涉及频繁的update的列最好用非聚集索引,因为频繁的update会频繁的改变索引结构
1,slow_query_log
这个参数设置为ON,可以捕获执行时间超过一定数值的SQL语句。
2,long_query_time
当SQL语句执行时间超过此数值时,就会被记录到日志中,建议设置为1或者更短。
3,slow_query_log_file
记录日志的文件名。
4,log_queries_not_using_indexes
这个参数设置为ON,可以捕获到所有未使用索引的SQL语句,尽管这个SQL语句有可能执行得挺快。
SELECT * FROM 表名 WHERE ROWNUM<=10
补充:ROWNUM是一个序列,是oracle数据库从数据文件或缓冲区中读取数据的顺序。
它取得第一条记录则rownum值为1,第二条为2,依次类推。小于等于10,则就会只取前10条记录。
利用mybatis的foreach 拼接动态sql或者java中写循环拼接,将数据分组拼接成大sql,
比如可以每1万行数据拼接为一个insert语句
设置Mybatis的sqlsession的ExecutorType为batch,如果用jdbc则用executeBatch
sql语句优化或者该数据表添加索引,
就比如一本书,你想看第六章第六节讲的是什么,你会怎么做,一般人肯定去看目录,
找到这一节对应的页数,然后翻到这一页。这就是目录索引,帮助读者快速找到想要的章节。
在数据库中,我们也有索引,其目的当然和我们翻书一样,能帮助我们提高查询的效率。
索引就像目录一样,减少了计算机工作量,对于表记录较多的数据库来说是非常实用的,
可以大大的提高查询的速度。否则的话,如果没有索引,计算机会一条一条的扫描,
每一次都要扫描所有的记录,浪费大量的cpu时间。
我们都知道对于一个无序的表,和一个有序的表,有序表的查询方法会有更多地选择,
每种查询方法的效率也不同,其实为表建立索引,也就是对表中的记录按照索引字段排序。
1、查询表中的索引
select * from user_indexes where table_name='WORKLOG';(大写表名)
我查询出并选择索引是PK_WORKLOG该索引建在LINTERID字段上,
2、另 查询所有聚集型索引(与分析索引无关)
select * from user_indexes where uniqueness='UNIQUE'
3、确定了表和表中的索引后,就要执行分析索引的语句,如下
alert index PK_WORKLOG monitoring usage;
这时候数据库会监控索引的使用情况
4、执行相关的sql语句,如
select * from WORKLOG T where T.LINTERID='110';
被分析的索引一定要在查询条件里,这样索引才能被分析
5、最后查询索引的使用情况。
select table_name,index_name,used from v$object_usage;
在used列就可以看到索引是否被使用,
由此分析,在复杂的sql语句语句中可以将语句分离,可以先单独的分析每个表的索引使用情况
like 要是使用索引 就必须这样写 like ‘a%’ ,两边都加上是不会触发索引的。
想想你也知道,没有一个确切的值怎么能按一定条件查找数据呢?‘%a%’这种写法只会造成全表扫描。
但是下面的SQL将走全表扫描(不会使用索引)
SELECT * FROM jka WHERE v LIKE '%ABC';
(1)DELETE语句执行删除的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作。
TRUNCATE TABLE 则一次性地从表中删除所有的数据并不把单独的删除操作记录记入日志保存,删除行是不能恢复的。并且在删除的过程中不会激活与表有关的删除触发器。执行速度快。
(2)表和索引所占空间。
当表被TRUNCATE 后,这个表和索引所占用的空间会恢复到初始大小,
DELETE操作不会减少表或索引所占用的空间。
drop语句将表所占用的空间全释放掉。
(3)一般而言,drop > truncate > delete
(4)应用范围。
TRUNCATE 只能对TABLE; DELETE可以是table和view
(5)TRUNCATE 和DELETE只删除数据, DROP则删除整个表(结构和数据)。
(6)truncate与不带where的delete :只删除数据,而不删除表的结构(定义)drop语句将删除表的结构被依赖的约束(constrain),触发器(trigger)索引(index);依赖于该表的存储过程/函数将被保留,但其状态会变为:invalid。
(7)delete语句为DML(data maintain Language),这个操作会被放到 rollback segment中,事务提交后才生效。如果有相应的 tigger,执行的时候将被触发。
(8)truncate、drop是DLL(data define language),操作立即生效,原数据不放到 rollback segment中,不能回滚
(9)在没有备份情况下,谨慎使用 drop 与 truncate。要删除部分数据行采用delete且注意结合where来约束影响范围。回滚段要足够大。要删除表用drop;若想保留表而将表中数据删除,如果于事务无关,用truncate即可实现。如果和事务有关,或老师想触发trigger,还是用delete。
(10) Truncate table 表名 速度快,而且效率高,因为:
truncate table 在功能上与不带 WHERE 子句的 DELETE 语句相同:二者均删除表中的全部行。但 TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少。DELETE 语句每次删除一行,并在事务日志中为所删除的每行记录一项。TRUNCATE TABLE 通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放。
(11) TRUNCATE TABLE 删除表中的所有行,但表结构及其列、约束、索引等保持不变。新行标识所用的计数值重置为该列的种子。如果想保留标识计数值,请改用 DELETE。如果要删除表定义及其数据,请使用 DROP TABLE 语句。
(12) 对于由 FOREIGN KEY 约束引用的表,不能使用 TRUNCATE TABLE,而应使用不带 WHERE 子句的 DELETE 语句。由于 TRUNCATE TABLE 不记录在日志中,所以它不能激活触发器。
一、delete
1、delete是DML,执行delete操作时,每次从表中删除一行,并且同时将该行的的删除操作记录在redo和undo表空间中以便进行回滚(rollback)和重做操作,但要注意表空间要足够大,需要手动提交(commit)操作才能生效,可以通过rollback撤消操作。
2、delete可根据条件删除表中满足条件的数据,如果不指定where子句,那么删除表中所有记录。
3、delete语句不影响表所占用的extent,高水线(high watermark)保持原位置不变。
二、truncate
1、truncate是DDL,会隐式提交,所以,不能回滚,不会触发触发器。
2、truncate会删除表中所有记录,并且将重新设置高水线和所有的索引,缺省情况下将空间释放到minextents个extent,除非使用reuse storage,。不会记录日志,所以执行速度很快,但不能通过rollback撤消操作(如果一不小心把一个表truncate掉,也是可以恢复的,只是不能通过rollback来恢复)。
3、对于外键(foreignkey )约束引用的表,不能使用 truncate table,而应使用不带 where 子句的 delete 语句。
4、truncatetable不能用于参与了索引视图的表。
三、drop
1、drop是DDL,会隐式提交,所以,不能回滚,不会触发触发器。
2、drop语句删除表结构及所有数据,并将表所占用的空间全部释放。
3、drop语句将删除表的结构所依赖的约束,触发器,索引,依赖于该表的存储过程/函数将保留,但是变为invalid状态。
总结:
1、在速度上,一般来说,drop> truncate > delete。
2、在使用drop和truncate时一定要注意,虽然可以恢复,但为了减少麻烦,还是要慎重。
3、如果想删除部分数据用delete,注意带上where子句,回滚段要足够大;
如果想删除表,当然用drop;
如果想保留表而将所有数据删除,如果和事务无关,用truncate即可;
如果和事务有关,或者想触发trigger,还是用delete;
如果是整理表内部的碎片,可以用truncate跟上reuse stroage,再重新导入/插入数据。
mysql数据库的默认字符集utf8,只能存储3个字节的数据。
标准的emoji表情是4个字节,在APP端输入保存表情是用户的普遍需求和行为。
插入数据库报错如下:
java.sql.SQLException: Incorrect string value: '\xF0\x9F\x92\xAA",...' for column 'raw_json' at row 1, 异常:org.springframework.jdbc.UncategorizedSQLException:
解决方式:更换字符集utf8-->utf8mb4 mb4的意思是most bytes 4,专门为兼容四个字节的。utf8mb4是向下兼容utf8的,所以即便修改了字段的字符集也不会影响线上数据。
前提:mysql大于5.5.3版本。
步骤: 1.修改字段的字符集
ALTER table mb_touchpay_record modify clientName varchar(100) character set utf8mb4 collate utf8mb4_unicode_ci
2.表 ALTER table mb_touchpay_record charset=utf8mb4;
3.库 set names utf8mb4
跟mysql版本有关系,如果是5.7的话,是几千万。但是5.7之后基本上就不用考虑数据量的问题了。但是这个问题问的不好,因为性能急剧下降不但但是跟数据量这一个因素有关系。
还有机器的配置,比如内存,如果内存放不下索引而把索引放在了虚拟内存上,那么效率就会急剧下降了。
还有就是sql建立合适的索引了。
如果查询到脏数据 或者没有的数据
mysql 就可能会“死掉”必须用net stop mysql 停止服务 ,
再用net start mysql 启动服务才行
①.读写分离的实现原理就是在执行SQL语句的时候,判断到底是读操作还是写操作,把读的操作转向到读的服务器上(从服务器,一般是多台),写的操作转到写的服务器上(主服务器,一般是一台),当然为了保证多台数据库数据的一致性,需要主从复制。
主从复制的实现原理是:
mysql中有一种日志,叫做bin日志(二进制日志),会记录下所有修改过数据库的sql语句。
主从复制的原理实际是多台服务器都开启bin日志,然后主服务器会把执行过的sql语句记录到bin日志中,之后从服务器读取这个bin日志,把该日志的内容保存到自己中继日志里面,从服务器再把中继日志中记录的sql语句同样的执行一遍,这样从服务器上的数据就和主服务器相同了。
②.中间件有淘宝开源的cobar,以及后来开源社区根据cobar进行二次开发的mycat
分表分库
隔离级别
对于SQL慢查询的优化?(主要是从查询语句和数据库表设计两个方面来考虑,查询语句方面可以增加索引,增加查询筛选的限制条件;数据库表设计的时候可以拆分表,设计得更细粒度。但是后来才发现面试官想要的就是查询大量数据的慢查询问题的优化。)
1.选择唯一性索引
唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。例如,学生表中学号是具有唯一性的字段。为该字段建立唯一性索引可以很快的确定某个学生的信息。如果使用姓名的话,可能存在同名现象,从而降低查询速度。
2.为经常需要排序、分组和联合操作的字段建立索引
经常需要ORDER BY、GROUP BY、DISTINCT和UNION等操作的字段,排序操作会浪费很多时间。如果为其建立索引,可以有效地避免排序操作。
3.为常作为查询条件的字段建立索引
如果某个字段经常用来做查询条件,那么该字段的查询速度会影响整个表的查询速度。因此,为这样的字段建立索引,可以提高整个表的查询速度。
4.限制索引的数目
索引的数目不是越多越好。每个索引都需要占用磁盘空间,索引越多,需要的磁盘空间就越大。修改表时,对索引的重构和更新很麻烦。越多的索引,会使更新表变得很浪费时间。
5.尽量使用数据量少的索引
如果索引的值很长,那么查询的速度会受到影响。例如,对一个CHAR(100)类型的字段进行全文检索需要的时间肯定要比对CHAR(10)类型的字段需要的时间要多。
6.尽量使用前缀来索引
如果索引字段的值很长,最好使用值的前缀来索引。例如,TEXT和BLOG类型的字段,进行全文检索会很浪费时间。如果只检索字段的前面的若干个字符,这样可以提高检索速度。
7.删除不再使用或者很少使用的索引
表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。
8 . 最左前缀匹配原则,非常重要的原则。
mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a 1=”” and=”” b=”2” c=”“> 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
9 .=和in可以乱序。
比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式
10 . 尽量选择区分度高的列作为索引。
区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就 是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条 记录
11 .索引列不能参与计算,保持列“干净”。
比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本 太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’);
12 .尽量的扩展索引,不要新建索引。
比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可
注意:选择索引的最终目的是为了使查询的速度变快。上面给出的原则是最基本的准则,但不能拘泥于上面的准则。读者要在以后的学习和工作中进行不断的实践。根据应用的实际情况进行分析和判断,选择最合适的索引方式。
索引的理解:
索引是对数据库表中的一列或多列的值进行排序的一种数据结构。
索引的作用就类似于书本的目录,新华字典的拼音,偏旁部首的首查字,可以快速的检索到需要的内容,mysql在300万条记录性能就下降了,虽然mysql官方文档说达500万~800万,所以当数据达到几百万的时候,那么索引就很有必要了。
当表中有大量记录的时候,若要对表进行查询,第一种就是就需要把表中的记录全部取出来,在和查询条件一一对比,然后返回满足条件的记录、这样做就会大大消耗数据库系统的时间,并造成大量磁盘I/O操作;第二种就是在表中建立索引,然后在索引中找到符合查询条件的索引值,最后通过保存在索引中ROWID(相当于页码)快速找到表中的记录。
语句:
CREATE TABLE table_name[filed_name data type]
[unique|fulltext][index|key][index_name](filed_name[length])[asc|desc]
unique|fulltext为可选参数,分别表示唯一索引、全文索引
index和key为同义词,两者作用相同,用来指定创建索引
filed_name为需要创建索引的字段列,该列必须从数据表中该定义的多个列中选择
index_name指定索引的名称,为可选参数,如果不指定,默认filed_name为索引值
length为可选参数,表示索引的长度,只有字符串类型的字段才能指定索引长度
asc或desc指定升序或降序的索引值存储
索引类型:
一、唯一索引(unique):列的值必须唯一,但允许有空值。如果是组合索引则列值必须唯一
1)、创建唯一索引
CREATE UNIQUE INDEX index_name ON table(column(length))
2)、修改表结构
ALTER TABLE table_name ADD UNIQUE INDEX index_name ON (column(length))
3)、创建表时直接指定
CREATE TABLE `table_name`(
`id` INT(11) NOT NULL AUTO_INCREMENT,
`code` INT(8) NOT NULL,
`title` VARCHAR(255) NOTNULL,
`content` TEXT,
`time` INT(10) NULL DEFAULT NULL,
UNIQUE index_name(code)
);
二、主键索引(primary key):是一特殊的唯一索引,一个表允许有一个主键,主键要求建表时指定.
CREATE TABLE `table_name`(
`id` INT(11) NOT NULL AUTO_INCREMENT,
`code` INT(8) NOT NULL,
`title` VARCHAR(255) NOTNULL,
`content` TEXT,
`time` INT(10) NULL DEFAULT NULL,
PRIMARY KEY(`id`)
);
三、普通索引(index):最基本的索引,没有任何限制。如果是char,varchar类型,length可以小于字段长度、如果是BLOB和TEXT类型,必须指定长度。
1)、直接创建
CREATE INDEX index_name ON table(column(length))
2)、修改表结构的方式添加
ALTER TABLE table_name ADD INDEX index_name ON (column(length))
3)、创建表时直接指定
CREATE TABLE `table_name`(
`id` INT(11) NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOTNULL,
`content` TEXT,
`time` INT(10) NULL DEFAULT NULL,
PRIMARY KEY(`id`),
INDEX index_name(title(length))
);
4)、删除索引
DROP INDEX index_name ON table
四、多列索引:指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段索引才会使用。使用组合索引遵循最左前缀原则
五、全文索引(fulltext):主要是用来查找文本的关键字,而不是直接与索引中的值比较。fulltext索引跟其他索引大不相同,它更像一个搜索引擎,而不是简单的where语句的参数匹配。fulltext要配合match against操作使用,而不是一般的where语句加like。它可以在create table、alter table、create index使用,不过目前只有char varchar text列上可以创建全文索引。
tip:全文索引只有MyISAM支持,不过在mysql5.6后Innodb也支持了
1)、创建表的时候添加全文索引
CREATE TABLE `table_name`(
`id` INT(11) NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOTNULL,
`content` TEXT,
`time` INT(10) NULL DEFAULT NULL,
FULLTEXT (content)
);
2)、修改表结构添加全文索引
ALTER TABLE table_name ADD FULLTEXT index_name(content)
3)、直接创建索引
CREATE FULLTEXT INDEX index_namet ON table_name(content)
缺点:
虽然索引大大的提升了查询的速度,同时也降低了对表的增删改的速度,因为更新表,不仅要保存数据,还要保存索引文件
建立索引会占用磁盘空间。一般不太严重,如果你在一个大表上创建多种组合索引,索引文件会增长很快索引只是提高效率的一个因素,如果有大数据量的表,就要花时间研究建立最优秀的索引,或优化查询语句
索引方式:
对于BTREE和HASH索引,当使用=、<=>、IN、IS NULL或者IS NOT NULL操作符时,关键元素与常量值的比较关系对应一个范围条件。Hash索引还有一些其它特征:它们只用于使用=或<=>操作符的等式比较(但很快)。优化器不能使用hash索引来加速ORDER BY操作。(该类索引不能用来按顺序搜索下一个条目)。MySQL不能确定在两个值之间大约有多少行(这被范围优化器用来确定使用哪个索引)。如果你将一个MyISAM表改为hash-索引的MEMORY表,会影响一些查询。只能使用整个关键字来搜索一行。(用B-树索引,任何关键字的最左面的前缀可用来找到行)。
对于BTREE索引,当使用>、<、>=、<=、BETWEEN、!=或者<>,或者LIKE ‘pattern’(其中 ‘pattern’不以通配符开始)操作符时,关键元素与常量值的比较关系对应一个范围条件。“常量值”系指:查询字符串中的常量、同一联接中的const或system表中的列、无关联子查询的结果、完全从前面类型的子表达式组成的表达式。
下面是一些WHERE子句中有范围条件的查询的例子。
下列范围查询适用于 btree索引和hash索引:
SELECT * FROM t1 WHERE key_col = 1 OR key_col IN (15,18,20);
下列范围查询适用于btree索引
SELECT * FROM t1 WHERE key_col > 1 AND key_col < 10;
SELECT * FROM t1 WHERE key_col LIKE 'ab%' OR key_col BETWEEN 'bar' AND 'foo';
创建索引原则与使用索引注意事项:
选择唯一索引
唯一索引的值是唯一的,可以更快速的通过该索引确定某一条记录
为经常需要排序、分组和联合操作的字段建立索引
经常需要order by、 group by、distinct和union等操作的字段,排序会浪费很多时间,如果为其建立索引,可以有效避免排序操作
经常作为查询条件的字段建立索引
如果某个字段经常作为查询条件,name该字段的查询速度就会影响整个表的速度。因此,为这样的字段建立索引,可以提高整个表的查询速度
限制索引的数目
索引的数目不是越多越好。每个索引都是要占用磁盘空间的,索引越多,需要的磁盘空间就越大。修改表时,和更新会很麻烦。越多的索引,会使更新表变得很浪费时间
尽量使用数据量少的索引
如果索引的值很长,那么查询的速度就会受到影响,例如对一个char(100)类型的字段进行全文检索需要的时间肯定要比char(10)类型的字段需要的时间多得多,并且char(10)全文检索而且可以节省磁盘空间和I/O操作。
尽量使用前缀来索引
如果索引字段的值很长,最好使用值的前缀来索引。例如,TEXT和BLOG类型的字段,进行全文检索会很浪费时间。如果只检索字段的前面的若干个字符,这样可以提高检索速度。
删除不再使用或者很少使用的索引
表中的数据被大量更新,或者数据使用方式被改变后,原有的一些索引可能不在需要。数据库管理员应当定期找出这些索引,将它们删除。从而减少索引对更新操作的影响
最左前缀原则,非常重要的一个原则
mysql会一直向右匹配知道遇到范围查询(>、<、between、like)就停止匹配,比如a=1 and b=2 and c>3 and d=4 如果建立(a,b,c,d)顺序索引,d是用不到索引的,如果是建立(a,b,d,c)则可以都用到
=和in可以乱序
比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式
尽量选择区分度高的列作为索引。
区分度的公式是count(distinct col)/count(*),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就 是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条 记录
索引列不能参与计算,保持列“干净”。
select * from users where YEAR(adddate)<'2007';
将在每个行上进行运算,这将导致索引失效而进行全表扫描,因此我们可以改成:
select * from users where adddate<‘2007-01-01';
尽量的扩展索引,不要新建索引。
比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可
不使用NOT IN和<>操作
like语句操作
一般情况下不鼓励使用like操作,如果非使用不可,如何使用也是一个问题。like “%aaa%” 不会使用索引而like “aaa%”可以使用索引。
索引列排序
MySQL查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。
索引不会包含有NULL值的列
只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL。
注意:选择索引的最终目的是为了使查询的速度变快。上面给出的原则是最基本的准则,但不能拘泥于上面的准则。读者要在以后的学习和工作中进行不断的实践。根据应用的实际情况进行分析和判断,选择最合适的索引方式。
索引类似大学图书馆建书目索引,可以提高数据检索的效率,降低数据库的IO成本。MySQL在300万条记录左右性能开始逐渐下降,虽然官方文档说500~800w记录,所以大数据量建立索引是非常有必要的。MySQL提供了Explain,用于显示SQL执行的详细信息,可以进行索引的优化。
一、导致SQL执行慢的原因:
1.硬件问题。如网络速度慢,内存不足,I/O吞吐量小,磁盘空间满了等。
2.没有索引或者索引失效。(一般在互联网公司,DBA会在半夜把表锁了,重新建立一遍索引,因为当你删除某个数据的时候,索引的树结构就不完整了。所以互联网公司的数据做的是假删除.一是为了做数据分析,二是为了不破坏索引 )
3.数据过多(分库分表)
4.服务器调优及各个参数设置(调整my.cnf)
二、分析原因时,一定要找切入点:
1.先观察,开启慢查询日志,设置相应的阈值(比如超过3秒就是慢SQL),在生产环境跑上个一天过后,看看哪些SQL比较慢。
2.Explain和慢SQL分析。比如SQL语句写的烂,索引没有或失效,关联查询太多(有时候是设计缺陷或者不得以的需求)等等。
3.Show Profile是比Explain更近一步的执行细节,可以查询到执行每一个SQL都干了什么事,这些事分别花了多少秒。
4.找DBA或者运维对MySQL进行服务器的参数调优。
三、什么是索引?
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。我们可以简单理解为:快速查找排好序的一种数据结构。Mysql索引主要有两种结构:B+Tree索引和Hash索引。我们平常所说的索引,如果没有特别指明,一般都是指B树结构组织的索引(B+Tree索引)。索引如图所示:
最外层浅蓝色磁盘块1里有数据17、35(深蓝色)和指针P1、P2、P3(黄色)。P1指针表示小于17的磁盘块,P2是在17-35之间,P3指向大于35的磁盘块。真实数据存在于子叶节点也就是最底下的一层3、5、9、10、13......非叶子节点不存储真实的数据,只存储指引搜索方向的数据项,如17、35。
查找过程:例如搜索28数据项,首先加载磁盘块1到内存中,发生一次I/O,用二分查找确定在P2指针。接着发现28在26和30之间,通过P2指针的地址加载磁盘块3到内存,发生第二次I/O。用同样的方式找到磁盘块8,发生第三次I/O。
真实的情况是,上面3层的B+Tree可以表示上百万的数据,上百万的数据只发生了三次I/O而不是上百万次I/O,时间提升是巨大的。
四、Explain分析
前文铺垫完成,进入实操部分,先来插入测试需要的数据:
初体验,执行Explain的效果:
索引使用情况在possible_keys、key和key_len三列,接下来我们先从左到右依次讲解。
--id相同,执行顺序由上而下explain select u.*,o.* from user_info u,order_info o where u.id=o.user_id;
--id不同,值越大越先被执行explain select * from user_info where id=(select user_id from order_info where product_name ='p8');
可以看id的执行实例,总共有以下几种类型:
SIMPLE: 表示此查询不包含 UNION 查询或子查询
PRIMARY: 表示此查询是最外层的查询
SUBQUERY: 子查询中的第一个 SELECT
UNION: 表示此查询是 UNION 的第二或随后的查询
DEPENDENT UNION: UNION 中的第二个或后面的查询语句, 取决于外面的查询
UNION RESULT, UNION 的结果
DEPENDENT SUBQUERY: 子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查询的结果.
DERIVED:衍生,表示导出表的SELECT(FROM子句的子查询)
table表示查询涉及的表或衍生的表:
explain select tt.* from (select u.* from user_info u,order_info o where u.id=o.user_id and u.id=1) tt
id为1的
type 字段比较重要,它提供了判断查询是否高效的重要依据依据。 通过 type 字段,我们判断此次查询是 全表扫描 还是 索引扫描等。
type 常用的取值有:
system: 表中只有一条数据, 这个类型是特殊的 const 类型。
const: 针对主键或唯一索引的等值查询扫描,最多只返回一行数据。 const 查询速度非常快, 因为它仅仅读取一次即可。例如下面的这个查询,它使用了主键索引,因此 type 就是 const 类型的:explain select * from user_info where id = 2;
eq_ref: 此类型通常出现在多表的 join 查询,表示对于前表的每一个结果,都只能匹配到后表的一行结果。并且查询的比较操作通常是 =,查询效率较高。例如:explain select * from user_info, order_info where user_info.id = order_info.user_id;
ref: 此类型通常出现在多表的 join 查询,针对于非唯一或非主键索引,或者是使用了 最左前缀 规则索引的查询。例如下面这个例子中, 就使用到了 ref 类型的查询:explain select * from user_info, order_info where user_info.id = order_info.user_id AND order_info.user_id = 5
range: 表示使用索引范围查询,通过索引字段范围获取表中部分数据记录。这个类型通常出现在 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, IN() 操作中。例如下面的例子就是一个范围查询:explain select * from user_info where id between 2 and 8;
index: 表示全索引扫描(full index scan),和 ALL 类型类似,只不过 ALL 类型是全表扫描,而 index 类型则仅仅扫描所有的索引, 而不扫描数据。index 类型通常出现在:所要查询的数据直接在索引树中就可以获取到, 而不需要扫描数据。当是这种情况时,Extra 字段 会显示 Using index。
ALL: 表示全表扫描,这个类型的查询是性能最差的查询之一。通常来说, 我们的查询不应该出现 ALL 类型的查询,因为这样的查询在数据量大的情况下,对数据库的性能是巨大的灾难。 如一个查询是 ALL 类型查询, 那么一般来说可以对相应的字段添加索引来避免。
通常来说, 不同的 type 类型的性能关系如下:
ALL < index < range ~ index_merge < ref < eq_ref < const < system
ALL 类型因为是全表扫描, 因此在相同的查询条件下,它是速度最慢的。而 index 类型的查询虽然不是全表扫描,但是它扫描了所有的索引,因此比 ALL 类型的稍快.后面的几种类型都是利用了索引来查询数据,因此可以过滤部分或大部分数据,因此查询效率就比较高了。
它表示 mysql 在查询时,可能使用到的索引。 注意,即使有些索引在 possible_keys 中出现,但是并不表示此索引会真正地被 mysql 使用到。 mysql 在查询时具体使用了哪些索引,由 key 字段决定。
此字段是 mysql 在当前查询时所真正使用到的索引。比如请客吃饭,possible_keys是应到多少人,key是实到多少人。当我们没有建立索引时:
explain select o.* from order_info o where o.product_name= 'p1' and o.productor='whh';create index idx_name_productor on order_info(productor);drop index idx_name_productor on order_info;
建立复合索引后再查询:
表示查询优化器使用了索引的字节数,这个字段可以评估组合索引是否完全被使用。
这个表示显示索引的哪一列被使用了,如果可能的话,是一个常量。前文的type属性里也有ref,注意区别。
rows 也是一个重要的字段,mysql 查询优化器根据统计信息,估算 sql 要查找到结果集需要扫描读取的数据行数,这个值非常直观的显示 sql 效率好坏, 原则上 rows 越少越好。可以对比key中的例子,一个没建立索引钱,rows是9,建立索引后,rows是4。
explain 中的很多额外的信息会在 extra 字段显示, 常见的有以下几种内容:
using filesort :表示 mysql 需额外的排序操作,不能通过索引顺序达到排序效果。一般有 using filesort都建议优化去掉,因为这样的查询 cpu 资源消耗大。
using index:覆盖索引扫描,表示查询在索引树中就可查找所需数据,不用扫描表数据文件,往往说明性能不错。
using temporary:查询有使用临时表, 一般出现于排序, 分组和多表 join 的情况, 查询效率不高,建议优化。
using where :表名使用了where过滤。
五、优化案例
explain select u.*,o.* from user_info u LEFT JOIN order_info o on u.id=o.user_id;
执行结果,type有ALL,并且没有索引:
开始优化,在关联列上创建索引,明显看到type列的ALL变成ref,并且用到了索引,rows也从扫描9行变成了1行:
这里面一般有个规律是:左链接索引加在右表上面,右链接索引加在左表上面。
六、是否需要创建索引?
索引虽然能非常高效的提高查询速度,同时却会降低更新表的速度。实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的。
select * from 表 order by 要最大值的字段 desc limit 0,10
SELECT DISTINCT
FROM
JOIN
ON
WHERE
GROUP BY
HAVING
ORDER BY
LIMIT
如果你知道每个关键字的意思,作用,如果你还用过的话,那再好不过了。但是,你知道这些语句,它们的执行顺序你清楚么?如果你非常清楚,你就没有必要再浪费时间继续阅读了;如果你不清楚,非常好,你应该庆幸你阅读到了这么好的一篇文章。
SQL逻辑查询语句执行顺序
还记得上面给出的那一长串的SQL逻辑查询规则么?那么,到底哪个先执行,哪个后执行呢?现在,我先给出一个查询语句的执行顺序:
(7) SELECT
(8) DISTINCT
(1) FROM
(3) JOIN
(2) ON
(4) WHERE
(5) GROUP BY
(6) HAVING
(9) ORDER BY
(10) LIMIT
上面在每条语句的前面都标明了执行顺序号,不要问我怎么知道这个顺序的。我也是读各种“武林秘籍”才得知的,如果你有功夫,去阅读一下MySQL的源码,也会得出这个结果的。
好了,上面我标出了各条查询规则的执行先后顺序,那么各条查询语句是如何执行的呢?这就是我今天这篇博文的重点内容。Go on…
执行FROM语句
在这些SQL语句的执行过程中,都会产生一个虚拟表,用来保存SQL语句的执行结果(这是重点),我现在就来跟踪这个虚拟表的变化,得到最终的查询结果的过程,来分析整个SQL逻辑查询的执行顺序和过程。
第一步,执行FROM语句。我们首先需要知道最开始从哪个表开始的,这就是FROM告诉我们的。现在有了
总共有28(table1的记录条数 * table2的记录条数)条记录。这就是VT1的结果,接下来的操作就在VT1的基础上进行。
执行ON过滤
执行完笛卡尔积以后,接着就进行ON a.customer_id = b.customer_id条件过滤,根据ON中指定的条件,去掉那些不符合条件的数据,得到VT2表,内容如下:
VT2就是经过ON条件筛选以后得到的有用数据,而接下来的操作将在VT2的基础上继续进行。
添加外部行
这一步只有在连接类型为OUTER JOIN时才发生,如LEFT OUTER JOIN、RIGHT OUTER JOIN和FULL OUTER JOIN。在大多数的时候,我们都是会省略掉OUTER关键字的,但OUTER表示的就是外部行的概念。
LEFT OUTER JOIN把左表记为保留表,得到的结果为:
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| 163 | hangzhou | 1 | 163 |
| 163 | hangzhou | 2 | 163 |
| 9you | shanghai | 3 | 9you |
| 9you | shanghai | 4 | 9you |
| 9you | shanghai | 5 | 9you |
| tx | hangzhou | 6 | tx |
| baidu | hangzhou | NULL | NULL |
+-------------+----------+----------+-------------+
RIGHT OUTER JOIN把右表记为保留表,得到的结果为:
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| 163 | hangzhou | 1 | 163 |
| 163 | hangzhou | 2 | 163 |
| 9you | shanghai | 3 | 9you |
| 9you | shanghai | 4 | 9you |
| 9you | shanghai | 5 | 9you |
| tx | hangzhou | 6 | tx |
| NULL | NULL | 7 | NULL |
+-------------+----------+----------+-------------+
FULL OUTER JOIN把左右表都作为保留表,得到的结果为:
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| 163 | hangzhou | 1 | 163 |
| 163 | hangzhou | 2 | 163 |
| 9you | shanghai | 3 | 9you |
| 9you | shanghai | 4 | 9you |
| 9you | shanghai | 5 | 9you |
| tx | hangzhou | 6 | tx |
| baidu | hangzhou | NULL | NULL |
| NULL | NULL | 7 | NULL |
+-------------+----------+----------+-------------+
添加外部行的工作就是在VT2表的基础上添加保留表中被过滤条件过滤掉的数据,非保留表中的数据被赋予NULL值,最后生成虚拟表VT3。
由于我在准备的测试SQL查询逻辑语句中使用的是LEFT JOIN,过滤掉了以下这条数据:
| baidu | hangzhou | NULL | NULL |
现在就把这条数据添加到VT2表中,得到的VT3表如下:
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| 163 | hangzhou | 1 | 163 |
| 163 | hangzhou | 2 | 163 |
| 9you | shanghai | 3 | 9you |
| 9you | shanghai | 4 | 9you |
| 9you | shanghai | 5 | 9you |
| tx | hangzhou | 6 | tx |
| baidu | hangzhou | NULL | NULL |
+-------------+----------+----------+-------------+
接下来的操作都会在该VT3表上进行。
执行WHERE过滤
对添加外部行得到的VT3进行WHERE过滤,只有符合
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| 163 | hangzhou | 1 | 163 |
| 163 | hangzhou | 2 | 163 |
| tx | hangzhou | 6 | tx |
| baidu | hangzhou | NULL | NULL |
+-------------+----------+----------+-------------+
但是在使用WHERE子句时,需要注意以下两点:
执行GROUP BY分组
GROU BY子句主要是对使用WHERE子句得到的虚拟表进行分组操作。我们执行测试语句中的GROUP BY a.customer_id,就会得到以下内容:
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| 163 | hangzhou | 1 | 163 |
| baidu | hangzhou | NULL | NULL |
| tx | hangzhou | 6 | tx |
+-------------+----------+----------+-------------+
得到的内容会存入虚拟表VT5中,此时,我们就得到了一个VT5虚拟表,接下来的操作都会在该表上完成。
执行HAVING过滤
HAVING子句主要和GROUP BY子句配合使用,对分组得到的VT5虚拟表进行条件过滤。当我执行测试语句中的HAVING count(b.order_id) < 2时,将得到以下内容:
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| baidu | hangzhou | NULL | NULL |
| tx | hangzhou | 6 | tx |
+-------------+----------+----------+-------------+
这就是虚拟表VT6。
SELECT列表
现在才会执行到SELECT子句,不要以为SELECT子句被写在第一行,就是第一个被执行的。
我们执行测试语句中的SELECT a.customer_id, COUNT(b.order_id) as total_orders,从虚拟表VT6中选择出我们需要的内容。我们将得到以下内容:
+-------------+--------------+
| customer_id | total_orders |
+-------------+--------------+
| baidu | 0 |
| tx | 1 |
+-------------+--------------+
不,还没有完,这只是虚拟表VT7。
执行DISTINCT子句
如果在查询中指定了DISTINCT子句,则会创建一张内存临时表(如果内存放不下,就需要存放在硬盘了)。这张临时表的表结构和上一步产生的虚拟表VT7是一样的,不同的是对进行DISTINCT操作的列增加了一个唯一索引,以此来除重复数据。
由于我的测试SQL语句中并没有使用DISTINCT,所以,在该查询中,这一步不会生成一个虚拟表。
执行ORDER BY子句
对虚拟表中的内容按照指定的列进行排序,然后返回一个新的虚拟表,我们执行测试SQL语句中的ORDER BY total_orders DESC,就会得到以下内容:
+-------------+--------------+
| customer_id | total_orders |
+-------------+--------------+
| tx | 1 |
| baidu | 0 |
+-------------+--------------+
可以看到这是对total_orders列进行降序排列的。上述结果会存储在VT8中。
执行LIMIT子句
LIMIT子句从上一步得到的VT8虚拟表中选出从指定位置开始的指定行数据。对于没有应用ORDER BY的LIMIT子句,得到的结果同样是无序的,所以,很多时候,我们都会看到LIMIT子句会和ORDER BY子句一起使用。
MySQL数据库的LIMIT支持如下形式的选择:
LIMIT n, m
表示从第n条记录开始选择m条记录。而很多开发人员喜欢使用该语句来解决分页问题。对于小数据,使用LIMIT子句没有任何问题,当数据量非常大的时候,使用LIMIT n, m是非常低效的。因为LIMIT的机制是每次都是从头开始扫描,如果需要从第60万行开始,读取3条数据,就需要先扫描定位到60万行,然后再进行读取,而扫描的过程是一个非常低效的过程。所以,对于大数据处理时,是非常有必要在应用层建立一定的缓存机制。
为什么要使用索引?
索引这么多优点,为什么不对表中的每一个列创建一个索引呢?
索引是如何提高查询速度的?
将无序的数据变成相对有序的数据(就像查目录一样)
说一下使用索引的注意事项
Mysql索引主要使用的哪两种数据结构?
什么是覆盖索引?
如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称
之为“覆盖索引”。我们知道在InnoDB存储引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次,这样就会比较慢。覆盖索引就是把要查询出的列和索引是对应的,不做回表操作!