往期热门文章:

1、《往期精选优秀博文都在这里了!》2、终于放弃了Maven,我选择用Gradle!3、Kotlin 越来越牛逼了!学Java的我想转了!4、骚操作:不重启 JVM,如何替换掉已经加载的类?5、应用程序被拖慢?罪魁祸首竟然是Log4j!

作者:张飞洪

cnblogs.com/jackyfei/p/12122767.html

经常有同学问我,我的一个SQL语句使用了索引,为什么还是会进入到慢查询之中呢?今天我们就从这个问题开始来聊一聊索引和慢查询。

另外插入一个题外话,个人认为团队要合理的使用ORM,可以参考 ORM的权衡和抉择。合理利用的是ORM在面向对象和写操作方面的优势,避免联合查询上可能产生的坑(当然如果你的Linq查询能力很强另当别论),因为ORM屏蔽了太多的DB底层的知识内容,对程序员不是件好事,对性能有极致追求,但是ORM理解不透彻的团队更加要谨慎。

案例剖析 

言归正传,为了实验,我创建了如下表:

CREATE TABLE `T`(`id` int(11) NOT NULL,`a` int(11) DEFAUT NULL,PRIMARY KEY(`id`),KEY `a`(`a`)) ENGINE=InnoDB;

该表有三个字段,其中用id是主键索引,a是普通索引。

首先SQL判断一个语句是不是慢查询语句,用的是语句的执行时间。他把语句执行时间跟long_query_time这个系统参数作比较,如果语句执行时间比它还大,就会把这个语句记录到慢查询日志里面,这个参数的默认值是10秒。当然在生产上,我们不会设置这么大,一般会设置1秒,对于一些比较敏感的业务,可能会设置一个比1秒还小的值。

语句执行过程中有没有用到表的索引,可以通过explain一个语句的输出结果来看KEY的值不是NULL。

我们看下 explain select * from t;的KEY结果是NULL

  (图一)

explain select * from t where id=2;的KEY结果是PRIMARY,就是我们常说的使用了主键索引

 (图二)

explain select a from t;的KEY结果是a,表示使用了a这个索引。

 (图三)

虽然后两个查询的KEY都不是NULL,但是最后一个实际上扫描了整个索引树a。

假设这个表的数据量有100万行,图二的语句还是可以执行很快,但是图三就肯定很慢了。如果是更极端的情况,比如,这个数据库上CPU压力非常的高,那么可能第2个语句的执行时间也会超过long_query_time,会进入到慢查询日志里面。

所以我们可以得出一个结论:是否使用索引和是否进入慢查询之间并没有必然的联系。使用索引只是表示了一个SQL语句的执行过程,而是否进入到慢查询是由它的执行时间决定的,而这个执行时间,可能会受各种外部因素的影响。换句话来说,使用了索引你的语句可能依然会很慢。

全索引扫描的不足

那如果我们在更深层次的看这个问题,其实他还潜藏了一个问题需要澄清,就是什么叫做使用了索引。

我们都知道,InnoDB是索引组织表,所有的数据都是存储在索引树上面的。比如上面的表t,这个表包含了两个索引,一个主键索引和一个普通索引。在InnoDB里,数据是放在主键索引里的。如图所示:

可以看到数据都放在主键索引上,如果从逻辑上说,所有的InnoDB表上的查询,都至少用了一个索引,所以现在我问你一个问题,如果你执行select from t where id>0,你觉得这个语句有用上索引吗?

我们看上面这个语句的explain的输出结果显示的是PRIMARY。其实从数据上你是知道的,这个语句一定是做了全面扫描。但是优化器认为,这个语句的执行过程中,需要根据主键索引,定位到第1个满足ID>0的值,也算用到了索引。

所以即使explain的结果里写的KEY不是NULL,实际上也可能是全表扫描的,因此InnoDB里面只有一种情况叫做没有使用索引,那就是从主键索引的最左边的叶节点开始,向右扫描整个索引树。

也就是说,没有使用索引并不是一个准确的描述。

你可以用全表扫描来表示一个查询遍历了整个主键索引树;

也可以用全索引扫描,来说明像select a from t;这样的查询,他扫描了整个普通索引树;

而select * from t where id=2这样的语句,才是我们平时说的使用了索引。他表示的意思是,我们使用了索引的快速搜索功能,并且有效的减少了扫描行数。

索引的过滤性要足够好

根据以上解剖,我们知道全索引扫描会让查询变慢,接下来就要来谈谈索引的过滤性。

假设你现在维护了一个表,这个表记录了中国14亿人的基本信息,现在要查出所有年龄在10~15岁之间的姓名和基本信息,那么你的语句会这么写,select * from t_people where age between 10 and 15

你一看这个语句一定要在age字段上开始建立索引了,否则就是个全面扫描,但是你会发现,在你建立索引以后,这个语句还是执行慢,因为满足这个条件的数据可能有超过1亿行。

我们来看看建立索引以后,这个表的组织结构图:

这个语句的执行流程是这样的:

从索引上用树搜索,取到第1个age等于10的记录,得到它的主键id的值,根据id的值去主键索引取整行的信息,作为结果集的一部分返回;

在索引age上向右扫描,取下一个id的值,到主键索引上取整行信息,作为结果集的一部分返回;

重复上面的步骤,直到碰到第1个age大于15的记录;

你看这个语句,虽然他用了索引,但是他扫描超过了1亿行。所以你现在知道了,当我们在讨论有没有使用索引的时候,其实我们关心的是扫描行数。

对于一个大表,不止要有索引,索引的过滤性还要足够好。

像刚才这个例子的age,它的过滤性就不够好,在设计表结构的时候,我们要让所有的过滤性足够好,也就是区分度足够高。

回表的代价

那么过滤性好了,是不是表示查询的扫描行数就一定少呢?

我们再来看一个例子:

如果你的执行语句是 select * from t_people where name='张三' and age=8

t_people表上有一个索引是姓名和年龄的联合索引,那这个联合索引的过滤性应该不错,可以在联合索引上快速找到第1个姓名是张三,并且年龄是8的小朋友,当然这样的小朋友应该不多,因此向右扫描的行数很少,查询效率就很高。

但是查询的过滤性和索引的过滤性可不一定是一样的,如果现在你的需求是查出所有名字的第1个字是张,并且年龄是8岁的所有小朋友,你的语句会怎么写呢?

你的语句要怎么写?很显然你会这么写:select * from t_people where name like '张%' and age=8;

在MySQL5.5和之前的版本中,这个语句的执行流程是这样的:

首先从联合索引上找到第1个年龄字段是张开头的记录,取出主键id,然后到主键索引树上,根据id取出整行的值;

判断年龄字段是否等于8,如果是就作为结果集的一行返回,如果不是就丢弃。

在联合索引上向右遍历,并重复做回表和判断的逻辑,直到碰到联合索引树上名字的第1个字不是张的记录为止。

我们把根据id到主键索引上查找整行数据这个动作,称为回表。你可以看到这个执行过程里面,最耗费时间的步骤就是回表,假设全国名字第1个字是张的人有8000万,那么这个过程就要回表8000万次,在定位第一行记录的时候,只能使用索引和联合索引的最左前缀,最称为最左前缀原则。

你可以看到这个执行过程,它的回表次数特别多,性能不够好,有没有优化的方法呢?

在MySQL5.6版本,引入了index condition pushdown的优化。我们来看看这个优化的执行流程:

首先从联合索引树上,找到第1个年龄字段是张开头的记录,判断这个索引记录里面,年龄的值是不是8,如果是就回表,取出整行数据,作为结果集的一部分返回,如果不是就丢弃;

在联合索引树上,向右遍历,并判断年龄字段后,根据需要做回表,直到碰到联合索引树上名字的第1个字不是张的记录为止;

这个过程跟上面的差别,是在遍历联合索引的过程中,将年龄等于8的条件下推到所有遍历的过程中,减少了回表的次数,假设全国名字第1个字是张的人里面,有100万个是8岁的小朋友,那么这个查询过程中在联合索引里要遍历8000万次,而回表只需要100万次。

虚拟列

可以看到这个优化的效果还是很不错的,但是这个优化还是没有绕开最左前缀原则的限制,因此在联合索引你还是要扫描8000万行,那有没有更进一步的优化方法呢?

我们可以考虑把名字的第一个字和age来做一个联合索引。这里可以使用MySQL5.7引入的虚拟列来实现。对应的修改表结构的SQL语句:

alter table t_people add name_first varchar(2) generated (left(name,1)),add index(name_first,age);

我们来看这个SQL语句的执行效果:

CREATE TABLE `t_people`(`id` int(11) DEFAULT NULL,`name` varchar(20) DEFAUT NULL,`name_first` varchar(2) GENERATED ALWAYS AS (left(`name`,1)) VIRTUAL,KEY `name_first`(`name_first`,'age')) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

首先他在people上创建一个字段叫name_first的虚拟列,然后给name_first和age上创建一个联合索引,并且,让这个虚拟列的值总是等于name字段的前两个字节,虚拟列在插入数据的时候不能指定值,在更新的时候也不能主动修改,它的值会根据定义自动生成,在name字段修改的时候也会自动修改。

有了这个新的联合索引,我们在找名字的第1个字是张,并且年龄为8的小朋友的时候,这个SQL语句就可以这么写:select * from t_people where name_first='张' and age=8。

这样这个语句的执行过程,就只需要扫描联合索引的100万行,并回表100万次,这个优化的本质是我们创建了一个更紧凑的索引,来加速了查询的过程。

总结

本文给你介绍了索引的基本结构和一些查询优化的基本思路,你现在知道了,使用索引的语句也有可能是慢查询,我们的查询优化的过程,往往就是减少扫描行数的过程。

慢查询归纳起来大概有这么几种情况:

  • 全表扫描

  • 全索引扫描

  • 索引过滤性不好

  • 频繁回表的开销

思考

假设业务要求的就是要统计年龄在10-15岁的14亿人的数量,不能增加过滤因子,那该怎么办?(select * from t_people where age between 10 and 15)

假设该统计必须是OLTP,实时展示统计数据,又该怎么解决?

往期热门文章:

1、《历史文章分类导读列表!精选优秀博文都在这里了!》

2、Docker 禁止被列入美国“实体名单”的国家、企业、个人使用3、日志框架到底是Logback 还是 Log4j2?4、IDEA 2020.2 重磅发布,动画级新功能预览!5、数据库链接池终于搞对了,这次直接从100ms优化到3ms!

6、互联网公司忽悠员工的黑话,套路太深了。。。7、两难!到底用 Spring BeanUtils 还是 Apache BeanUtils?8、滴滴开源了哪些有意思的项目?有点牛脾~9、图解Spring循环依赖,看过之后面试再也不用慌了!10、他来了!IDEA 2020.1 新版介绍!不过升级前请注意避坑!

not null primary key什么意思_为什么我使用了索引,查询还是慢?相关推荐

  1. not null primary key什么意思_为什么我使用了索引,索引却没有生效?

    关注我们,设为星标,每天7:00不见不散,每日java干货分享 作者:张飞洪 https://www.cnblogs.com/jackyfei/p/12122767.html 经常有同学问我,我的一个 ...

  2. not null primary key什么意思_explain都不会用,你还好意思说精通Mysql查询优化?

    Explain简介 Explain关键字是Mysql中sql优化的常用「关键字」,通常都会使用Explain来「查看sql的执行计划,而不用执行sql」,从而快速的找出sql的问题所在. 在讲解Exp ...

  3. mysql primary key 多个_关于mysql中primary key重复的解决方法

    我们都知道MySQL数据库中是讲究primarykey的唯一性的,如果primarykey出现了重复,则会影响其他的表制定的规则. 今天我们要和大家一起分享的是Mysql数据库中primarykey重 ...

  4. 数据库六大约束用法:主键(primary key)、外键(foreign key)、非空(not null)、默认(default)、检查(check)、唯一(unique)

    1. 数据库有六大约束 主键(primary key) 外键(foreign key):被参照的键必须有唯一约束或是主键 非空(not null) 默认(default) 检查(check):orac ...

  5. SQL_菜鸟教程_unique、primary key、foreign key

    SQL_unique.primary key.FOREIGN KEY insert into select create 约束Constraints *** not null_添加.修改 unique ...

  6. mysql alter table drop primary key_删除主键: Alter table tabname drop primary key(col)

    下列语句部分是Mssql语句,不可以在access中使用.|4J,Y,FzYS*q A0x051Testing软件测试网)c#QS"?f{ SQL分类:d2HM[]$rw0DDL-数据定义语 ...

  7. key mysql_mysql中key 、primary key 、unique key 与index区别

    mysql中索引是非常重要的知识点,相比其他的知识点,索引更难掌握,并且mysql中的索引种类也有很多,比如primary key .unique key 与index等等,本文章向大家介绍mysql ...

  8. Discuz升级 Database Error : pre_common_syscache ADD PRIMARY KEY (cname)【解决办法】

    错误码: 1068 Multiple primary key defined Execution Time : 00:00:00:000 Transfer Time : 00:00:00:000 To ...

  9. mysql创建数据库时使用sql/wordbench使主键(primary key)自增

    sql `id` int(4) primary key not null auto_increment 例: CREATE TABLE `supplier_recommand`.`new_table` ...

最新文章

  1. eclipse注释模板设置(未整理)
  2. fetch使用的常见问题及解决办法
  3. excel图表交互联动_如何使用高大上的多级联动交互式图表来分析人员结构?
  4. python 残差图_python 残差图
  5. linux 下简单的ftp客户端程序
  6. APPLE苹果电子设备模型样机|展示你的专业设计最佳选择
  7. 随笔写一个简单的爬虫
  8. IOS开发UI控件UIScrollView和Delegate的使用
  9. Castle Team宣布Castle将与ASP.NET MVC整合
  10. android带动画的饼图,Android部分源码资源共享(视屏转GIF图片工具、仿抖音、仿朋友圈、仿红包、饼状图、引导图,图灵源码等)...
  11. Python 读取文本文件编码错误解决方案(未知文本文件编码情况下解决方案)
  12. 互联网企业:如何建设数据安全体系?
  13. easy excel 设置某一列的格式
  14. ubuntu安装翻译软件 stardict
  15. C#中路径表示\ 和 /
  16. 绕流运动与附面层基本概念
  17. 关键词挖掘的方法和技巧
  18. Linux服务器压测/拷机软件收集
  19. NameNode HA的部署方法
  20. Sublime Text 3 配置python开发环境遇见的问题

热门文章

  1. javascript调用alert()
  2. jsp之servlet模板问题
  3. JNI之常用函数大全
  4. Eclipse快捷键_10个最高效的快捷键
  5. MVC3.0 如何点击点击一张图片连接到另一地址
  6. 两个必备小本领——恢复设备出厂配置、如何配置web方式登陆交换机
  7. centos手动增加删除swap分区
  8. Linux环境下虚拟化之KVM常用命令
  9. 配置文件中有“路径信息”时,需呀注意的问题(路径中的\是转义字符)~
  10. 【PL/SQL】用星号拼出金字塔