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查看表和清空表的常用命令总结
May 26 MySQL
MySQL8.0.18配置多主一从
Jun 21 MySQL
mysql left join快速转inner join的过程
Jun 30 MySQL
MySQL8.0升级的踩坑历险记
Nov 01 MySQL
MySQL数据库完全卸载的方法
Mar 03 MySQL
解决MySQL添加新用户-ERROR 1045 (28000)的问题
Mar 03 MySQL
浅谈redis的过期时间设置和过期删除机制
Mar 18 MySQL
详解Mysql数据库平滑扩容解决高并发和大数据量问题
May 25 MySQL
MySQL数据库安装方法与图形化管理工具介绍
May 30 MySQL
MySQL普通表如何转换成分区表
May 30 MySQL
MySQL的意向共享锁、意向排它锁和死锁
Jul 15 MySQL
MySQL索引失效十种场景与优化方案
May 08 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
Linux下实现PHP多进程的方法分享
2012/08/16 PHP
php模拟用户自动在qq空间发表文章的方法
2015/01/07 PHP
ecshop后台编辑器替换成ueditor编辑器
2015/03/03 PHP
PHP生成静态HTML页面最简单方法示例
2015/04/09 PHP
PHP 验证身份证是否合法的函数
2017/02/09 PHP
php实例化一个类的具体方法
2019/09/19 PHP
Prototype ObjectRange对象学习
2009/07/19 Javascript
javascript中使用replaceAll()函数实现字符替换的方法
2010/12/25 Javascript
Bootstrap CSS组件之分页(pagination)和翻页(pager)
2016/12/17 Javascript
JavaScript观察者模式(publish/subscribe)原理与实现方法
2017/03/30 Javascript
Bootstrap布局之栅格系统学习笔记
2017/05/04 Javascript
解决OneThink中无法异步提交kindeditor文本框中修改后的内容方法
2017/05/05 Javascript
jQuery插件DataTables分页开发心得体会
2017/08/22 jQuery
JS Input里添加小图标的两种方法
2017/11/11 Javascript
微信小程序左滑动显示菜单功能的实现
2018/06/14 Javascript
Javascript中绑定click事件的四种方式介绍
2018/10/26 Javascript
Vue 使用typescript如何优雅的调用swagger API
2020/09/01 Javascript
[40:29]2018DOTA2亚洲邀请赛 4.7总决赛 LGD vs Mineski 第一场
2018/04/10 DOTA
Python实现的简单hangman游戏实例
2015/06/28 Python
Python使用PIL库实现验证码图片的方法
2016/03/11 Python
python3实现全角和半角字符转换的方法示例
2017/09/21 Python
python实现彩色图转换成灰度图
2019/01/15 Python
解决python文件双击运行秒退的问题
2019/06/24 Python
python文件和文件夹复制函数
2020/02/07 Python
python和node.js生成当前时间戳的示例
2020/09/29 Python
HTML5之SVG 2D入门3—文本与图像及渲染文本介绍
2013/01/30 HTML / CSS
怎样实现H5+CSS3手指滑动切换图片的示例代码
2019/05/05 HTML / CSS
美国南部最大的家族百货公司:Belk
2017/01/30 全球购物
英国著名的小众美容品牌网站:Alyaka
2017/08/08 全球购物
大学生毕业的自我鉴定
2013/11/13 职场文书
2013年员工自我评价范文
2013/12/27 职场文书
生日庆典策划方案
2014/06/02 职场文书
中秋节国旗下演讲稿
2014/09/13 职场文书
区长工作作风个人整改措施
2014/10/01 职场文书
中学团支部工作总结
2015/08/13 职场文书
职业规划从高考志愿专业选择开始
2019/08/08 职场文书