本文来自作者 奋斗 在 GitChat 上分享 「SQL 优化必懂知识点」

1. 基数

单个列唯一键(distict_keys)的数量叫做基数。比如性别列,该列只有男女之分,抛开中性,所以这一列基数就是主键列的基数等于表的总行数。基数的高低影响列的数据分布。

MariaDB [test]> select count(distinct gender),count(distinct phone) from test;
+------------------------+-----------------------+
| count(distinct gender) | count(distinct phone) |
+------------------------+-----------------------+
|                      2 |                     7 |
+------------------------+-----------------------+
1 row in set (0.00 sec)

test 表的总函数是 7,gender 列的基数是 2,说明 gender 列里面有大量重复值,phone 列的基数等于总行数,说明 phone 列没有重复值,相当于主键。gender 列的数据分布如下:

MariaDB [test]> select gender,count(*) from test group by gender order by 2 desc;
+--------+----------+
| gender | count(*) |
+--------+----------+
|      1 |        4 |
|      2 |        3 |
+--------+----------+
2 rows in set (0.00 sec)

gender 列的数据分布极其不均衡,运行如下 SQL。

MariaDB [test]> select * from test where gender=1;
+----+--------+-------------+
| id | gender | phone       |
+----+--------+-------------+
|  1 |      1 | 13054480665 |
|  2 |      1 | 13167007801 |
|  4 |      1 | 13167007803 |
|  6 |      1 | 13167007805 |
+----+--------+-------------+
4 rows in set (0.00 sec)

gender 为 1 有 4 条数据,从 7 条数据里查询 4 条数据,也就是说要返回表中超过 50% 的数据。

MariaDB [test]> select 4/7*100 "percent from dual";
+-------------------+
| percent from dual |
+-------------------+
|           57.1429 |
+-------------------+
1 row in set (0.00 sec)

那么请思考,你认为如上查询应该使用索引?现在我们换一种查询语句。

MariaDB [test]> select * from test where phone='13054480665';
+----+--------+-------------+
| id | gender | phone       |
+----+--------+-------------+
|  1 |      1 | 13054480665 |
+----+--------+-------------+
1 row in set (0.00 sec)

phone 等值条件有 1 条数据,从 7 条数据里查询 1 条数据,也就是说要返回表中 14% 的数据。

MariaDB [test]> select 1/7*100 "percent from dual";
+-------------------+
| percent from dual |
+-------------------+
|           14.2857 |
+-------------------+
1 row in set (0.00 sec)

请思考,返回表中 14% 的数据是否走索引?

如果你还不懂索引,没关系,可以看下笔者其他相关的 chat。如果你回答不了上述问题,我们先提醒一下。当查询结果返回表中 30% 内的数据时,应该走索引(表中数据量小,其实 phone 的等值查询也是);当查询结果返回的是超过表中 30% 数据时,基本会走全表扫描。

当然了,返回表中 30% 内的数据会走索引,返回超过 30% 数据就使用全表扫描,这个结论太绝对了,但其实大多场景下,你先记住这个 30% 这个界限吧。这里之所以让记住 30% 这个界限,是不想让初学者为了答案纠结,其实工作中真返回超过 30% 的数据量,本身业务角度就有问题,尤其在 oltp 业务下。

现在有如下查询语句:

select * from test where gender=:b1;

语句中 :b1 是绑定变量,可以传入任何值,该查询可能走索引也可能走全表扫描。

现在得到一个结论:如果一个列基数很低,该列数据分布不均衡,由于该列数据分布极度不均衡,会导致 SQL 查询可能走索引,也可能走全表扫描。

在做 SQL 优化时,如果怀疑该列数据分布不均衡,我们可以使用 select 列,count(*) from 表 group by 列 order by 2 desc 来查看列的数据分布。

如果 SQL 语句是单表访问,可能走索引扫描,也可能走全表扫描,也可能走物理物化视图扫描。在不考虑物理物化视图的情况下,单表访问要么走索引扫描,要么走全表扫描。

现在,回忆一下,走索引的条件:返回表中 30% 内的数据要么走索引,要么走全表扫描。相信大家看到这里,已经懂得单表访问的优化方法。

我们来看如下查询:

select * from test where phone=:b1;

不管 phone 传入任何值,都应该走索引。

2. 选择性(SELECTIVITY)

基数与总行数的比值再乘 100% 就是一个列的选择性。

在进行 SQL 优化的时候,单独看列的基数是没有任何意义的,基数相对于总行数才有实际意义,正是这个原因,我们才引出选择性这个概念。

请思考,什么样的列必须建立索引?

有人说基数高的列,有人说在 where 条件中的列。这些答案并不完美。基数高究竟多高?没有和总行数对比,始终不知道有多高。

比如一个列的基数是几万行,但是总数是十几亿行,那么这个列的基数还高?这就是引出选择性的根本原因。

对于如下 SQL

select * from test where phone=:b1;

不管 phone 传入任何值,最多返回1条。

什么样的列必须要创建索引呢?当一个列出现在 where 条件中,该列没有创建索引并且选择性大于 20% 时,那么该列必须创建索引,从而提升 SQL 查询性能。当然了,如果表只有几百条数据,那我们就不用创建索引了。

下面抛出 SQL 优化核心的第一个观点:只有大表才会产生性能问题。

也许有人会说:“我有个表很小,只有几百条,但是经常进行 DML,会产生热点块,也会出性能问题。”对此我们并不想过多的讨论,这属于应用程序设计问题,不属于 SQL 优化的范畴。

3. 回表(TABLE ACCESS BY INDEX ROWID)

当对一个列创建索引之后,索引会包含该列的键值及键值对应行所在的 rowid。通过索引中记录的 rowid 访问表中的数据就叫回表。

回表一般是单块读,回表次数太多会严重影响 SQL 性能,如果回表次数太多,就不应该走索引扫描,应该直接走全表扫描。

在进行 SQL 优化时,一定要注意回表次数!特别是注意回表的物理 IO 次数。

MariaDB [test]> explain select * from test where gender=1;
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id   | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+
|    1 | SIMPLE      | test  | ALL  | NULL          | NULL | NULL    | NULL |    7 | Using where |
+------+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)

此 SQL 执行计划的 ALL,也就是说全表扫描,但是 select * 需要查询表中所有的列,也就是回表。

在此处,引出 SQL 优化有一核心点:减少回表,也就是网络传输消耗。笔者公司前段时间刚出现大量回表导致小部分用户,使用功能超时。

什么样的 SQL 必须要回表?

select * from test where .......;

这样的 SQL 就必须要回表,所以我们一般禁止使用 select *。那么什么样的 SQL 不需要回表?

select count (*) from test;

这样的 SQL 就无需回表。

当要查询的列也包含在索引中,这个时候就不需要回表,所以我们往往会建立组合索引来消除回表,从而提升性能。

当一个 SQL 有多个过滤条件但是只有一个列或者部分列建立了索引,这个时候回出现回表再过滤,也需要创建组合索引,进而消除回表再过滤,从而提升查询性能。

关于回表有些专业名词,笔者是借用 Oracle 数据中的,其实思想是想通的。

4. 集群因子

集群因子用于判断索引回表需要消耗的物理 IO 次数。

我们先对测试表 test 的 object_id 累创建一个索引 idx_id。

create index idx_id on test(object_id);

然后我们查看该索引的集群因子。(基于 Oracle 的)

select onwer,index_name,clustering_factor from dba_indexes where owner="scott" and index_name="IDX_ID';

索引 idx_id 的叶子块中有序存储了索引的键值及键值对应行所在的 rowid。

sql>select * from (select object_id,rowid from test where object_id is not null order by object_id) where rownum<=5;
object_id     rowid
2             AAASNJAAEAAAAAITAAw
3             AAASNJAAEAAAAAITAAf
4             AAASNJAAEAAAAAITAAx
5             AAASNJAAEAAAAAITAAa
6             AAASNJAAEAAAAAITAAV

集群因子的算法如下:

  • 首先我们比较 2、3 对应的 rowid 是否在同一个块,如果在同一个块 clustering_factor+0,如果不在同一个数据块,clustering_factor+1。

  • 然后我们比较 3、4 对应的 rowid 是否在同一个数据块,如果在同一个块 clustering_factor+0,如果不在同一个数据块,clustering_factor+1。

如上面步骤一样,一直这样有序的比较下去,直到比较完索引中最后一个键值。

根据算法,我们直到集群因子介于表的块数和表的行数之间。

如果集群因子与块数接近,表明表的数据基本上是有序的,而且其顺序基本与索引顺序一致。这样在进行索引范围扫描或者全索引扫描时,回表只需要读取少量的数据块就能完成。

如果集群因子与表记录数接近时,表明表的数据和索引顺序差别很大,在进行索引范围扫描或者索引全扫描的时候,回表会读取更多的数据块。

集群因子只会影响索引范围扫描及索引全扫描,因为只有这两种索引扫描数据会有大量数据回表。

集群因子不会影响索引唯一扫描,因为索引唯一扫描只返回一条数据。集群因子更不会影响索引快速扫描,因为索引快速扫描不回表。

集群因子到底影响的是什么性能?集群因子影响的是索引回表的物理 IO 次数。

我们假设索引范围扫描返回了 1000 行数据,如果 buffer  cache 中没有缓存表的数据块,假设这 1000 行数据都在同一数据块中,那么回表就需要耗费 1 个物理 IO;

假设这 1000 行数据都在不同的数据块中,那么回表就需要消耗 1000 个物理 IO。因此集群因子影响索引回表的物理 IO 次数。

请注意,不要尝试重建索引来降低集群因子,这根本没用,因为表中的数据顺序始终没变。

唯一能降低集群因子的办法就是根据索引列排序对表进行重建(creeate table new_table as select * from old_table order by 索引列),但是这在实际操作中是不可取的,因为我们无法照顾到每一个索引。

怎么才能避免集群因子对 SQL 查询性能产生影响?集群因子只影响索引范围扫描和索引全扫描。当索引范围扫描,索引全扫描不回表或者返回数据量很少的时候,不管集群因子多大,对SQL查询性能几乎不受影响。

重点强调一下,在进行 SQL 优化时,大多会建立合适的组合索引消除回表,或者建立组合索引尽量减少回表次数。

5. 表与表之间的关系

关系数据库中,表与表之间会进行关联,在进行关联的时候,我们一定要清楚表与表之间的关系。表与表之间存在三种关系。

一种是 1:1,一种是 1:n,最后一种是 n:n 关系。搞懂表与表之间的关系,对于 SQL 优化、SQL 等价改写、表设计及分表分库都有帮助。

两表再进行关联的时候,如果两表属于 1:1 关系,关联之后返回的机构也属于 1:1 的关系,数据不会重复。如果两表属于 1:n 关系,关联之后的结果集属于 1:n 的关系。

如果两表属于 n:n 关系,关联之后的结果集会产生局部的笛卡尔积,n:n 关系一般不存在内 / 外连接中,只能存在于半连接或者反连接中。

如果我们不知道业务,不知道数据字典,怎么判断两表是什么关系?我们用下面 SQL:

select * from emp e,dept d where e.deptno=d.deptno;

我们只需要对两表进行汇总就可以知道两表什么关系:

select deptno,count(*) from emp group by deptno order by 2 desc;
select deptno,count(*) from dept group by deptno order by 2 desc;

从上面 SQL 的结果集发现,emp 和 dept 是 n:1 的关系。搞清楚表与表之间的关系对于 SQL 优化很有用,这是最基本的,也是应用程序设计的基础。

总之所述知识点都是 SQL 优化的基础点,更是一个应用程序开发的基础,如果连基数、表关系等都不懂,盲目去做业务开发和表设计,基本是灾难。

在笔者公司的技术人员里,不少资深应用程序开发老司机设计表简直就是瞎玩,SQL 出现慢就只知道加索引,压根不看基数,连那种状态值的列也加。

本来就是大表,经常查询的状态值得结果集都是超过 30%。程序研发是操作数据,可是数据相关的基础都没有,纵使你换最牛逼的语言也是歇菜。

笔者一直比较喜欢一句话,尤其是做技术:“基础的扎实程度决定你走多远”。希望看完本文的小伙伴,能多多交流。

SQL 优化必懂知识点相关推荐

  1. 干货:Java并发编程必懂知识点解析

    本文大纲 1.并发编程三要素 原子性 原子,即一个不可再被分割的颗粒.在Java中原子性指的是一个或多个操作要么全部执行成功要么全部执行失败. 有序性 程序执行的顺序按照代码的先后顺序执行.(处理器可 ...

  2. E站账号cookie分享_产品经理必懂知识点—cookie和session

    作为一个产品经理,如果不了解cookie和session很难设计好产品,尤其对于网站产品经理而言,cookie和session是一个必须要懂的知识点.如果在中大型企业做产品经理,这个cookie和se ...

  3. B站小迪安全学习笔记第11天-WEB漏洞必懂知识点讲解

    前言: 本章节将讲解各种 WEB 层面上的有那些漏洞类型,具体漏洞 的危害等级,以简要的影响范围测试进行实例分析,思维导图中的漏洞也 是后面我们将要学习到的各个知识点,其中针对漏洞的形成原理,如何发现 ...

  4. 优秀程序员必懂知识点,你要是还不会就out了

    进行社招面试时,有一个问题几乎是必问的: 你为什么要离开上一家公司? 其实这个问题主要是想试探一下求职者的核心诉求,并借此预估一下他在本公司工作的稳定性.常见的答案也无非就是这么几种:对薪酬不满意.干 ...

  5. Java面试必懂知识点总结

    1.介绍一下面向对象 面向对象是基于万物皆对象. 封装:隐藏方法的具体实现细节,提供出一个公共接口给API调用,提高了代码的可维护性,和安全性. 继承:继承就是在已知类上建立新类的技术,子类继承父类的 ...

  6. python 字符串%和format_Python必懂知识点,格式化字符串,到底用.format还是%

    第一次听说格式化,是清理电脑磁盘时,以为格式化就是清空一切,重回自由,后来才知道,格式化,是另一种妥协. 以下部分节选自<编写高质量代码:改善Python程序的91个建议>一书,需要该书电 ...

  7. java二维数组添加数据_Java小白入门必懂知识点

    1.Java语言的特点 (1)Java语言是一种面向对象的编程语言 (2)简单.高效.稳定.安全性高 (3)Java语言是一种与平台无关的编程语言,因为其自身提供程序运行的解 释环境 (4)支持多线程 ...

  8. SQL优化核心思想:或许你不知道的5条优化技巧

    点击关注 异步图书,置顶公众号 每天与你分享 IT好书 技术干货 职场知识 参与文末话题讨论,每日赠送异步图书. --异步小编 随着系统的数据量逐年增加,并发量也成倍增长,SQL性能越来越成为IT系统 ...

  9. SQL 优化核心思想

    SQL 优化核心思想 SQL 优化必懂的概念 概念 英文 含义 影响 示例 计算 临界 基数 Cardinality 某个列唯一键(Distinct Keys)的数量 基数的高低影响列的数据分布 性别 ...

最新文章

  1. 自然语言处理(NLP)之英文单词词性还原
  2. 【poe设备加电配置】
  3. ReactNative手势解锁(react-native-ok-gesture-password)
  4. Python批量添加库搜索路径
  5. (二)Mysql 基础了解,修改字符集,配置文件
  6. [通俗易懂]深入理解TCP协议(下):RTT、滑动窗口、拥塞处理
  7. 多媒体计算机辅助教学与课件制作,清华大学出版社-图书详情-《计算机辅助教学多媒体课件设计制作与应用》...
  8. SoftGrid教程——综合应用
  9. python 获取文件夹所有文件列表_python获取文件夹下所有文件及os模块方法
  10. 按周汇总_有合并单格及空行的数据如何快速汇总?简单几步快速搞定
  11. crx文件里面的html文件,javascript – Chrome扩展程序:在crx文件中打开html,标签上没有图标...
  12. t3软件怎么生成报表_用友T3怎么生成财务报表-
  13. 计算机的自带拍视频教程,电脑如何录制视频并剪辑
  14. 机器学习如何优化策略游戏
  15. const T vs. T const ——Dan Saks 【翻译】
  16. 电脑突然变成繁体字,格式化代码快捷键失灵
  17. python模拟登录淘宝直通车_Python实现的淘宝直通车数据抓取(2)
  18. UCK Network 为开发者提供全方位孵化,打造区块链爆款应用
  19. 2.3.1 导引型传输媒体
  20. Beaglebone Black LCD 支持,BB VIEW配置

热门文章

  1. Chrome浏览器——报错解决:应用程序无法启动,因为应用程序的并行配置不正确。
  2. 2个万兆光口16个千兆网口机架式网管交换机管理型万兆工业级以太网交换机
  3. 麻省理工学院的研究生学习指导——怎样做研究生?
  4. 计划任务linux时间,系统运维|在 Linux 中怎么使用 cron 计划任务
  5. 培养团队协作能力的小游戏
  6. 100句绝对经典的人生哲理语句
  7. iOS 键盘回车键(换行、回车符)修改
  8. 《我的眼睛--图灵识别》第五章:基础:形状识别
  9. 大数据规划布局“未来生产要素”
  10. C++ explicit和implicit