《基于 Oracle 的 SQL 优化》笔记 第五章 Oracle 里统计信息

CBO 是基于对各种不同执行路径成本的计算,比较并选取成本值最小的执行路径来作为目标 SQL 的执行计划的;而成本值的计算则是根据目标 SQL 所涉及的表、索引、列等相关对象的统计信息,运用 CBO 固有的成本值计算公式计算出来的。所以,对统计信息的了解成了理解 CBO、理解执行计划的关键。

Oracle 数据库里的统计信息分为六种类型,包含对应的对象的信息,维度不同:

  • 表的统计信息:典型的维度有 记录数、表块(表里的数据块)的数量、平均行长度等。
  • 索引的统计信息:典型的维度有 索引的层级、叶子块的数量、聚族因子等。
  • 列的统计信息:典型的维度有 列的 distinct 值的数量、列的 null 值的数量、列的最小值、列的最大值以及直方图等。
  • 系统统计信息:所在数据库服务器的系统处理能力,包含来 CPU 和 I/O 这两个维度,借助于系统统计信息,Oracle 可以知道目标数据库服务器的实际处理能力。
  • 数据字典统计信息:描述 Oracle 数据库里的数据字典基表(如 TAB$IND$ 等)数据字典基表上的索引,以及这些数据字典基表的列的详细信息,与普通表、索引、列的统计信息没有本质区别。
  • 内部对象统计信息:描述 Oracle 数据库里的内部表(如 X$ 系列表,其实只是 Oracle 自定义的内存结构)的信息信息,并不占用实际的物理存储空间。

可以用 ANALYZE 命令(只能收集前四种,且不能并行收集)或 DBMS_STATS 包来收集统计信息,

继续阅读

《基于 Oracle 的 SQL 优化》笔记 第四章 Oracle 里的查询转换

查询转换(Query Transformation)又称为查询改写(Query Rewrite),是指 Oracle 在解析目标 SQL 时可能会对其做等价改写,目的是为了生成更高效地执行目标 SQL,即 Orace 可能会将目标 SQL 改写成语义上完全相等但执行效率更高的形式。

子查询展开

子查询展开(subquery unnesting)是指 Oracle 不再将目标 SQL 中的子查询当作一个独立的处理单元来单独执行,而是将该子查询转换为它自身和外部查询之间等价的表连接。这种等价表连接转换要么是将子查询拆开(即将该子查询中的表、视图从子查询中拿出来,然后和外部查询中的表、视图做表连接),要么是不拆开但是会把子查询转换为一个内嵌视图(inline view),然后再和外部查询中的表、视图做表连接。

在 Oracle 10g 及其以后的版本中,对于那种不拆开子查询但是会把该子查询转换成一个内嵌视图的子查询展开,只有当经过子查询展开后的等价改写 SQL 的成本值小于原 SQL 的成本值时,Oracle 才会对原 SQL 执行子查询展开。

子查询展开通常都会提高原 SQL 的执行效率,因为如果原 SQL 不做子查询展开,那么通常情况下,该子查询就会在其执行计划的最后一步才被执行,并且会走 FILTER 类型的执行计划,这样就意味着对于外部查询所在结果集中的每一条记录,该子查询都会被当作一个独立的执行单元来执行一次,外部查询所在的结果集有多少条记录,该子查询就会被执行多少次(近似这样,实际上不完全是这样)。

Oracle 数据库里子查询前的 where 条件如果是如下这些条件之一,那么这种类型的目标 SQL 在满足了一定的条件后就可以做子查询展开:

  • SINGLE_ROW(即 =, <, >, <=, >=, <>),意味着子查询的返回结果至多只会有一条记录。
  • 可能返回多条记录的子查询:EXISTS, NOT EXISTS, IN, NOT IN, ANY, ALL

继续阅读

《基于 Oracle 的 SQL 优化》笔记 第一章 Oracle 里的优化器

CBO 相关基础

cardinality :指定结果集的行数,实际上表示对目标 SQL 的某个具体步骤的执行结果所包含记录数的估算。如果是针对整个目标 SQL,那么此时的 cardinality 就表示对该 SQL 最终执行结果所包含记录数的估算。cardinality 值越大,对应的成本就越高。

selectivity 选择度:是指施加指定谓词条件后返回结果集的记录数占未施加任何谓词条件的原始结果集的记录数的比率。计算公式为 selectivity = 指定谓词条件后返回结果集的记录数 / 未施加任何谓词条件的原始结果集的记录数。这个取值处于 0 - 1 之间,值越小越好,说明选择性高(能过滤更多不符合条件的数据)。

对于每一种具体情况,selectivity 会有不同的计算公式。

最简单的情况是对目标列做等值查询时 selectivity 的计算公式,在目标列上没有直方图且没有 NULL 值的情况下,用目标列做等值查询时的 selectivity 的计算公式为 selectivity = 1 / NUM_DISTINCT,NUM_DISTINCT 表示目标列的 distinct 值的数量。

transitivity 可传递性:是 CBO 在查询转换中所做的第一件事,是指 CBO 可能会对原目标 SQL 做简单的等价改写,即在原目标 SQL 中加上根据该 SQL 现有的谓词条件推算出来的新的谓词条件,这么做的目的是提供更多的执行路径给 CBO 做选择,进而得到更高效执行计划的可能性。

Oracle 中可传递性分下列三种情形:

  • 简单的谓词传递:比如 t1.c1 = t2.c1 and t1.c1 = 10 ,额外添加 t2.c1 = 10
  • 连接谓词传递:比如 t1.c1 = t2.c1 and t2.c1 = t3.c1,额外添加 t1.c1 = t3.c1
  • 外连接谓词传递:比如 t1.c1 = t2.c1(+) and t1.c1 = 10,额外添加 t2.c1 (+)= 10

继续阅读

《基于 Oracle 的 SQL 优化》笔记 第二章 Oracle 里的执行计划

如何查看执行计划的顺序

简单的原则:根据往右的缩进,最右的先执行,即从右往左;缩进相同的,从上往下执行。

Oracle 里的常见的执行计划

与表访问相关的执行计划

全表扫描对应的执行计划关键词是“TABLE ACCESS FULL”;
rowid 扫描对应的关键词是“TABLE ACCESS BY USER ROWID”(目标 SQL 直接指定了 rowid)或 “TABLE ACCESS BY INDEX ROWID”(先检索索引,然后用 rowid 访问表)。

与 B-tree 相关的执行计划

连接类型          执行计划关键词
排序合并连接        SORT JOINMERGE JOIN
嵌套循环          NESTED LOOPS
哈希连接          HASH JOIN
反连接           ANTI(可能是 HASH JOIN ANTI, MERGE JOIN ANTI, NESTED LOOPS ANTI
半连接           SEMI(可能是 HASH JOIN SEMI, MERGE JOIN SEMI, NESTED JOIN SEMI

半连接对连接结果进行必要的去重。

其他执行计划

继续阅读

Oracle B-Tree 索引的结构、特点及索引扫描方式

《Oracle DBA 手记 — 数据库诊断案例与性能优化实践》摘录(P218)

Oracle 数据库中索引的存储结构使用的是 B Tree 的一种变体,称为 B*Tree (B Star Tree),在数据库中存储数据以块为单位。
Oracle B-Tree索引结构

一些术语:

  • 节点 M 的深度:从树根节点到节点 M 的最短路径长度。图中根节点 Root 的深度是 0,节点 L1-1 的深度是 1,节点 L0-1 的深度是 2。
  • 节点 M 的层数:节点 M 的层数与其深度是相同的。
  • 树的高度:树的深度值最大的那个节点,其深度 +1 即为树的高度。图中树的高度为 3。

继续阅读

Oracle 索引分区

摘录自《 ORACLE数据存储与访问技术》

从物理上说,索引是组织索引键值及其行地址的存储手段,是一个具有两字段的特殊表。

Oracle 支持表分区和索引分区的独立设计,即支持 “表不分区-索引不分区、 表分区-索引不分区、 表分区-索引分区、 表不分区-索引分区” 四种组合。

Oracle 支持 本地分区索引 和 全局分区索引。

本地分区索引(local partitioning index)

本地分区索引是指索引的分区方法与对应表的分区方法相同,表的每个分区有自己对应的索引段,创建时用 local 指定索引属性。

本地分区索引的优点:

  1. 取决于分区条件和查询条件的组合,Oracle 能够应用分区裁剪(partition pruning)功能定向到分区索引的特定分区,减小了索引树的规模。
  2. 分区表在分区的维护过程中,如对表的某个分区执行删除(drop)、截断(truncate)、合并(merge)等操作,Oracle 自动对与之关联的本地前缀索引执行相同的操作,且索引始终保持有效的状态,不需要对整个索引进行重建(rebuild)。
  • 本地前缀分区索引(local prefixed partitioning index):本地前缀分区索引是指表分区键是索引键的前导列。

  • 本地非前缀分区索引(local nonprefixed partitioning index):索引键的前导列不是分区键。

继续阅读

《Oracle数据库性能优化》笔记 – 索引与 bitmap 索引

需要复杂技巧及高深知识来解决所遇到的问题的情形并不多,相对来说,在对基本概念有清晰认识的情形下,解决问题的切入口通常都会比较准确。

索引的作用

索引是以最快的路径找到所需要的数据;另一个功用是对数据完整性的强制控制。

在下面两种情形下,系统会自动为表的列建立索引:

  1. 当表的列被指定为 primary key 时;
  2. 当表的列有 unique constraint 时。

当一个表中有 foreign key 存在时,通常也建议这个列使用索引。

索引管理的常见问题

  1. 最近是否做过数据库的分析
  2. 索引的数量,过多或过少都是不好的

索引不被使用的情况

  • 当对同一个表的两个列进行比较的情形下,索引有时不会被使用。

  • null 值。如果 where 语句中出现 is null 或者 is not null 时,索引就不能被使用。

  • 当 where 语句中存在有 not function 时,比如 not in, not exist, column <> value, column > value, column2 < value 等情形下,索引不能被使用。

  • 当 select 语句使用了 single-row function时,如 nvl, to_char, lower 等,索引不能被使用。

  • 通配符 % 或者 _ 作为查询字符串的第一个字符时,索引页无法使用。如果查询字符串的第一个字确定,则可以使用索引。

继续阅读

Oracle hint

公司的DBA要求把他们固化的执行计划改用 Oracle hint 来实现,正好了解下平时没注意去用的 hint。

优化方式

Oracle的优化器有两种优化方式:

  • 基于规则的优化方式(Rule-Based Optimization,简称为RBO) :优化器在分析 SQL 语句时,所遵循的是 Oracle 内部预定的一些规则。比如我们常见的,当一个 where 子句中的一列有索引时去走索引。

  • 基于代价的优化方式(Cost-Based Optimization,简称为CBO) ;它是看语句的代价(Cost),这里的代价主要指Cpu和内存。优化器在判断是否用这种方式时,主要参照的是表及索引的统计信息。统计信息给出表的大小、有少行、每行的长度等信息。这些统计信息起初在库内是没有的,是做 analyze 后才出现的,很多的时侯过期统计信息会令优化器做出一个错误的执行计划,因些应及时更新这些信息。

hint 也不例外,除了 /*+ rule */ 其他的都是 CBO 优化方式 。

优化模式

优化模式包括 Rule、Choose、First rows、All rows 四种方式:

  • Rule:基于规则的方式。
  • Choolse:默认的情况下 Oracle 用的便是这种方式。指的是当一个表或或索引有统计信息,则走 CBO 的方式,如果表或索引没统计信息,表又不是特别的小,而且相应的列有索引时,那么就走索引,走 RBO 的方式。
  • First Rows:它与 Choose 方式是类似的,所不同的是当一个表有统计信息时,它将是以最快的方式返回查询的最先的几行,从总体上减少了响应时间。
  • All Rows:也就是我们所说的 Cost 的方式,当一个表有统计信息时,它将以最快的方式返回表的所有的行,从总体上提高查询的吞吐量。没有统计信息则走 RBO 的方式。

hint

hint 语法

{DELETE|INSERT|SELECT|UPDATE} /*+ hint [text] [hint[text]]... */
多个hint之间需要用空格分开,如果没有指定正确的hint,oracle讲忽视该hint,没有任何错误提示。

我们可以使用注释(comment)来为一个语句添加 hints,一个语句块只能有一个注释,而且注释只能放在 SELECTUPDATEDELETE 关键字的后面。

继续阅读

oracle 表连接类型 join type

Join 是一种试图将两个表结合在一起的谓词,一次只能连接 2 个表,表连接也可以被称为表关联。Join 过程的各个步骤经常是串行操作,即使相关的 row source 可以被并行访问,但是在将表中符合限制条件的数据读入到内存形成 row source 后,join 的其它步骤一般是串行的。

row source (表)之间的连接顺序对于查询的效率有非常大的影响。通过首先存取特定的表,即将该表作为驱动表,这样可以先应用某些限制条件,从而得到一个较小的 row source,使连接的效率较高,这也就是我们常说的要先执行限制条件的原因。一般是在将表读入内存时,应用 where 子句中对该表的限制条件。

根据 2 个 row source 的连接条件的中操作符的不同,可以将连接分为等值连接(如 WHERE A.COL1 = B.COL2)、非等值连接(WHERE A.COL1> B.COL2)、外连接(WHERE A.COL1= B.COL2+))。

继续阅读

MySQL 事务隔离级别与MVCC

一、隔离级别

参考了维基百科:事務隔離

隔离级别规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。

下面对隔离级别的说明都是基于锁机制并发控制的数据库管理系统而言。

1. 可序列化(Serializable)

实现可序列化要求在选定对象上的读锁和写锁保持直到事务结束后才能释放。在 SELECT 的查询中使用一个 WHERE 子句来描述一个范围时应该获得一个“范围锁(range-locks)”。这种机制可以避免“幻影读(phantom reads)”现象。

可序列化是最高级别的隔离。

2. 可重复读(Repeatable read)

该级别保证了同一个事务中多次读取同样的记录的结果是一致的。

对选定对象的读锁(read locks)和写锁(write locks)一直保持到事务结束,但不要求“范围锁(range-locks)”,因此可能会发生“幻影读(phantom reads)”。

幻影读:是因为没有保持范围锁,该事务执行了一个 where 子句的范围查询后,其他事务可能新增了一条处于该事务 where 查询范围内的记录,那么该事务再次执行范围查询时就会看到这些新增的记录行(幻行,Phantom row)。

可重复读是 MySQL 的默认事务隔离级别。

3.提交读(Read committed)

该级别也叫不可重复读(nonrepeatable read)。

DBMS需要对选定对象的写锁(write locks)一直保持到事务结束,但是读锁(read locks)在SELECT操作完成后马上释放(因此“不可重复读”现象可能会发生,见下面描述)。和前一种隔离级别一样,也不要求“范围锁(range-locks)”。

不可重复读是因为,事务只维持了选定对象的写锁,如果一些选定对象只涉及读锁,那么在读锁释放之后,其它事务可以对这些对象进行修改,该事务再次读取时就不一致了。

大多数数据库的默认事务隔离级别都是这个。

4. 未提交读(Read uncommitted)

也称为脏读(dirty read)。

一个事务可以读取到其它事务未提交的更改。

不可重复读的重点是修改:同样的条件,读取过的数据,再次读取出来发现值不一样了。

幻读的重点在于新增或者删除:同样的条件,第 1 次和第 2 次读出来的记录数不一样。

继续阅读