MySQL技术内幕-InnoDB存储引擎读书笔记

MySQL技术内幕-InnoDB存储引擎读书笔记

第1章 MySQL体系结构和存储引擎

1.1 定义数据库和实例

  1. 数据库:物理操作系统文件或其他形式文件类型的集合;
  2. 实例:MySQL数据库由后台线程以及一个共享内存区组成;在系统上的表现就是一个进程。

1.2 MySQL体系结构

  1. 数据库是由一个个文件组成,要对这些文件执行增删查改等数据库操作是不能通过简单的操作文件来更改数据库的内容,需要通过数据库实例来完成对数据库的操作。
  2. 存储引擎是基于表的,而不是数据库。

1.3 MySQL存储引擎

  1. MySQL数据库的核心在于存储引擎;
  2. InnoBD
    1. 支持事务、行锁设计、支持外检、非锁定读;
    2. 只要面向在线事务处理(OLTP)
    3. 后续详细介绍
  3. MyISAM
    1. 不支持事务、表锁设计
    2. 支持全文索引,只要面向 OLAP 数据库应用
    3. 缓冲池只缓冲索引文件,而不缓冲数据文件,数据文件的缓冲交给操作系统完成
  4. NDB
    1. 集群存储引擎
    2. 数据全放在内存中(MySQL5.1 开始非索引数据放磁盘),因此主键查找极快
    3. 添加NDB数据存储节点可以线性提高数据库性能,是高可用、高性能的集群系统
  5. Memory
    1. 将表中数据存放在内存中
    2. 适用于存储临时数据的临时表
    3. 默认使用哈希索引,而非 B+ 索引
    4. 只支持表锁,并发性能差,不支持 TEXT和BLOB列类型
    5. 浪费内存
  6. Archive
    1. 只支持 INSERT 和 SELECT
    2. 设计目标:提供高速的插入和压缩功能
  7. Federated
    1. 只支持 MySQL 数据库表,不支持异构数据库表
  8. Maria
    1. 设计目标:用来取代 MyISAM 从而成为 MySQL 的默认存储引擎。
    2. 支持缓存数据和索引文件,应用了行锁设计,提供 MVCC 功能,支持事务和非事务安全的选项,以及更好的 BLOB 字符类型的处理性能
  9. 问题?
  • 为什么 MySQL 不支持全文索引?
    • 答:支持,MyISAM、InnoDB都支持
  • MySQL 数据库快是因为不支持事务?
    • 答:MyISAM 不支持,但是 InnoDB 支持。“快”是相对于不同应用来说的,对于 ETL 这种操作,MyISAM 更有优势,但在 OLTP 环境中,InnoDB 效率更高;

1.4 连接 MySQL

  1. 连接 MySQL操作是一个连接进程和 MySQL 数据库实例进行通信;
  2. 从程序设计的角度来看,本质上是进程通信

第2章 InnoDB存储引擎

2.1 概述

  1. 第一个完整支持 ACID 事务的 MySQL 存储引擎
  2. 特点:行锁设计、MVCC、支持外键,提供一致性非锁定读,同时被设计用来最有效地利用以及使用内存和 CPU。

2.3 InnoDB 体系架构

  1. 后台线程的主要作用是负责刷新内存池中的数据,保证缓冲池中的内存缓存的是最近的数据;
  2. 此外,将已修改的数据文件刷新到磁盘文件,同时保证在数据库发生异常的情况下 InnoDB 能恢复到正常运行状态。

2.3.1 后台线程

InnoDB 是多线程模型,顾后台有多个不同的后台线程;

  1. Master Thread

非常核心的后台线程,主要负责将缓冲池中的数据异步刷新到磁盘,保证数据一致性。包括脏页的刷新、合并插入缓冲。UNDO 页的回收。

  1. IO Thread

在 InnoDB 存储引擎中大量使用 AIO(Async IO)来处理写 IO 请求,这样可以极大提高数据库的性能。该线程主要负责这些 IO 请求的回调处理。

  1. Purge Thread

事务被提交后,其所使用的 undolog 可能不再需要,因此需要该线程来回收已经使用并分配的 undo 页。从 InnoDB 1.2 开始,支持多个 Purge Thread,为了进一步加快 undo 页的回收,由于该线程需要离散读取 undo 页,这样也能更进一步利用磁盘的随机读取性能。

mysql> show variables like 'innodb_purge_threads';
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| innodb_purge_threads | 4     |
+----------------------+-------+
1 row in set, 1 warning (0.21 sec)
  1. Page Cleaner Thread

在 InnoDB 1.2.x 版本引入。作用是将之前版本中脏页的刷新操作都放到单独的线程中完成。目的是为了减轻原 Master Thread 的工作及对于用户查询线程的阻塞,进一步提高 InnoDB 存储引擎的性能。

2.3.2 内存

  1. 缓冲池

InnoDB 基于磁盘存储,可看做基于磁盘的数据库系统。由于 CPU 与磁盘之间速度的鸿沟,基于磁盘的数据库系统通常采用缓冲池技术来提高数据库的整体性能。

缓冲池简单来说是一块内存区域,通过内存的速度来弥补磁盘速度较慢对数据库性能的影响。

对于数据库中页的修改操作,首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。

InnoDB内存数据对象

  1. LRU List、Free List 和 Flush List

数据库中的缓冲池是通过 **LRU(Least Recently Used 最近最少使用算法)**进行管理。即最频繁使用的页在 LRU 列表的前端,最少使用的在尾端。当缓冲池不能存放新读取到的页时,首先释放 LRU 列表尾部的页。

InnoDB 对 LRU 进行改进,引入 midpoint 位置,将新读取到的页放入这个位置,该位置之前为 new 列表,之后为 old 列表。还引入 innodb_old_blocks_time 用于表示页读取到 mid 位置后需要等待多久时间才会被加入到 LRU 的热端,可以避免 LRU 列表中的热点数据被刷出。

  1. 重做日志缓冲

重做日志缓冲刷新到外部磁盘的重做日志文件

  • Master Thread 每一秒将缓冲刷新到日志;
  • 每个事务提交时会将缓冲刷新到日志;
  • 当重做日志缓冲池(8M)剩余空间小于 1/2 时,缓冲刷新到日志。
  1. 额外的内存池

2.4 CheckPoint 技术

解决如下几个问题

  1. 缩短数据库的恢复时间;
  2. 缓冲池不够用时,将脏页刷新到磁盘;
  3. 重做日志不可用时,刷新脏页。

在 InnoDB 引擎内部,有两种 CheckPoint,分别是:

  1. Sharp CheckPoint:默认的,在数据库关闭时将所有的脏页都刷新回磁盘
  2. Fuzzy CheckPoint:只刷新一部分脏页,而不是所有脏页

2.5 Master Thread 工作方式

2.6 InnoDB 关键特性

2.6.1 插入缓冲

1. Insert Buffer

非缓冲池一部分,而是和数据页一样,是物理页的一个组成部分。

满足:索引是辅助索引、索引不唯一。

2. Change Buffer

适用对象是非唯一的辅助索引。

3. Merge Insert Buffer

2.6.2 两次写

如果说 Insert Buffer 带给 InnoDB 存储引擎的是性能上的提升,那么 doublewrite 带给他的就是数据页的可靠性。

有些文件系统本身就提供了部分写失效的防范机制,如 ZFS 文件系统,那么用户就不用启动 doublewrite 了。

2.6.3 自适应哈希索引

InnoDB 存储引擎会监控对表上各索引页的查询,如果观察到建立哈希索引可以带来速度提升,则建立哈希索引,称之为自适应哈希索引(Adaptive Hash Index-AHI)。

AHI 是通过缓冲池的 B+ 树页构造而来,建立的速度快,不需要对整张表构建哈希索引。InnoDB 会自动根据访问的频率和模式来自动为某些热点页建立哈希索引。

2.6.4 异步 IO

异步 IO(Asynchronous IO-AIO)方式处理磁盘操作能提高磁盘操作性能。

2.6.5 刷新邻接页


第4章 表

表是关于特定实体的数据集合。

4.1 索引组织表

在 InnoDB 中,表都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表。在 InnoDB 中,每张表都有一个主键,如果在创建表时没有显示地定义主键,则 InnoDB 会按如下方式选择或创建主键:

  • 首先判断表中是否有非空的唯一索引(Unique NOT NULL),如果有,则列为主键;
  • 如不符合上述条件,InnoDB 会自动创建一个6字节大小的指针。

当表中有多个非空唯一索引时,InnoDB 会选择创建表时第一个定义的非空唯一索引为主键。主键的选择根据的是定义索引的顺序,而不是建表时列的顺序。

4.2 InnoDB 逻辑存储结构

所有数据都被逻辑地放在一个空间中,称之为**“表空间(tablespace)”。表空间又由段(segment)、区(extent)、页(page)**组成。页也可以称为块。

逻辑存储结构如下所示:

InnoDB逻辑存储结构

4.2.1 表空间

在默认情况下 InnoDB 有一个共享表空间 ibdata1,即所有数据都存放在这个表空间内。

4.2.2 段

表空间由各个段组成,常见的有:数据段、索引段、回滚段

因为 InnoDB 存储引擎表是索引组织的,因此数据即索引,索引即数据。

数据段即为 B+ 树的叶子节点(图4-1 Leaf Node segment),索引段即为 B+ 树的非叶子节点(图4-1 Non-Leaf Node segment)。

4.2.3 区

区是连续页组成的空间,任何情况下,每个区的大小都是 1M。

默认情况下,InnoDB 页的大小为 16KB,即一个区中有64个连续的页。从 InnoDB 1.0.x 开始引入压缩页,页大小可以由参数设置,因此区的数量跟着变化,但是区的总大小始终为 1M

4.2.4 页(块)

页是 InnoDB 磁盘管理的最小单位。 常见的页类型有:

  1. 数据页
  2. undo 页
  3. 系统页
  4. 事务数据页
  5. 插入缓冲位图页
  6. 插入缓冲空闲列表页
  7. 未压缩的二进制大对象页
  8. 压缩的二进制大对象页

4.2.5 行

InnoDB 是面向行(row-oriented)的,也就是数据是按行进行存放的。一页存放的行记录最多为 16 KB / 2 - 200 = 7992 行。

4.3 InnoDB 行记录格式

页中保存着表中一行行的数据,数据库实例的作用之一就是读取页中存放的行记录。InnoDB 提供了两种格式来存放行记录数据。Compact(默认的)和 Redundant

4.3.1 Compact 行记录格式

不管是 CHAR 类型还是 VARCHAR 类型,在 compact 格式下NULL 值都不占用任何存储空间。

4.3.2 Redundant 行记录格式

在 redundant 中,VARCHAR 类型的NULL值不占空间,但是 CHAR 类型 NULL 值要占空间。

4.3.3 行溢出数据

实测,InnoDB 存储引擎能存放 VARCHAR 类型的最大长度为65532字节,而非65535. 一般情况下,InnoDB 存储引擎的数据是存放在页类型为 B-tree node 中。但是当发生行溢出时,数据存放在页类型为 UNcompress BLOB 页中。

4.3.4 Compressed 和 Dynamic 行记录格式

InnoDB 1.0.x 版本引入了新的文件格式(新的页格式)。以前的 Compact 和 Redundant 格式称为 Antelope 文件格式,新的文件格式称为 Barracuda 文件格式。包括,Compressed 和 Dynamic 两种行记录格式。

Compressed ,存储在其中的行数据以 zlib 算法进行压缩,因此对于 BLOB、TEXT、VARCHAR 等大长度类型的数据能够进行非常有效的存储。

4.3.5 CHAR 的行结构存储

在多字节字符集的情况下, CHAR 和 VARCHAR 的实际行存储基本是没有区别的。

4.4 InnoDB 数据页结构

页是 InnoDB 存储引擎管理数据库的最小单位。页类型为 B-tree node 的页存放的即是表中行的实际数据。

InnoDB 数据页由以下7个部分组成:

数据页结构

4.4.1 File Header

记录页的一些头信息,共占用38字节

4.4.2 Page Header

记录数据页的状态信息,由14个部分组成,占用56字节。

4.4.3 Infimum 和 Supremum Record

InnoDB的每个数据页中有两个虚拟的行记录,用于限定记录的边界。这两个值在页创建时被建立,在任何情况下都不会被删除。

4.4.4 User Record 和 Free Space

4.4.5 Page Directory

4.4.6 File Trailer

检测页是否被完整地写入磁盘。占用8字节。

4.6 约束

4.6.1 数据完整性

几乎所有的关系型数据库都提供了约束(constraint)机制,该机制确保了数据库中数据的完整性。

数据完整性有以下三种形式:

  1. 实体完整性保证表中有一个主键。可通过 Primary Key 和 Unique Key 约束来保证,也可编写一个触发器来保证数据完整性。
  2. 域完整性保证数据每列的值满足特定的条件。可通过以下几种途径来保证:
    1. 选择合适的数据类型确保一个数据值满足特定条件;
    2. 外键约束;
    3. 编写触发器;
    4. 考虑用 DEFAULT 约束作为强制域完整性的一个方面。
  3. 参照完整性保证两张表之间的关系。定义外键以强制参照完整性。

对 InnoDB 本身而言,提供一下几种约束:

  • primary key
  • unique key
  • foreign key
  • not null
  • default

4.6.2 约束的创建和查找

创建的两种方式:

  • 表建立时就进行约束定义
  • 利用 ALTER TABLE 命令来进行创建约束

对于主键约束而言,其默认约束名为 PRIMARY;对于 Unique Key 约束,默认约束名和列名一样,也可以人为指定。

通过 information_schema 结构下的 TABLE_CONSTRAINTS 来查看当前 MySQL 库下所有的约束信息。对于外键约束的命名,可通过查看表 REFERENCE_CONSTRAINTS 来详细了解。

4.6.3 约束和索引的区别

确实,当用户创建了一个唯一索引就创建了一个唯一的约束。但约束是一个逻辑的概念,用来保证数据完整性;索引是一个数据结构,既有逻辑上的概念,在数据库中还代表着物理存储的方式。

4.6.4 对错误数据的约束

数据库本身没有对数据的正确性进行约束。如果用户想通过约束对于数据库非法数据的插入或更新,即数据库提供报错而不是警告,那就必须设置参数 sql_mode,用来严格审核输入的参数。

SET sql_mode = 'STRICT_TRANS_TABLES';

4.6.5 ENUM 和 SET 约束

搭配 sql_mode 可以规定某一个域为 ENUM 指定的范围值。

4.6.6 触发器与约束

4.6.7 外键约束

外键用来保证参照完整性。MyISAM 中的外键只是起到一个注释的作用,InnoDB 完整支持外键约束。

用户可以在执行 CREATE TABLE 时就添加外键,也可以在表创建后通过 ALTER TABLE 命令来添加。

一般来说,称被引用的表为父表,引用的表称为子表。外键定义时的 ON DELETE 和 ON UPDATE 表示对父表进行 DELETE 和 UPDATE 操作时,对子表所做的操作。

可定义的子表操作有:

  • CASCADE: 当父表发生 DELETE 和 UPDATE 操作时,对应的子表中的数据也进行 DELETE 和 UPDATE 操作;
  • SET NULL: 当父表发生 DELETE 和 UPDATE 操作时,相应的子表的数据被更新为 NULL,前提是子表中的列允许为 NULL。
  • NO ACTION: 当父表发生 DELETE 和 UPDATE 操作时,抛出错误,不允许这类操作发生。
  • RESTRICT: 当父表发生 DELETE 和 UPDATE 操作时,抛出错误,不允许这类操作发生。如果定义外键时没有指定 ON DELETE 或 ON UPDATE ,RESTRICT 就是默认的外键设置。
  • 指定RESTRICT(或者NO ACTION)和忽略ON DELETE或者ON UPDATE选项的效果是一样的。

外键约束对于参照完整性约束能起到很好的作用,但是对于数据的导入操作,外键约束的检查会浪费大量时间,因为 MySQL 的外键是即时检查的,所以对导入的每一行都会进行外键检查,用户可以在导入过程中忽视外键的检查:

mysql> SET foreign_key_checks = 0;
mysql> LOAD DATA...
mysql> Set foreign_key_checks = 1;

第5章 索引与算法

5.1 InnoDB 存储引擎索引概述

几种常见的索引:

  • B+ 树索引: 目前关系型数据库系统中查找最为常用和最为有效的索引。B+ 树的构造类似于二叉树,根据键值快速找到数据。
  • 全文索引
  • 哈希索引:自适应的,会根据表的使用情况自动为表生成哈希索引,不能认为干预。

B+ 树索引并不能找到一个给定键值的具体行。他只能找到数据行所在的页,然后数据库通过把页读入到内存,再在内存中进行查找,最后得到要查找的数据。

5.2 数据结构与算法

5.2.1 二分查找

每页 Page Directory 中的槽是按照主键顺序存放的,对于某一条具体记录的查询时通过对 Page Directory 进行二分查找得到的。

计算中间索引,避免整型溢出
int mid = left + (right - left) / 2;

5.2.2 二分查找树(BST)和平衡查找树(AVL)

平衡二叉树: 首先符合二叉查找树的定义,其次必须满足任何节点的两个子树的高度最大差为1。

平衡二叉树的查询速度快,但是维护代价较大。通过需要1次或多次左旋和右旋来确保AVL树的平衡性。

5.3 B+ 树

B+ 树是为磁盘或其他直接存取辅助设备设计的一种平衡查找树。在 B+ 树中,所有记录节点都是按键值的大小顺序存放在同一层的叶子节点上,由各叶子节点指针进行连接。

5.3.1 B+ 树的插入操作

三种情况,涉及三种插入算法:

  1. Leaf Page 不满,Index Page 不满
  2. Leaf Page 满,Index Page 不满
  3. Leaf Page 满,Index Page 满

2,3 都涉及拆分页的操作,应在可能的情况下尽量减少页的拆分操作。B+ 树提供了类似于 AVL 树的旋转功能减少页的拆分。

5.3.2 B+ 树的删除操作

B+ 树使用填充因子来控制树的删除变化,50%是填充因子可设的最小值。也分为三种情况:

  1. 叶子节点 > 填充因子,中间结点 > 填充因子。
  2. 叶子节点 < 填充因子,中间结点 > 填充因子。
  3. 叶子节点 < 填充因子,中间结点 < 填充因子。

可参考原书 p187-p191

5.4 B+ 树索引

在第4章 段 中提及。

在数据库中,B+ 树的高度一般都在2 ~ 4 层。

数据库中的 B+ 树索引可以分为聚集索引(clustered index)辅助索引(secondary index)。聚集索引与辅助索引不同的是,叶子节点存放的是不是一整行的信息。

5.4.1 聚集索引

聚集索引根据每张表的主键构造一棵 B+ 树,同时叶子节点中存放的即为整张表的行记录数据,也将聚集索引的叶子节点称为数据页。聚集索引的这个特性决定了索引组织表中数据也是索引的一部分(因为要先利用数据去找到叶子节点)。每个数据页都通过一个双向链表进行连接。

查询优化器倾向于采用聚集索引。因为聚集索引能够在 B+ 树索引的叶子节点上直接找到数据。此外,由于定义了数据的逻辑顺序,聚集索引能够很快地访问针对范围值的查询。查询优化器能够快速发现某一段范围的数据页需要扫描。

数据页上存放的是完整的每行的记录,而在非数据页的索引页中,存放的仅仅是键值及指向数据页的偏移量,而不是一个完整的行记录。

聚集索引的存储并不是物理上连续的,而是逻辑连续的。

聚集索引对于主键的排序查找和范围查找速度很快。

5.4.2 辅助索引

也称非聚集索引,叶子节点并不包含行记录的全部数据。叶子节点除了包含键值以外,每个叶子节点中的索引行中还包含了一个书签。该书签用来告诉 InnoDB 存储引擎哪里可以找到与索引相对应的行数据。

5.4.3 B+ 树索引的分裂

5.4.4 B+ 树索引的管理

  1. 索引管理

在非高峰时期,对应用程序下的几张核心表做 ANALYZE TABLE 操作,使得优化器和索引更好地工作。

  1. Fast Index Creation(FIC)

让InnoDB避免创建临时表,只限定于辅助索引,对于主键的创建和删除同样需要重建一张表。

  1. Online Schema Change(OSC-在线架构改变)
  2. Online DDL

5.5 Cardinality 值

5.5.1 什么是 Cardinality

并不是在所有的查询条件中出现的列都需要添加索引。对于什么时候添加 B+ 树索引,一般的经验是,在访问表中很少一部分时使用 B+ 树索引才有意义。

对于性别字段、地区字段、类型字段,他们可能的取值范围很小,称为低选择性

若取值范围很广,几乎没有重复,即属于高选择性,此时使用 B+ 树索引是最适合的。例如,姓名字段。

可以通过 SHOW INDEX 结果中的列 Cardinality 来观察。 Cardinality 值非常关键,表示索引中不重复记录数量的预估值。 实际应用中,Cardinality / n_rows_in_table 应该尽可能接近1。

5.5.2 InnoDB 存储引擎的 Cardinality 统计

5.6 B+ 树索引的使用

5.6.1 不同应用中 B+ 树索引的使用

  1. OLTP(On-Line Transaction Processing)应用

在 OLTP 中,查询操作只从数据库中取得一小部分数据,一般可能只在10条以下,甚至一条。如根据主键值来获得用户信息,根据订单号获得详细的订单信息。这种情况下,B+ 树索引建立后,对该索引的使用应该只是通过该索引取得表中少部分的数据。这时建立 B+ 树索引才是有意义的。

  1. OLAP(On-Line Analytical Processing)应用

访问表中大量数据,根据数据来产生查询结果,查询多是面向分析的查询,目的是为决策者提供支持。

5.6.2 联合索引

联合索引是指对表上的多个列进行索引。创建方法与单个索引的创建方法一致,不同之处在于有多个索引列。

联合索引的一个好处是已经对第二个键值进行了排序处理。例如,在很多情况下,应用程序需要查询某个用户的购物情况,并按照时间排序,最后取出最近三次的购买记录,这时使用联合索引可以避免多一次排序操作,因为索引本身在叶子节点已经排序了。

举例,联合索引(a,b)是根据 a,b 进行排序的,在 a 一定的情况下,b 是排序的。又如,联合索引(a,b,c)是根据 a,b,c 排序的,在 a,b 一定的情况下,c 是排序的,但是在 a 一定的情况下,c 不一定是排序的,还需要再执行一次 filesort操作将 c 排序。在查询中可用。

5.6.3 覆盖索引

覆盖索引(covering index),即从辅助索引中就可以得到查询的记录,而不需要查询聚集索引中的记录。好处是,辅助索引不包含整行记录的所有信息,所以其大小远小于聚集索引,能减少大量 IO 操作。

覆盖索引的另一个好处是针对某些统计问题而言的。Using index 就是代表了优化器进行了覆盖索引操作。

❤:联合索引和覆盖索引都是辅助索引的应用。

5.6.4 优化器选择不使用索引的情况

使用 EXPLAIN 命令进行 SQL 分析时,会发现优化器并没有选择索引去查找数据,而是通过扫描聚集索引,也就是直接进行全表的扫描。

多发生于范围查找,JOIN 链接操作等情况。

5.6.5 索引提示

5.6.6 Multi-Range Read 优化

5.6.7 Index Condition PushDown(ICP)优化

5.7 哈希算法

5.7.1 哈希表

多个关键字映射到一个槽上称之为碰撞,数据库中采用最简单的碰撞解决技术,链接法

链接法把散列到同一个槽中的元素都放在一个列表中,槽 j 中有一个指针,指向由所有映射到槽 j 的元素构成的链表的链表头,如果不存在这样的元素,就为 NULL

数据库中一般采用除法散列的方法。

将 k 映射到 m 个槽中。h(k) = k mod m

5.7.2 哈希函数

对于缓冲池中的页,在缓冲池中的 Page 页都有一个 chain 指针,指向相同哈希函数值的页。对于除法散列,m 的取值为略大于2倍的缓冲池页数量的质数

InnoDB 存储引擎的缓冲池对于其中的页是怎么进行查找的?

答:InnoDB 存储引擎的表空间都有一个 space_id,用户所有查询的应是某个表空间的某个连续16KB 的页,即偏移量 offset。InnoDB 将 space_id 左移20位,然后加上这个 space_id 和 offset,即关键字 K = space_id << 20 + space_id + offset,然后通过除法散列到各个槽中去。

5.7.3 自适应哈希索引

只适用于等值查询,如 Select * from table where index_col = "xxx"

不适用于范围查询。

该索引由 InnoDB 存储引擎自身控制。

5.8 全文检索

5.8.1 概述

搜索引擎需要根据用户输入的关键词进行全文查找等;

全文检索(Full-Text Search)是将存储于数据库中的整本书或整片文章中的任意内容信息查找出来的技术。 它可以根据需要获得全文中有关章、节、段、句、词等信息,也可以进行各种统计和分析。

5.8.2 倒排索引

全文检索通常使用倒排索引实现。同 B+ 树索引一样,也是一种索引结构。它在辅助表中存储了单词与单词自身在一个或多个文档中所在位置之间的映射。有两种表现形式:

  • inverted file index:{单词,单词所在文档的ID}
  • full inverted index:{单词,(单词所在文档ID,在具体文档中的位置)}

full inverted index 占用更多空间,但能更好的定位数据,并扩展搜索功能。

5.8.3 InnoDB 全文检索

采用 full inverted index,将 (DocumentID, Position)看作一个“ilist”。

为了提高全文检索的并行性能,InnoDB 共有6张辅助表,每张表根据 word 的 latin 编码进行分区。

辅助表都是持久的表,存放于磁盘上。

FTS Index Cache 全文检索索引缓存,用来提高全文检索的性能,采用红黑树结构,根据 (word, ilist) 进行排序。

InnoDB 全文检索目前的限制:

  1. 每张表只能有一个全文检索的索引;
  2. 由多列组合而成的全文检索的索引列必须使用相同的字符集与排序规则;
  3. 不支持没有单词界定符的语言,如中文,日韩语

5.8.4 全文检索

MySQL 数据库使用 MATCH()。。。AGAINST()语法支持全文检索的查询

语法为:

MATCH (col1,col2,...) AGAINST (expr [search_modifier])
search_modifier:
{
	IN NATURAL LANGUAGE MODE
	| IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION
	| IN BOOLEAN MODE
	| WITH QUERY EXPANSION
}

以下为3中查询模式:

  1. Natural Language
  2. Boolean
  3. Query Expansion

第6章 锁

人们认为行级锁总会增加开销,实际上,只有当实现本身会增加开销时,行级锁才会增加开销。InnoDB 存储引擎不需要锁升级,因为一个锁和多个锁的开销是一样的。

6.1 什么是锁

数据库系统使用锁是为了支持对共享资源的并发访问,提供数据的完整性和一致性。

InnoDB 存储引擎锁的实现和 Oracle 数据库非常类似,都提供一致性的非锁定读、行级锁支持。行级锁没有相关额外的开销,并可以同时得到并发性和一致性。

6.2 lock 与 latch

  1. latch 一般称为“闩(shuan)锁”(轻量级锁)

因为其要求锁定的时间非常短。若持续的时间长,应用的性能会非常差。InnoDB 存储引擎中。latch 又可以分为 mutex(互斥量)和 rwlock(读写锁)。目的是用来保证并发线程操作临界资源的正确性,通常没有死锁检测机制。

  1. lock(本节关注对象)

对象是事务,用来锁定的是数据库中的对象,如表,页,行。且 lock 的对象仅在事务 commit 或 rollback 后进行释放,有死锁机制。

  1. lock 与 latch 的比较
lock latch
对象 事务 线程
保护 数据库内容 内存数据结构
持续时间 整个事务过程 临界资源
模式 行锁、表锁、意向锁 mutex、rwlock
死锁 死锁检测与处理 无。仅通过应用程序加锁的顺序保证无死锁的发生
存在于 Lock Manager 的哈希表中 每个数据结构的对象中

6.3 InnoDB 存储引擎中的锁

6.3.1 锁的类型

如下两种标准的行级锁:

  • 共享锁(S Lock),允许事务读一行数据;
  • 排它锁(X Lock),允许事务删除或更新一行数据。

如果一个事务 T1 已经获得 记录 r 的共享锁,那么事务 T2 可以立即获得行 r 的共享锁,因为读取并没有改变行 r 的数据,称这种情况为锁兼容。但若有事务 T3 想获得行 r 的排它锁,则必须等待事务 T1, T2 释放行 r 上的共享锁——这种情况称为锁不兼容兼容是指对同一行记录锁的兼容性情况。

X S
X 不兼容 不兼容
S 不兼容 兼容

InnoDB 支持多粒度锁定,这种锁定允许事务在行级上的锁和表级上的锁同时存在。InnoDB 支持意向锁。意向锁是指将锁定的对象分为多个层次,意向锁意味着事务希望在更细粒度上进行加锁,如下图所示:

层次结构

如上图,如果想对最细粒度的对象进行上锁,那么需要首先对粗粒度的对象上锁。

举例:如需要对页上的记录 r 进行上 X 锁,那么需要先对数据库A、表页上意向锁 IX,最后对记录 r 上 X 锁。若其中任何一个部分导致等待,那么该操作需要等待粗粒度锁的完成。举例来说,在对记录 r 加 X 锁之前,已经有事务对表1进行了 S 表锁,那么表1就已经存在 S 锁,之后事务需要对记录 r 在表1上加上 IX 锁,由于不兼容,所以该事务需要等待表锁操作的完成。

意向锁即为表级别的锁,设计目的是为了在一个事务中揭示下一行将被请求的锁类型。

  1. 意向共享锁(IS Lock):事务想要获得一张表中某几行的共享锁;
  2. 意向排它锁(IX Lock):事务想要获得一张表中某几行的排它锁;
IS IX S X
IS 兼容 兼容 兼容 不兼容
IX 兼容 兼容 不兼容 不兼容
S 兼容 不兼容 兼容 不兼容
X 不兼容 不兼容 不兼容 不兼容

由于 InnoDB 支持的是行级别的锁,所以意向锁其实不会阻塞除全表扫以外的任何请求。

6.3.2 一致性非锁定读

consistent nonlocking read 是指 InnoDB 通过行多版本控制的方式来读取当前执行时间数据库中的数据。如果读取的行正在进行 DELETE 或 UPDATE 操作,这时读取操作不会因此去等待行上锁的释放。相反,会去读取行的一个快照数据。

快照数据是指该行的之前版本的数据,该实现是通过 undo 段来完成。undo 用来在事务中回滚数据,因此快照数据本身是没有额外的开销。此外,读取快照数据不需要上锁,因为没有事务需要对历史的数据进行修改操作。

InnoDB 默认读取方式,即读取不会占用和等待表上的锁

一个行记录可能有多个快照数据,一般称这种技术为行多版本技术,由此带来的并发控制,称之为多版本并发控制(Multi Version Concurrency Control)

在事务隔离级别 READ COMMITED 和 REPEATABLE READ (InnoDB 默认隔离级别)下,InnoDB使用非锁定的一致性读。但对于快照数据的定义不同:

  • READ COMMITED 事务隔离级别下,非一致性读总是读取被锁定行的最新一份快照数据;
  • REPEATABLE READ 事务隔离级别下,非一致性读总是读取事务开始时的行数据版本。

6.3.3 一致性锁定读

在某些情况下,用户需要显示地对数据库读取操作进行加锁以保证数据逻辑的一致性。要求数据库支持加锁语句,即使是对 SELECT 的只读操作。两种一致性锁定读:

  • SELECT…FOR UPDATE
  • SELECT…LOCK IN SHARE MODE

SELECT…FOR UPDATE 对读取的行记录加一个 X 锁;

SELECT…LOCK IN SHARE MODE 对读取的行记录加一个 S 锁。

当事务提交了,锁也就释放了。

6.3.4 自增长与锁

InnoDB 内存结构中,对每一个含有自增长值的表都有一个自增长计数器。插入操作会依据这个自增长的计数器增加1赋予自增长列,这个实现方式称作 AUTO-INC Locking,这种锁采用特殊的表锁机制,为了提高插入的性能,锁不是在一个事务完成后释放,而是在完成对自增长值插入的SQL语句后立即释放。

6.3.5 外键和锁

6.4 锁的算法

6.4.1 行锁的3种算法

  • Record Lock:单个行记录上的锁;
  • Gap Lock:间隙锁,锁定一个范围,但不包含记录本身;
  • Next-Key Lock:Gap Lock+Record Lock,锁定一个范围,且锁定记录本身。

当查询的索引含有唯一属性时,InnoDB 会对 Next-Key Lock 进行优化,降级为 Record Lock,即仅锁住索引本身,而不是范围。

6.4.2 解决 Phantom Problem

在默认事务隔离级别下,即 REPEATABLE READ下,InnoDB 采用 Next-Ley Locking 机制来避免 Phantom Problem(幻象问题)。

Phantom Problem 是指:在同一事务下,连续执行两次同样的 SQL 语句可能导致不同的结果,第二次的 SQL 语句可能会返回之前不存在的行。

6.5 锁问题

锁只会带来三种问题,如果可以防止这三种情况的发生,那就不会出现并发异常。

6.5.1 脏读

脏页: 指在缓冲池中已经被修改的页,但是还没有刷新到磁盘中,即数据库实例内存中的页和磁盘中的页的数据是不一致的,当然在刷新到磁盘之前,日志都已经被写入到了重做日志文件中。数据库实例内存与磁盘的异步造成的,不影响数据一致性。脏页的刷新是异步的,带来性能提升。

脏数据: 事务对缓冲池中行记录的修改,并且还没有被提交。如果读到了脏数据,即一个事务可以读到另一个事务中还未提交的数据,则显然违反了数据库的隔离性。

脏读: 可以读到脏数据。

脏读需要事务的隔离级别为 READ UNCOMMITTED,InnoDB 默认 REPEATABLE READ。

6.5.2 不可重复读

在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了一个事务内两次读到的数据是不一样的情况 ,称为不可重复读。

脏读读到的是未提交的数据,不可重复读读到的是已提交的数据,但是违反了事务一致性的要求。

InnoDB 的默认事务隔离级别是 REPEATABLE READ,采用 Next-Key Lock算法,避免了不可重复读。

6.5.3 丢失更新

一个事务的更新操作会被另一个事务的更新操作所覆盖,从而导致数据的不一致。

要避免丢失更新,需要让事务在这种情况下的操作变成串行化,而不是并行操作。

6.7 死锁

6.7.1 死锁的概念

死锁是指两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象。

两种解决办法:

  1. 设置超时

当两个事务互相等待时,当一个等待时间超过设置的某一阈值时,其中一个事务进行回滚,另一个等待的事务就能继续运行。

innodb_lock_wait_timeout 设置超时时间
  1. wait-for graph(等待图),InnoDB 采用

要求数据库保持以下两种信息

  • 锁的信息链表
  • 事务等待链表

根据上述链表可以构造出一张有向图,若在这个图中存在回路,就代表存在死锁。InnoDB 通常选择回滚 undo 量最小的事务。深度优先算法实现。

6.8 锁升级

InnoDB 不存在锁升级的问题,因为其不是根据每个记录来产生行锁的,是根据每个事务访问的每个页对锁进行管理的,采用的是位图的方式。


第7章 事务

引入事务的目的:事务会把数据库从一种一致状态转换为另一种一致状态。在数据库提交工作时,可以确保要么所有修改都已经保存了,那么所有修改都不保存。

InnoDB 中的事务完全符合 ACID 的特性:

  • 原子性(Atomicity)
  • 一致性(Consistency)
  • 隔离性(Isolation)
  • 持久性(Durability)

7.1 事务

7.1.1 概念

在事务中的操作,要么都做修改,要么都不做。

InnoDB 默认的事务隔离级别 REPEATABLE READ 完全遵循和满足事务的 ACID 性。

  • 原子性

指整个数据库事务是不可分割的工作单位。只有使事务中的所有数据库操作都执行成功,才算整个事务成功。事务中任何一个 SQL 语句执行失败,已经执行成功的 SQL 语句也必须撤销,数据库状态应该退回到执行事务前的状态。

  • 一致性

事务开始前和结束后,数据库的完整性约束没有被破坏。事务是一致性的单位,如果事务中某个动作失败了,系统可以自动撤销事务——范围初始化的状态。

  • 隔离性(又称:并发控制、可串行化、锁)

事务提交前对其他事务都不可见,通常用锁来实现。

  • 持久性

事务一旦提交,其结果就是永久性的。即使发生宕机故障,数据库也能够将数据恢复。

7.1.2 分类

  • 扁平事务
  • 带有保存点的扁平事务
  • 链事务
  • 嵌套事务
  • 分布式事务

7.2 事务的实现

redo log 称为重做日志,用来保证事务的原子性和持久性。undo log 用来保证事务的一致性。锁保证事务的隔离性。

redo 和 undo 的作用都可以视为一种恢复操作,redo 恢复提交事务修改的页操作,undo 回滚行记录到某个特定版本。

redo 通常是物理日志,记录的是页的物理修改操作;undo 是逻辑日志,根据每行记录进行记录。

7.2.1 redo

  1. 基本概念

①内存中的重做日志缓冲,易失;②重做日志文件,持久的;

持久化,即当事务提交时,必须先将该事务的所有日志写入到重做日志文件进行持久化,待事务的提交(commit)操作完成才算完成。

为确保每次日志都写入重做日志文件,在每次将重做日志缓冲写入重做日志文件后,InnoDB 都需要调用一次 fsync 操作。由于重做日志文件打开并没有使用 O_DIRECT 选项,因此重做日志缓冲先写入文件系统缓存,为了确保重做日志写入磁盘,必须进行一次 fsync 操作。磁盘的性能决定了提交事务的性能,也就是数据库的性能。

也可以手工设置非持久化的情况发生,提高数据库性能,即设置等待一个周期才进行 fsync 操作,但当数据库发生宕机时,由于部分日志未刷新到磁盘,会丢失部分为刷新到磁盘的事务。

  1. log block

InnoDB 中,重做日志都是以512字节进行存储的。故,重做日志缓冲,重做日志文件都是以方式进行保存的,称之为重做日志块。

7.2.2 undo

  1. 基本概念

undo 存放在数据库内部的一个特殊段中,称为 undo 段,位于共享表空间中。

在对数据库进行修改时,不但会产生 redo,还会产生一定量的 undo。

undo log 的产生会伴随 redo log 的产生,因为undo log 也需要持久化的保护。

7.6 事务的隔离级别

SQL 标准定义的四种隔离级别为:

  1. READ UNCOMMITTED
  2. READ COMMITTED
  3. REPEATABLE READ
  4. SERIALIZABLE
发布了16 篇原创文章 · 获赞 2 · 访问量 1271

猜你喜欢

转载自blog.csdn.net/yx185/article/details/103980635