举个例子,我参与的一个项目需要处理每表上亿条数据的库,我选择只统计10%,结果造成了巨大的时间消耗 。本例证明这是个糟糕的决定,因为有时候 Oracle 10G 从特定表的特定列中选出的 10% 跟全部 100% 有很大不同(对于拥有一亿行数据的表,这种情况极少发生) 。这次错误的统计导致了一个本应 30 秒完成的查询最后执行了 8 个小时,查找这个现象根源的过程简直是个噩梦 。这个例子显示了统计的重要性 。
注:当然了,每个数据库还有其特定的更高级的统计 。如果你想了解更多信息,读读数据库的文档 。话虽然这么说,我已经尽力理解统计是如何使用的了,而且我找到的最好的官方文档来自PostgreSQL 。
查询优化器
文章插图
所有的现代数据库都在用基于成本的优化(即CBO)来优化查询 。道理是针对每个运算设置一个成本,通过应用成本最低廉的一系列运算,来找到最佳的降低查询成本的方法 。
为了理解成本优化器的原理,我觉得最好用个例子来『感受』一下这个任务背后的复杂性 。这里我将给出联接 2 个表的 3 个方法,我们很快就能看到即便一个简单的联接查询对于优化器来说都是个噩梦 。之后,我们会了解真正的优化器是怎么做的 。
对于这些联接操作,我会专注于它们的时间复杂度,但是,数据库优化器计算的是它们的 CPU 成本、磁盘 I/O 成本、和内存需求 。时间复杂度和 CPU 成本的区别是,时间成本是个近似值(给我这样的懒家伙准备的) 。而 CPU 成本,我这里包括了所有的运算,比如:加法、条件判断、乘法、迭代……还有呢:
- 每一个高级代码运算都要特定数量的低级 CPU 运算 。
- 对于 Intel Core i7、Intel Pentium 4、AMD Opteron…等,(就 CPU 周期而言)CPU 的运算成本是不同的,也就是说它取决于 CPU 的架构 。
索引
在研究 B+树的时候我们谈到了索引,要记住一点,索引都是已经排了序的 。
仅供参考:还有其他类型的索引,比如位图索引,在 CPU、磁盘I/O、和内存方面与B+树索引的成本并不相同 。
另外,很多现代数据库为了改善执行计划的成本,可以仅为当前查询动态地生成临时索引 。
存取路径
在应用联接运算符(join operators)之前,你首先需要获得数据 。以下就是获得数据的方法 。
注:由于所有存取路径的真正问题是磁盘 I/O,我不会过多探讨时间复杂度 。
【四种类型的Oracle索引扫描介绍 】
全扫描
如果你读过执行计划,一定看到过『全扫描』(或只是『扫描』)一词 。简单的说全扫描就是数据库完整的读一个表或索引 。就磁盘 I/O 而言,很明显全表扫描的成本比索引全扫描要高昂 。
范围扫描
其他类型的扫描有索引范围扫描,比如当你使用谓词 ” WHERE AGE > 20 AND AGE < 40 ” 的时候它就会发生 。
当然,你需要在 AGE 字段上有索引才能用到索引范围扫描 。
在第一部分我们已经知道,范围查询的时间成本大约是 log(N)+M,这里 N 是索引的数据量,M 是范围内估测的行数 。多亏有了统计我们才能知道 N 和 M 的值(注: M 是谓词 “ AGE > 20 AND AGE < 40 ” 的选择率) 。另外范围扫描时,你不需要读取整个索引,因此在磁盘 I/O 方面没有全扫描那么昂贵 。
唯一扫描
如果你只需要从索引中取一个值你可以用唯一扫描 。
根据 ROW ID 存取
多数情况下,如果数据库使用索引,它就必须查找与索引相关的行,这样就会用到根据 ROW ID 存取的方式 。
例如,假如你运行:
MySQL
文章插图
如果 person 表的 age 列有索引,优化器会使用索引找到所有年龄为 28 的人,然后它会去表中读取相关的行,这是因为索引中只有 age 的信息而你要的是姓和名 。
但是,假如你换个做法:
MySQL
文章插图
PERSON 表的索引会用来联接 TYPE_PERSON 表,但是 PERSON 表不会根据行ID 存取,因为你并没有要求这个表内的信息 。
虽然这个方法在少量存取时表现很好,这个运算的真正问题其实是磁盘 I/O 。假如需要大量的根据行ID存取,数据库也许会选择全扫描 。
推荐阅读
- 如何挑选黑豆
- 不是夫妻合租房犯罪吗
- 医保断缴三个月余额清零?员工可自愿放弃社保?这些谣言别再信了
- 手串颗数大有讲究,千万别戴错了
- “禁止长时间停车”,到底指的是几分钟?交警:最后再说一遍
- 太热了!别再披头散发了,这4款发型够美够清凉
- 没钱还信用卡有什么解决办法
- 信用卡过期卡怎么处理
- 信用卡逾期被注销怎么还钱
- 如何找回抖音私聊记录