MySQL如何构建数据表索引


Posted in MySQL onMay 13, 2021

理解索引概念最简单的方式是通过一个案例来进行,以下就是这样的一个案例。

假设我们需要设计一个在线的约会网站,这个网站的用户资料有许多列,例如国籍、省份、城市、性别、年龄、眼睛颜色等等。这个网站必须支持通过多种组合方式搜索用户资料。同时,也需要支持支持排序和根据用户最近在线时间和其他用户的评价返回有限的结果等等。对于这种复杂场景我们如何设计索引?

有点奇怪,首先要做的事情是要决定我们是否必须使用索引排序,或者检索后再排序是否能够接受。索引排序限制了索引和查询构建的方式。例如,在WHERE age BETWEEN 18 AND 25这样的查询条件和基于其他用户评价排序的场景中,我们不能使用同一个索引。如果MySQL在范围查询中使用了一个索引,那就没法在排序中使用另一个索引。假设这是一个最常用的WHERE条件,同时我们还需要支持大多数查询都可以排序。

支持多种类型的过滤

现在我们需要看看哪些列的值比较分散以及哪些列在WHERE条件中最常出现。数据列值比较分散的筛选性很好。这通常会是一个好事情,因为这让MySQL可以将高效过滤掉不相关的数据行。

国籍列可能筛选性不太好,但却可能是最常查询的。性别列通常不具备筛选性,但却也经常用于查询。基于这样的认识,我们为许多不同的列的组合创建了一系列的索引,这些索引使用(sex, country)开头。

传统的认知是对于低筛选性的列构建索引是没用的。那我们为什么要在每个索引开头都加上不具筛选性的列? 我们有两个理由这么做。第一个理由是,如前所述,基本每个查询都会使用性别。我们甚至设计了用户一次只能搜索一个性别。但更重要的是,增加这样的列并没有多少缺点,因为我们使用了一个小招数。

这是我们的招数:即便不限制性别查询,我们也能够保证在WHERE语句中加上AND sex IN('m', 'f')让索引生效。这不会过滤掉我们所需要的行,因此与WHERE语句中不包含性别作用相同。然而,因为MySQL会在更多列的索引中前置这个列,我们需要包含这个列。这个招术在这样的场景下有效,但是如果是这个列具有很多不同的值,那反而不起作用,这是因为这会导致IN()中的列过多。

这个例子阐述了一个基本的原则:在数据表设计上保留所有的选项。当你设计索引的时候,不要只想着那种查询中的那类索引,也同时考虑优化查询。当你需要一个索引却发现其他查询可能会受其影响,你应该先问问自己能否改变查询。你应该同时优化查询和索引去找到解决之道。你不一定需要设计完美的索引。

接下来,我们需要考虑可能用到的其他组合的WHERE条件,然后考虑其中的哪些组合在没有合理索引的情况下会变慢。(sex, country, age)这样的索引是很明显的选择,但我们也可能需要(sex, country, region, age)和(sex, country, region, city, age)这样的索引。

这会导致需要建立很多的索引。如果我们能够重复利用索引,那就不会产生过多的组合。我们可以使用IN()这种小招数来去掉(sex, country, age)和(sex, country, region, age)索引。如果这些列在搜索表单中没有指定,我们可以使用国家清单、地区清单来保证满足索引前置的约束(全部国家,全部地区和全部性别的组合可能很多)。

这些索引会满足指定的大部分搜索查询,但我们如何设计那些不那么常见的筛选,例如上传了图片(has_pictures),眼睛颜色(eye_color),头发颜色(hair_color)和教育水平(education)?如果这些列不是那么具有筛选性并且不那么常用,我们可以直接跳过他们,让MySQL去扫描额外的一些数据行。相应地,我们可以在age列前增加他们,并且使用IN()技巧去提前描述以处理那种这些列没有指定的情况。

你也许注意到我们将age放到了索引的最后面。为什么要特别处理这个列?我们在试图保证MySQL能够尽可能多地利用索引列。由于MySQL使用最左匹配规则,直到遇到第一个范围查询条件。所有我们提到的列都可以在WHERE语句中使用相等条件,但年龄(age)大概率是范围查询。

我们也能够将范围查询改为清单使用IN查询,例如age IN(18, 19, 20, 21, 22, 23, 24, 25)来替代age BETWEEN 18 AND 25,但这并不总是能够这么做。通用的原则是我们尽量将范围判决条件放到索引的末尾,因此优化器会尽可能地使用索引。

我们提到你可以使用尽可能多的列使用IN查询去覆盖那些在WHERE条件中未指定的索引条件。但你可能做得过头了导致新的问题。使用更多的这样的IN查询清单导致优化器需要评估大量的组合,这反而可能降低查询速度。考虑下面的查询条件语句:

WHERE eye_color 	IN('brown', 'blue', 'hazel')
	AND hair_color	IN('black', 'red', 'blonde', 'brown')
  	AND sex 	IN('M', 'F')

这个优化器会转变为432=24种组合,WHERE条件会检查每一种情况。24还不是一个很大的组合数字,但如果数量达到了几千。旧版本的MySQL在IN查询中数量过多时可能会有更多的问题。查询优化器会执行更慢并且消耗很多内存。新版本的MySQL会在组合过多时停止评估,但这会影响MySQL使用索引。

避免多个范围查询

让我们假设有一个last_online(最近在线时间)的列,然后我们需要展示最近一周在线的用户:

WHERE eye_color		IN('brown', 'blue', 'hazel')
	AND hair_color	IN('black', 'red', 'blonde', 'brown')
  	AND sex 	IN('M', 'F')
 	AND last_online	 > DATE_SUB(NOW(), INTERVAL 7 DAY)
 	AND age		BETWEEN 18 AND 25

这个查询的问题在于它有两个范围查询。MySQL可以使用last_online或age条件,但不能同时使用。 如果last_online约束出现时没有age约束,或last_online比age更有筛选性,我们可能希望增加另一组索引,将last_online放到最后面。但是如果我们不能将age转换为IN查询,而我们也希望能够在同时有last_oinline和age范围查询时提高查询速度怎么办?这个时候,我们没有直接的方法。但我们可以将一个范围转换为相等比较。去这么做的时候,我们增加一个预先计算的active列,这个列我们会定期维护。如果用户登录后,我们标记为1,如果7天内没有连续登录则重新标记为0。

这个方法可以让MySQL使用如(active, sex, country, age)这样的索引。这个列也许没那么精准,但这类查询也许不需要很高的精准度。如果我们需要精准查询,我们可以保留last_online在WHERE条件中,但不增加索引。这种技巧与URL查找的情况类似。这种条件不会使用任何索引,因为它不太可能会将索引命中的行给过滤掉。增加索引未必能够让查询收益。

现在,你可以看到这个模式:如果用户想同时查找活跃和不活跃的结果,我们可以使用IN查询。我们增加了很多这样的清单查询,一个变通的方式是通过将各个组合分开的查询单独建立索引,例如,我们可以使用如下的索引:(active, sex, country, age),(active, country, age),(sex, country, age)和(country, age)。虽然这样的索引对于特定的查询可能是更优的选择,但维护这些组合的负面效果,组合所需的额外存储空间都可能导致是一个很弱的策略。

这是一个优化器改变后可以真正影响索引优化的案例。如果在未来的MySQL版本中可以真正丢弃索引扫描,它可能能够在一个索引上使用多个范围条件,此时我们不再需要通过IN查询这种方式解决此类问题。

优化排序

最后一个议题是排序。小数据量的结果使用文件排序(filesort)很快,但如果是上百万行数据呢?例如,如果只在WHERE条件中指定了性别时的排序。

对于这类低筛选性的场景,我们可以增加特定的索引用于排序。例如,一个(sex, rating)的索引可以用于下面的查询:

SELECT <cols> FROM profiles WHERE sex='M' ORDER BY rating LIMIT 10;

这个查询同时有排序和LIMIT子句,在没有索引的情况下可能很慢。即便是有索引,这个查询在用户界面有分页查询,而页码不在起始位置附近时也可能很慢。下面的例子的ORDER BY和LIMIT造成了一个糟糕的组合:

SELECT <cols> FROM profiles WHERE sex='M' ORDER BY rating LIMIT 100000, 10;

即便有索引,这样的查询也可能导致十分严重的问题。这是因为很高的偏移会导致花费大量的时间扫描大量的数据,且这些数据会被丢弃。反范式设计,提前计算和缓存可能能够解决这类查询的问题。一个更好的策略是限制用户可查询的页码。这不太可能会降低用户的体验,因为实际上不会有人会关心第10000页的搜索结果。

另一个好的策略是使用推断联合查询,这是我们利用覆盖索引去获取主键列后再获取数据行的方式。你可以将需要获取的列全部联合,这会减少MySQL收集那些需要丢弃的数据的工作。下面是一个例子:

SELECT <cols> FROM profiles INNER JOIN (
  SELECT <primary key cols> FROM profiles
  WHERE x.sex='M' ORDER BY rating LIMIT 100000, 10
AS x USING(<primary key cols>);

以上就是MySQL如何构建数据表索引的详细内容,更多关于MySQL构建数据表索引的资料请关注三水点靠木其它相关文章!

MySQL 相关文章推荐
MySQL infobright的安装步骤
Apr 07 MySQL
MySQL官方导出工具mysqlpump的使用
May 21 MySQL
修改MySQL的默认密码的四种小方法
May 26 MySQL
如何设计高效合理的MySQL查询语句
May 26 MySQL
带你学习MySQL执行计划
May 31 MySQL
MySQL中存储时间的最佳实践指南
Jul 01 MySQL
低版本Druid连接池+MySQL驱动8.0导致线程阻塞、性能受限
Jul 01 MySQL
MySql 缓存查询原理与缓存监控和索引监控介绍
Jul 02 MySQL
MySQL中datetime时间字段的四舍五入操作
Oct 05 MySQL
详解MySql中InnoDB存储引擎中的各种锁
Feb 12 MySQL
MySQL数据库如何给表设置约束详解
Mar 13 MySQL
Mysql超详细讲解死锁问题的理解
Apr 01 MySQL
MySQL 自定义变量的概念及特点
May 13 #MySQL
为什么mysql字段要使用NOT NULL
MySQL表字段时间设置默认值
May 13 #MySQL
MySql新手入门的基本操作汇总
May 13 #MySQL
MySQL中你可能忽略的COLLATION实例详解
浅谈mysql执行过程以及顺序
mysql 8.0.24版本安装配置方法图文教程
You might like
虚拟主机中对PHP的特殊设置
2006/10/09 PHP
从手册去理解分析PHP session机制
2011/07/17 PHP
PHP几个实用自定义函数小结
2016/01/25 PHP
PHP请求Socket接口测试实例
2016/08/12 PHP
Yii2选项卡的简单使用
2017/05/26 PHP
PHP7 echo和print语句实例用法
2019/02/15 PHP
浏览器解析js生成的html出现样式问题的解决方法
2012/04/16 Javascript
js计算精度问题小结
2013/04/22 Javascript
扒一扒JavaScript 预解释
2015/01/28 Javascript
使用AngularJS处理单选框和复选框的简单方法
2015/06/19 Javascript
js实现遍历含有input的table实例
2015/12/07 Javascript
原生js仿jquery animate动画效果
2016/07/13 Javascript
轻松实现jquery选项卡切换效果
2016/10/10 Javascript
BootStrap select2 动态改变值的方法
2017/02/10 Javascript
基于匀速运动的实例讲解(侧边栏,淡入淡出)
2017/10/17 Javascript
node.js将MongoDB数据同步到MySQL的步骤
2017/12/10 Javascript
vue 注册组件的使用详解
2018/05/05 Javascript
win10环境下python3.5安装步骤图文教程
2017/02/03 Python
python清除字符串中间空格的实例讲解
2018/05/11 Python
解决python中遇到字典里key值为None的情况,取不出来的问题
2018/10/17 Python
Python文件打开方式实例详解【a、a+、r+、w+区别】
2019/03/30 Python
Flask框架路由和视图用法实例分析
2019/11/07 Python
win10安装tensorflow-gpu1.8.0详细完整步骤
2020/01/20 Python
Python中zip()函数的解释和可视化(实例详解)
2020/02/16 Python
Python unittest单元测试框架实现参数化
2020/04/29 Python
python 列表推导和生成器表达式的使用
2021/02/01 Python
巴西最大的体育用品商城:Netshoes巴西
2016/11/29 全球购物
日本必酷网络直营店:Biccamera
2019/03/23 全球购物
在校生汽车维修实习自我鉴定
2013/09/19 职场文书
办公室副主任岗位职责
2013/11/25 职场文书
表彰先进集体通报
2014/01/12 职场文书
八项规定整改措施
2014/02/12 职场文书
满月酒邀请函
2015/01/30 职场文书
师范生教育见习总结
2015/06/23 职场文书
升学宴来宾致辞
2015/07/27 职场文书
opencv读取视频并保存图像的方法
2021/06/04 Python