diff --git a/notes/MySQL.md b/notes/MySQL.md index fe9ba7da..0e39a69c 100644 --- a/notes/MySQL.md +++ b/notes/MySQL.md @@ -9,10 +9,10 @@ * [字符串](#字符串) * [时间和日期](#时间和日期) * [三、索引](#三索引) + * [B-Tree 和 B+Tree 原理](#b-tree-和-btree-原理) * [索引分类](#索引分类) * [索引的优点](#索引的优点) * [索引优化](#索引优化) - * [B-Tree 和 B+Tree 原理](#b-tree-和-btree-原理) * [四、查询性能优化](#四查询性能优化) * [使用 Explain 进行分析](#使用-explain-进行分析) * [优化数据访问](#优化数据访问) @@ -34,7 +34,7 @@ InnoDB 是 MySQL 默认的事务型存储引擎,只有在需要 InnoDB 不支 采用 MVCC 来支持高并发,并且实现了四个标准的隔离级别,默认级别是可重复读(REPEATABLE READ),并且通过间隙锁(next-key locking)策略防止幻读的出现。间隙锁使得 InnoDB 不仅仅锁定查询涉及的行,还会对索引中的间隙进行锁定,以防止幻影行的插入。 -表是基于聚簇索引建立的,它对主键的查询性能有很高的提升。 +表是基于聚簇索引建立的,它对主键的查询性能有很大的提升。 内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够自动在内存中创建哈希索引以加速读操作的自适应哈希索引、能够加速插入操作的插入缓冲区等。 @@ -42,7 +42,7 @@ InnoDB 是 MySQL 默认的事务型存储引擎,只有在需要 InnoDB 不支 ## MyISAM -MyISAM 提供了大量的特性,包括全文索引、压缩表、空间数据索引等。应该注意的是,MySQL 5.6.4 也添加了对 InnoDB 存储引擎的全文索引支持。 +MyISAM 提供了大量的特性,包括压缩表、空间数据索引等。 不支持事务。 @@ -120,6 +120,58 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提 对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效。对于中到大型的表,索引就非常有效。但是对于特大型的表,建立和使用索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。 +## B-Tree 和 B+Tree 原理 + +### 1. B-Tree + +

+ +定义一条数据记录为一个二元组 [key, data],B-Tree 是满足下列条件的数据结构: + +- 所有叶节点具有相同的深度,也就是说 B-Tree 是平衡的; +- 一个节点中的 key 从左到右非递减排列; +- 如果某个指针的左右相邻 key 分别是 keyi 和 keyi+1,且不为 null,则该指针指向节点的所有 key 大于等于 keyi 且小于等于 keyi+1。 + +查找算法:首先在根节点进行二分查找,如果找到则返回对应节点的 data,否则在相应区间的指针指向的节点递归进行查找。 + +由于插入删除新的数据记录会破坏 B-Tree 的性质,因此在插入删除时,需要对树进行一个分裂、合并、转移等操作以保持 B-Tree 性质。 + +### 2. B+Tree + +

+ +与 B-Tree 相比,B+Tree 有以下不同点: + +- 每个节点的指针上限为 2d 而不是 2d+1(d 为节点的出度); +- 内节点不存储 data,只存储 key; +- 叶子节点不存储指针。 + +### 3. 顺序访问指针 + +

+ +一般在数据库系统或文件系统中使用的 B+Tree 结构都在经典 B+Tree 基础上进行了优化,在叶子节点增加了顺序访问指针,做这个优化的目的是为了提高区间访问的性能。 + +### 4. B+Tree 和 B-Tree 优势 + +红黑树等平衡树也可以用来实现索引,但是文件系统及数据库系统普遍采用 B+Tree 和 B-Tree 作为索引结构,主要有以下两个原因: + +**(一)更少的检索次数** + +平衡树检索数据的时间复杂度等于树高 h,而树高大致为 O(h)=O(logdN),其中 d 为每个节点的出度。 + +红黑树的出度为 2,而 B+Tree 与 B-Tree 的出度一般都非常大。红黑树的树高 h 很明显比 B+Tree 和 B-Tree 大非常多,因此检索的次数也就更多。 + +B+Tree 相比于 B-Tree 更适合外存索引,因为 B+Tree 内节点去掉了 data 域,因此可以拥有更大的出度,检索效率会更高。 + +**(二)利用计算机预读特性** + +为了减少磁盘 I/O,磁盘往往不是严格按需读取,而是每次都会预读。这样做的理论依据是计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。预读过程中,磁盘进行顺序读取,顺序读取不需要进行磁盘寻道,并且只需要很短的旋转时间,因此速度会非常快。 + +操作系统一般将内存和磁盘分割成固态大小的块,每一块称为一页,内存与磁盘以页为单位交换数据。数据库系统将索引的一个节点的大小设置为页的大小,使得一次 I/O 就能完全载入一个节点,并且可以利用预读特性,临近的节点也能够被预先载入。 + +更多内容请参考:[MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) + ## 索引分类 ### 1. B+Tree 索引 @@ -130,13 +182,9 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提 B+Tree 索引是大多数 MySQL 存储引擎的默认索引类型。 -因为不再需要进行全表扫描,只需要对树进行搜索即可,因此查找速度快很多。 +因为不再需要进行全表扫描,只需要对树进行搜索即可,因此查找速度快很多。除了用于查找,还可以用于排序和分组。 -可以指定多个列作为索引列,多个索引列共同组成键。B+Tree 索引适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。 - -除了用于查找,还可以用于排序和分组。 - -如果不是按照索引列的顺序进行查找,则无法使用索引。 +可以指定多个列作为索引列,多个索引列共同组成键。B+Tree 索引适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。如果不是按照索引列的顺序进行查找,则无法使用索引。 ### 2. 哈希索引 @@ -163,9 +211,9 @@ MyISAM 存储引擎支持空间数据索引,可以用于地理数据存储。 ### 4. 全文索引 -MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较索引中的值。 +MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比值是否相等。查找条件使用 MATCH AGAINST,而不是普通的 WHERE。 -查找条件使用 MATCH AGAINST,而不是普通的 WHERE。 +InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。 ## 索引的优点 @@ -187,13 +235,7 @@ MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而 SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5; ``` -### 2. 前缀索引 - -对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。 - -对于前缀长度的选取需要根据 **索引选择性** 来确定:不重复的索引值和记录总数的比值。选择性越高,查询效率也越高。最大值为 1,此时每个记录都有唯一的索引与其对应。 - -### 3. 多列索引 +### 2. 多列索引 在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 film_id 设置为多列索引。 @@ -202,9 +244,11 @@ SELECT film_id, actor_ id FROM sakila.film_actor WhERE actor_id = 1 AND film_id = 1; ``` -### 4. 索引列的顺序 +### 3. 索引列的顺序 -让选择性最强的索引列放在前面,例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。 +让选择性最强的索引列放在前面,索引的选择性是指:不重复的索引值和记录总数的比值。最大值为 1,此时每个记录都有唯一的索引与其对应。选择性越高,查询效率也越高。 + +例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。 ```sql SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity, @@ -219,7 +263,23 @@ customer_id_selectivity: 0.0373 COUNT(*): 16049 ``` -### 5. 聚簇索引 +### 4. 前缀索引 + +对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。 + +对于前缀长度的选取需要根据索引选择性来确定。 + +### 5. 覆盖索引 + +索引包含所有需要查询的字段的值。 + +**优点** + +- 因为索引条目通常远小于数据行的大小,所以若只读取索引,能大大减少数据访问量。 +- 一些存储引擎(例如 MyISAM)在内存中只缓存索引,而数据依赖于操作系统来缓存。因此,只访问索引可以不使用系统调用(通常比较费时)。 +- 对于 InnoDB 引擎,若二级索引能够覆盖查询,则无需访问聚簇索引。 + +### 6. 聚簇索引

@@ -231,77 +291,16 @@ customer_id_selectivity: 0.0373 **优点** -1. 可以把相关数据保存在一起,减少 I/O 操作。例如电子邮件表可以根据用户 ID 来聚集数据,这样只需要从磁盘读取少数的数据也就能获取某个用户的全部邮件,如果没有使用聚聚簇索引,则每封邮件都可能导致一次磁盘 I/O。 -2. 数据访问更快。 +- 可以把相关数据保存在一起,减少 I/O 操作。例如电子邮件表可以根据用户 ID 来聚集数据,这样只需要从磁盘读取少数的数据也就能获取某个用户的全部邮件,如果没有使用聚聚簇索引,则每封邮件都可能导致一次磁盘 I/O。 +- 数据访问更快。 **缺点** -1. 聚簇索引最大限度提高了 I/O 密集型应用的性能,但是如果数据全部放在内存,就没必要用聚簇索引。 -2. 插入速度严重依赖于插入顺序,按主键的顺序插入是最快的。 -3. 更新操作代价很高,因为每个被更新的行都会移动到新的位置。 -4. 当插入到某个已满的页中,存储引擎会将该页分裂成两个页面来容纳该行,页分裂会导致表占用更多的磁盘空间。 -5. 如果行比较稀疏,或者由于页分裂导致数据存储不连续时,聚簇索引可能导致全表扫描速度变慢。 - -### 6. 覆盖索引 - -索引包含所有需要查询的字段的值。 - -**优点** - -1. 因为索引条目通常远小于数据行的大小,所以若只读取索引,能大大减少数据访问量。 -2. 一些存储引擎(例如 MyISAM)在内存中只缓存索引,而数据依赖于操作系统来缓存。因此,只访问索引可以不使用系统调用(通常比较费时)。 -3. 对于 InnoDB 引擎,若二级索引能够覆盖查询,则无需访问聚簇索引。 - -## B-Tree 和 B+Tree 原理 - -### 1. B-Tree - -

- -定义一条数据记录为一个二元组 [key, data],B-Tree 是满足下列条件的数据结构: - -- 所有叶节点具有相同的深度,也就是说 B-Tree 是平衡的; -- 一个节点中的 key 从左到右非递减排列; -- 如果某个指针的左右相邻 key 分别是 keyi 和 keyi+1,且不为 null,则该指针指向节点的所有 key 大于等于 keyi 且小于等于 keyi+1。 - -在 B-Tree 中按 key 检索数据的算法非常直观:首先在根节点进行二分查找,如果找到则返回对应节点的 data,否则在相应区间的指针指向的节点递归进行查找。 - -由于插入删除新的数据记录会破坏 B-Tree 的性质,因此在插入删除时,需要对树进行一个分裂、合并、转移等操作以保持 B-Tree 性质。 - -### 2. B+Tree - -

- -与 B-Tree 相比,B+Tree 有以下不同点: - -- 每个节点的指针上限为 2d 而不是 2d+1; -- 内节点不存储 data,只存储 key,叶子节点不存储指针。 - -### 3. 带有顺序访问指针的 B+Tree - -

- -一般在数据库系统或文件系统中使用的 B+Tree 结构都在经典 B+Tree 基础上进行了优化,在叶子节点增加了顺序访问指针,做这个优化的目的是为了提高区间访问的性能。 - -### 4. 为什么使用 B+Tree 和 B-Tree - -红黑树等平衡树也可以用来实现索引,但是文件系统及数据库系统普遍采用 B+Tree B-Tree 作为索引结构,主要有以下两个原因: - -**(一)更少的检索次数** - -红黑树和 B+Tree B-Tree 检索数据的时间复杂度等于树高 h,而树高大致为 O(h)=O(logdN),其中 d 为每个节点的出度。 - -红黑树的出度为 2,而 B+Tree 与 B-Tree 的出度一般都非常大。红黑树的树高 h 很明显比 B+Tree B-Tree 大非常多,因此检索的次数也就更多。 - -B+Tree 相比于 B-Tree 更适合外存索引,因为 B+Tree 内节点去掉了 data 域,因此可以拥有更大的出度,检索效率会更高。 - -**(二)利用计算机预读特性** - -为了减少磁盘 I/O,磁盘往往不是严格按需读取,而是每次都会预读。这样做的理论依据是计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。预读过程中,磁盘进行顺序读取,顺序读取不需要进行磁盘寻道,并且只需要很短的旋转时间,因此速度会非常快。 - -操作系统一般将内存和磁盘分割成固态大小的块,每一块称为一页,内存与磁盘以页为单位交换数据。数据库系统将索引的一个节点的大小设置为页的大小,使得一次 I/O 就能完全载入一个节点,并且可以利用预读特性,临近的节点也能够被预先载入。 - -更多内容请参考:[MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) +- 聚簇索引最大限度提高了 I/O 密集型应用的性能,但是如果数据全部放在内存,就没必要用聚簇索引。 +- 插入速度严重依赖于插入顺序,按主键的顺序插入是最快的。 +- 更新操作代价很高,因为每个被更新的行都会移动到新的位置。 +- 当插入到某个已满的页中,存储引擎会将该页分裂成两个页面来容纳该行,页分裂会导致表占用更多的磁盘空间。 +- 如果行比较稀疏,或者由于页分裂导致数据存储不连续时,聚簇索引可能导致全表扫描速度变慢。 # 四、查询性能优化 @@ -355,14 +354,14 @@ do { } while rows_affected > 0 ``` -### 2. 分解关联查询 +### 2. 分解大连接查询 -将一个关联查询分解成对每一个表进行一次单表查询,然后将结果在应用程序中进行关联,这样做的好处有: +将一个大连接查询(JOIN)分解成对每一个表进行一次单表查询,然后将结果在应用程序中进行关联,这样做的好处有: -- 让缓存更高效。对于关联查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用。 +- 让缓存更高效。对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用。 - 减少锁竞争; -- 在应用层进行关联,可以更容易对数据库进行拆分,从而更容易做到高性能和可扩展。 -- 查询本身效率也可能会有所提升。例如下面的例子中,使用 IN() 代替关联查询,可以让 MySQL 按照 ID 顺序进行查询,这可能比随机的关联要更高效。 +- 在应用层进行连接,可以更容易对数据库进行拆分,从而更容易做到高性能和可扩展。 +- 查询本身效率也可能会有所提升。例如下面的例子中,使用 IN() 代替连接查询,可以让 MySQL 按照 ID 顺序进行查询,这可能比随机的连接要更高效。 - 分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而减少冗余记录的查询。 ```sql