MySQL 高性能的索引策略

重新看了一遍做得记录。

独立的列

索引列不能是表达式的一部分,也不能是函数的参数。在 where 语句里,始终将索引列单独放在比较符号的一侧。

前缀索引和索引的选择性

选择性是指不重复的索引值和数据表的记录总数的比值。
选择性越高查询效率越高,唯一索引的选择性是 1.

对于很长的索引列,判等它的前缀是否有足够的选择性。
MySQL 无法用前缀索引做 order bygroup by ,也无法用前缀索引做覆盖扫描。

多列索引

MySQL 5.0 引入索引合并策略。索引合并使用的范围: OR 条件的联合,AND 条件的相交,组合前两种条件的组合和相交。
索引合并暗示着不良的设计,可以考虑组合索引。

索引列顺序

正确的顺序依赖于使用该索引的查询,并且同时需要考虑如何更好地满足排序和分组的需要。

将选择性搞的列放在最前列,通常不如避免随机 I/O 和排序重要。
不需要考虑排序、分组时,应将选择性最高的列放在最前面。

性能不只是依赖于所有索引的列的选择性,也和查询条件的具体值有关,也就是和值的分布有关。

继续阅读

MySQL datetime 与时区

问题

最近碰到这样的问题:jdbc 插入 timestamp 类型的数据到 MySQL 的 datetime 类型列时,小时会变成 12 小时制了。比如 2017-04-11 15:24:32 变成 2017-04-11 03:24:32

一开始以为是默认时区设置导致,后面发现不是,在 MySQL client 命令行里直接插入是不存在这样问题的。

如果是时区问题,跟本地时间相差不会是 12 小时的。

绕过方法:
1. 直接使用 MySQL 的 now() 函数;
2. 在 Java 里把 timestamp 转换为 yyyy-MM-dd HH:mm:ss 格式字符串,然后用 MySQL 的 convert 函数进行转换。

时区

CET, Central European Time,欧洲中部时间:比世界标准时早一个小时。冬季时间为 UTC+1,夏季欧洲夏令时为 UTC+2

UTC, Coordinated Universal Time, 世界标准时间或世界协调时间:

GMT, Greenwich Mean Time, 格林尼治标准时间。

CST, China Standard Time, 中国标准时间。

CST 同时可以代表如下 4 个不同的时区:
Central Standard Time (USA) UT-6:00
Central Standard Time (Australia) UT+9:30
China Standard Time UT+8:00
Cuba Standard Time UT-4:00

关系:
CET = UTC/GMT + 1小时
CST = UTC/GMT + 8小时
CST = CET + 9小时

show variables like '%time_zone%';
set time_zone='+8:00';

欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。

《高性能 MySQL》 — 第五章 创建高性能的索引

索引基础

索引的使用:现在索引中找到对应值,然后根据匹配的索引记录找到对应的数据行。

对于有多列的索引,MySQL 只能高效地使用索引的最左前缀列。因此列的顺序很重要。

MySQL 的唯一限制和主键限制都是通过索引实现。

索引的类型

MySQL 中,索引是在存储引擎层而不是服务器层实现的,所以没有统一的索引标准。

B-Tree 索引

B-Tree 通常意味着所有的值都是按顺序存储的,并且每一个叶子页到根的距离相等,适合范围查询。

btree-structure

B-Tree 索引适用于全键值、键值范围或键前缀查找,其中键前缀查找只适用于根据最左前缀的查找。

索引还可以用于查询中的 order by 操作。

B-Tree 索引的限制:

  • 如果不是安装索引的最左列开始查找,则无法使用索引。
  • 不能跳过索引中的列。
  • 如果查询中有某个列的范围查询,则其右边所有列都无法使用索引优化查找。

这些限制是 MySQL 优化器和存储引擎使用索引的方式导致的。

继续阅读

《高性能 MySQL》 — 第四章 Schema 与数据类型优化

选择优化的数据类型

选择数据类型的一般原则:

  • 更小的通常更好:尽量使用可以正确存储数据的最小数据类型。

  • 简单就好:简单数据类型的操作通常需要更少的 CPU 周期。例如整型比字符操作代价更低,以为字符集和校对规则(排序规则)使字符比较比整型更复杂。举例,应该使用 MySQL 内建的类型(date, time, datetime) 而不是字符串来存储日期和时间,应该使用整型来存储 IP 地址。

  • 尽量避免 NULL:如果查询中包含可为 NULL 的列,对 MySQL 来说更难优化,因为可为 NULL 的列使得索引、索引统计和值比较都更复杂。可为 NULL 的列会使用更多的存储空间,在 MySQL 里也需要特殊处理。当可为 NULL 的列被索引时,每个索引记录需要一个额外的字节,在 MyISAM 里甚至还可能导致固定大小的索引变成可变大小的索引。例外,对于稀疏数据(很多列的值为 NULL),InnoDB 使用单独的位(bit)存储 NULL 值,有很好的空间效率。

在为列选择数据类型时,第一步需要确定合适的大类型:数字、字符串、时间等。下一步是选择具体类型。

继续阅读

《基于 Oracle 的 SQL 优化》笔记 第三章 Oracle 里的 Cursor 和变量绑定

系统性能随着并发数量的递增而显著下降的现象,往往是因为没有使用绑定变量而产生大量硬解析所致。

Cursor,游标

Cursor 是 Oracle 数据库里 SQL 解析和执行的载体。有两种类型的 Cursor:Shared Cursor,Session Cursor。

库缓存对象

库缓存(Libaray Cache)实际上是 SGA 中的一块内存(更确切说是 Shared Pool 中的一块内存区域),它的主要作用是缓存刚刚执行过的 SQL 语句和 PL/SQL 语句(如存储过程、函数、包、触发器)所对应的执行计划、解析树(Parse Tree)、Pcode、Mcode 等对象,当同样的 SQL 语句和 PL/SQL 语句再次被执行时,就可以利用缓存在 Libaray Cache 中的那些对象而无须再次从头开始解析,这就提高了这些 SQL 语句和 PL/SQL 语句在重复执行时的执行效率。
Oracle-arch

缓存在库缓存中的对象称为库缓存对象(Library Cache Object),所有的库缓存对象都是以一种名为库缓存对象句柄(Library Cache Object Handle)的结构存储在库缓存中的,Oracle 是通过访问相关的库缓存对象句柄来访问对应的库缓存对象的。

库缓存对象句柄是以哈希表(Hash Table)的方式存储在库缓存中的。

继续阅读

《基于 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。

继续阅读