可以考虑使用 命名管道 或 共享内存 进行通信
使用 命名管道 来进行进程间通信
需要在启动服务器程序的命令中加上 --enable-named-pipe
参数,然后在启动客户端程序的命令中加入 --pipe
或者 --protocol=pipe
参数
使用 共享内存 来进行进程间通信
需要在启动服务器程序的命令上加上 shared-memory
参数,然后成功启动服务器后 共享内存 便成为本地客户端程序的默连接方式,不过我们也可以在启动客户端程序的命令中加入 --protocol=memory
参数来显示的指定使用共享内存进行通信
需要注意的是,使用 共享内存 的方式进行通信的服务器进程和客户端进程必须在同一台 Windows
主机中。
如果服务器和客户端进程都运行在同一台操作系统为类 unix
的机器上的话,我们可以使用 Unix域套接字文件 来进行进程间通信
如果在启动客户端程序的时候指定的主机名为 localhost
,或者指定了 --protocol=socket
的启动参数,那服务器程序和客户端程序之间就可以通过 Unix域套接字文件 来进行通信了
TCP/IP
协议进行连接// 如果表已经建好了,可以使用这个语句来修改表的存储引擎
ALTER TABLE 表名 ENGINE = 存储引擎名称;
utf8字符集表示字符需要使用14个字节,但是我们常用的一些字符使用13个字节就可以表示了。而在MySQL中字符集表示一个字符所用最大字节长度在某些方面会影响系统的存储和性能,所以MySQL定义了以下两个概念:
utf8mb3
:阉割过的 utf8
字符集,只使用1~3个字节表示字符。utf8mb4
:正宗的 utf8
字符集,使用1~4个字节表示字符在MySQL中,utf8
是 utf8mb3
的别名,所以之后在MySQL中提到 utf8
就意味着使用1~3个字节来表示一个字符,但是如果有使用4个字节编码一个字符的情况,比如说存储一些emoji表情之类的,就使用 utf8mb4
。
比较规则的作用通常体现在比较字符串大小的表达式以及对某个字符串列进行排序中,所以有时候也称为排序规则
后缀 | 英文释义 | 描述 |
---|---|---|
_ai |
accent insensitive |
不区分重音 |
_as |
accent sensitive |
区分重音 |
_ci |
case insensitive |
不区分大小写 |
_cs |
case sensitive |
区分大小写 |
_bin |
binary |
以二进制方式比较 |
比如 utf8_general_ci
是以 _ci
结尾,说明不区分大小写
所以说如果以后在对字符串作比较或者对某个字符串做排序操作时没有得到想象中的结果,需要考虑一下是不是比较规则的问题
InnoDB
将表中的数据存储到磁盘上,但是处理数据的过程是发生在内存中的,InnoDB
不会一条一条这样的从磁盘中读取数据,这样太慢了。
InnoDB
采取的方式是将数据划分为若干个页,一般大小为 16KB
,以页作为磁盘和内存之间交互的基本单位。也就是说在一般情况下,InnoDB
一次最少从磁盘读取16KB到内存中,一次最少把内存中的16KB内容刷新到磁盘中。
当一条记录中某个列中存储的数据占用字节数非常多时,该列就可能成为溢出列,伴随着就会出现溢出页的情况,也就是一条记录存储在多个页的情况
行格式:Compact
delete_mask
标记当前记录是否被删除,0:没有被删除,1:已被删除
这些被删除的记录之所以不立即从磁盘上移除,是因为移除它们之后把其他的记录在磁盘上重新排列需要性能消耗,所以只是打上一个删除标记,所有被删除的记录都会组成一个所谓的垃圾链表,这个链表中的记录占用的空间称之为所谓的可重用空间,当以后有新记录插入到表中时,可能会覆盖这些可重用空间。
next_record
它表示从当前记录的真实数据到下一条记录的真实数据的地址偏移量,也就相当于是指向下一条记录的地址
注意:下一条记录指的并不是我们按插入顺序的下一条记录,而是按照主键值由小到大的顺序的下一条记录
这其实就是链表,可以通过一条记录找到它的下一条记录
而且当数据页中存在多条被删除掉的记录时,这些记录的
next_record
属性会把这些被删除掉的记录组成一个垃圾链表,以备之后重用这部分存储空间。
如何根据主键值查找某条记录
因为一个页中的记录是按照主键值由小到大串联成的单链表,我们想要查找根据主键值查找某条记录,显而易见可以直接遍历单链表来进行查询,但这种方法效率太低,比较笨。所以说 InnoDB
采用了类似于书的的目录那用的解决方案
设计师将所有正常的记录按从小到大划分为几个组(包括InnoDB自己自动插入的两个伪记录,最小记录和最大记录),规定最小记录所在的组只能有它自己本身,最大记录所在的分组只能在18条记录之间,剩下的分组中记录的条数范围是48条之间,每个组中的主键值最大的那条记录的 n_owned
值代表所在组现在有几条记录。
n_owned
值加一。直到记录数到达8个因为这些槽也是从小到大排序的,所以可以直接用二分查找来查找到记录所在的槽,并找到该分组中主键值最小的那条记录,然后再进行遍历就可以了。
因为是链表,槽对应的偏移量记录的是组中主键值最大的元素(组中最后一个元素),所以我们可以通过前一个槽对应的记录来查询到当前分组的主键值最小的记录,然后再进行遍历,且因为一个组中包含的记录数只能是1~8条,所以遍历一个组中所有记录的代价也很小。
在页中对数据进行增删改操作时,我们必须通过一些记录的移动操作来保证下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值,这个过程称为页分裂。
B+树本身就是一个目录,或者说本身就是一个索引。它有两个特点:
使用记录主键值的大小进行记录和页的排序,包含三方面:
B+树的叶子节点存储的是完整的用户记录
所谓完整的用户记录,就是指这个记录中存储了所有列的值(包括隐藏列)
我们把具有这两种特性的B+树称为 聚簇索引 ,所有完整的用户记录都存放在这个聚簇索引的叶子节点处,聚簇索引并不需要我们显式的使用 INDEX
语句来创建,InnoDB
会自动为我们创建。另外,在 InnoDB
存储引擎中,聚簇索引 就是数据的存储方式(所有的用户记录都存储在叶子节点),也就是所谓的 索引即数据,数据即索引 。
也称为 辅助索引
InnoDB
会将查询列与主键列再建一棵B+树,以查询列的大小为排序规则,叶子节点存储的并不是完整的用户记录,而是 查询列 + 主键值(因为如果每建立一颗B+树就全部拷贝一次全部完整的用户记录太浪费存储空间了)
当使用查询列的某条记录时,会根据查询列一层一层找到叶子节点,然后在叶子节点中找到对应的主键值。再根据主键值到聚簇索引中再查找一遍完整的用户记录,这个过程称为 回表
因为这种按照 非主键索引 建立的B+树需要一次 回表 操作才可以定位到完整的用户记录,所以这种 B+树 也被称为 二级索引,或者辅助索引
同时以多个列的大小作为排序规则,也就是同时为多个列建立索引,比方说我们想让B+树按照 c2
和 c3
列的大小进行排序,这个包含两层含义:
c2
进行排序c2
列相同的情况下,采用 c3
进行排序注意
- 每条目录项记录都由
c2
、c3
、 页号这三个部分组成,各条记录先按照c2
列的值进行排序,如果记录的c2
列相同,则按照c3
列的值进行排序。- B+树叶子节点处的用户记录由
c2
、c3
、和主键c1
列组成- 联合索引本质上也是一个二级索引
搜索条件中的列和索引的列一致
搜索条件的先后顺序不会对查询结果造成影响,因为
MySQL
有 查询优化器,会分析这些搜索条件并且按照可以使用的索引索引中列的顺序来决定先使用哪个搜索条件
在搜索语句中可以不用包含全部联合索引中的列,只包含左边的或者多个左边的列就行
字符串的前 n
个字符,也就是前缀都是排好序的,所以对于字符串类型的索引来说,我们只匹配它的前缀也是可以快速定位记录的,比如说 'As%'
但是只给出后缀
'%As'
或中间某个字符'%As%'
不行,因为字符串中间或结尾有'As'
的字符串并没有排好序,所以只能全表扫描了
因为B+树中所有记录值都是按照索引列的值从小到大的顺序排好序的,所以可以极大的方便我们查找索引列的值在某个范围内的记录
需要注意在使用联合进行范围查找的时候需要注意,如果对多个列同时进行范围查找的话,只有对索引最左边的那个列进行范围查找的时候才能用到B+树索引,因为只有在最左边列值相同的情况下才会使用下一个列的值进行排序,而在联合范围查找中通过左边列进行范围查找的记录可能并不是按照下一个列进行排序的
对于同一个联合索引来说,虽然多个列都进行范围查找时只能用到最左边那个索引列,但是如果左边的列是精确查找,则右边的列可以进行范围查找
总体上与使用索引查找值的情况相似,也是根据最左匹配原则,因为索引本身就是排序好的,如果可以满足条件直接从索引中提取数据,然后进行回表操作取出该索引中不包含的列就好了
和使用B+树索引进行排序是一个道理
因为 回表 操作可能会带来较高的性能损耗,所以建议:最好在查询列表中只包含索引列
因为如果在查询列表中只包含索引列,那么在通过索引得到结果后就不必再到 聚簇索引 中查找记录的剩余项(因为我们没有查询它),这样就省去了 回表 操作带来的性能损耗。我们把这种只需要用到索引的查询方式称为 索引覆盖,排序操作也优先使用 覆盖索引 的方式进行查询。
也就是说,只为出现在 WHERE
子句中的列、连接子句中的连接列,或者出现在 ORDER BY
或 GROUP BY
子句中的列创建索引。而出现在查询列表中的列就没必要建立索引了
2、5、8、2、5、8、2、5、8
,虽然有 9 条记录,但该列的基数却是 3,也就是说,在记录行数一定的情况下,列的基数越大,该列的值越分散,列的基数越小,列的值越集中。这个列的基数指标很重要,直接关系到我们是否能有效利用索引
我们最好为那些列的基数大的列建立索引,为基数太小的列建立索引效果可能不好
因为重复值多的话,那么使用这个二级索引还可能要进行回表操作,这样性能损耗就更大了
另外也可以从InnoDB设计的思路来想,主键也是不可重复的
以整数类型为例,有 TINYINT
、MEDIUMINT
、INT
、BIGINT
这么几种
尽量让索引列使用较小的类型
I/O
带来的性能损耗,也就意味着可以把更多的数据页缓存在内存中,从而加快读写效率这个建议对于表的主键来说更见适用,因为不仅是聚簇索引中会存储主键值,其他所有二级索引的节点处都会存储一份记录的主键值。如果主键值适用更小的数据类型,也就意味着节省更多的存储空间和更高效的 I/O
。
假如说我们需要建立索引的字段是字符串类型的,并且它很长的话,那么我们存储一个字符串就需要很大的存储空间,这样,当我们在适用索引进行字符串比较的时候也会占用更多的时间
所以我们鼓励使用字符串前缀来建立索引,比如说这样
# name(10)就表示在建立的B+树索引中只保留记录的前10个字符的编码
KEY idx_name_birthday_phone_number (name(10), birthday, phone_number)
但是需要注意,如果按照这样建立索引,再按照
name
进行排序语句是无法使用索引排序的
比如说下面两个语句:
WHERE my_col * 2 < 4
WHERE my_col < 4/2
第一个语句中存储引擎会依次遍历所有的元素,计算 my_col
的值是不是小于 4,所以这种情况下使用不到索引
第二个语句 my_col
是以单独列的形式出现,可以直接使用索引
所以,如果索引列在比较表达式中不是以单独列的形式出现,而是以某个表达式,或者函数调用形式出现的话,是用不到索引的
当一个数据页满了,这时候再插入一条主键值位于数据页主键值中间大小的值(也就是新插入的这条记录应该存放在这个数据页中),这时候就会造成 页分裂,也就意味着 性能损耗
所以建议将主键值设为自增,在插入记录时存储引擎会自动为我们填入自增的主键值
避免冗余的索引
InnoDB 是以页为单位管理存储空间的,我们的聚簇索引(也就是完整的表数据)和其他二级索引都是以 B+树 的形式保存到表空间的,而B+树的节点就是数据页
连接的本质就是把各个连接表中的记录都取出来依次匹配的组合加入结果集并返回给用户
笛卡尔积:连接查询的结果集中包含一个表中的每一条记录与另一个表中的每一条记录相互匹配的组合,像这样的结果集就可以称之为 笛卡尔积
对于 内连接 的两个表,驱动表中的记录在被驱动表中找不到匹配的记录,该记录不会加入到最后的结果集
SELECT * FROM t1 [INNER | CROSS] JOIN t2 [ON 连接条件] [WHERE 普通过滤条件];
// 内连接有多种写法,下面几种内连接的写法都是等价的
SELECT * FROM t1 JOIN t2;
SELECT * FROM t1 INNER JOIN t2;
SELECT * FROM t1 CROSS JOIN t2;
SELECT * FROM t1,t
对于 外连接 的两个表,驱动表中的记录即使在被驱动表中没有匹配的记录,也仍然需要加入到结果集中
在 MySQL 中,根据选取驱动表的不同,外连接仍然可以细分为两种
SELECT * FROM t1 LEFT JOIN t2 ON 连接条件 [WHERE普通过滤条件];
SELECT * FROM t1 RIGHT JOIN t2 ON 连接条件 [WHERE普通过滤条件];
WHERE 子句中的过滤条件
不管是内连接还是外连接,凡是不符合 WHERE 子句的过滤条件的记录都不会被加入最后的结果集
ON 子句中的过滤条件
对于外连接的驱动表的记录来说,如果无法在被驱动表中找到匹配 ON 子句的过滤条件的记录,那么该记录仍然会被加入到结果集中,对应的被驱动表记录的各个字段使用 NULL
值填充
需要注意的是,ON 子句是专门为外连接驱动表中的记录在被驱动表找不到匹配记录时应不应该把该记录加入结果集这个场景下提出的,所以如果把 ON 子句放到内连接中,MySQL 会把它和 WHERE 子句一样对待,也就是说:
内连接中的 WHERE 子句和 ON 子句是等价的
一般把放到 ON 子句中的过滤条件也称之为 连接条件
包括 I/O 成本和 CPU 成本
对于 InnoDB 存储引擎来说,页是磁盘和内存之间交互的基本单位,MySQL 的设计者规定读取一个页面花费的成本默认是 1.0,读取及检测一条记录是否符合搜索条件的成本默认是 0.2,
注意,不管读取记录时需不需要检测是否满足搜索条件,其成本都算是 0.2
MySQL 的 查询优化器 会找出执行该语句所有可能使用的方案,对比之后找出成本最低的方案,这个成本最低的方案就是所谓的 执行计划,之后才会调用存储引擎提供的接口真正的执行查询
对于单表查询 MySQL 的查询过程是这样:
MySQL 中连接查询采用的是嵌套循环连接算法,驱动表被访问一次,被驱动表可能会被访问多次,所以对于两表连接查询来说,它的查询成本由下边两个部分构成:
a = 5 AND b > a
可以被替换为 a = 5 AND b > 5
True
或 False
的表达式,优化器会移除掉它们group by
子句,优化器就把 HAVING
子句和 WHERE
子句合并起来因为 内连接 的驱动表和被驱动表的位置可以相互转换,而 左连接 和 右连接 的驱动表和被驱动表是固定的,这就导致 内连接 可能通过优化表的连接顺序来降低整体的查询成本,而外连接不行
但是在外连接中有一些特殊情况,就是 WHERE
子句中包含对被驱动表中的列不为 null
的条件,称之为 空值拒绝。在被驱动表的 WHERE
子句符合空值拒绝的条件后,外连接和内连接就可以相互转换了,然后查询优化器就可以评估不同连接顺序的成本,选出成本最低的连接顺序来执行查询。
MySQL 的子查询可以在一个外层查询的各种位置出现:
SELECT
子句中FROM
子句中WHERE
或 ON
子句中ORDER BY
子句中(语法支持,但没啥意义)GROUP BY
子句中(也是语法支持,但没啥意义)子查询语法注意事项
SELECT
子句中的子查询必须是标量子查询LIMIT 1
语句来限制记录数量[NOT] IN/ANY/SOME/ALL
子查询来说,子查询中不允许有 LIMIT
语句如果 IN
子查询的结果集太多,意味着 IN
子句中的参数特别多,可能会导致无法有效的使用索引,只能对外层查询进行全表扫描,并且因为 IN
子句中参数太多,检测一条记录是否符合 IN
子句中的参数匹配花费的时间太长
MySQL的设计者针对这个问题,使用的优化办法是直接将子查询的结果集写入到一个临时表里,该临时表的列就是子查询结果集中的列,写入的临时表记录会被去重(因为 IN
语句是判断某个操作数在不在某个集合中,去重不会影响结果并且会使我们的临时表变得更小),如果子查询的结果集不大的离谱,就会为它建立基于内存的使用 Memory
存储引擎的临时表,而且会为该表建立哈希索引;如果子查询的结果集非常大,超过的系统变量所设置的临界值,临时表会转而使用基于磁盘的存储引擎来保存结果集中的记录,索引类型也对应转变为 B+
树索引
这个将子查询结果集中的记录保存到临时表的过程称为之 物化,临时表称之为 物化表。
InnoDB 对于 IN 子查询的优化多种策略,如果
IN
子查询符合转换semi-join(半连接)
的条件,查询优化器会优先把该子查询转换为semi-join
,然后再考虑怎样执行半连接的成本最低,如果IN
子查询不符合semi-join
的条件,那么查询优化器会在 “先将子查询物化之后再执行查询” 和 “执行IN to EXISTS
转换” 中找出一种成本更低的方式执行子查询
id
列的值是相同的,出现在前面的表表示 驱动表,出现在后边的表表示 被驱动表。SELECT
关键字都会对应一个唯一的 id
值。需要注意的是,查询优化器可能会对涉及子查询的查询语句进行重写,从而转化为连接查询。所以如果我们想知道查询优化器是否对某个子查询语句进行了重写,直接查看执行计划就好了
因为从磁盘上读取数据页太慢,InnoDB使用内存做为缓存
InnoDB 为了缓存磁盘中页,在 MySQL 服务器启动的时候就向操作系统申请了一片连续的内存,命名为 Buffer Pool(缓冲池)
,为了方便管理缓存页,InnoDB 为每一个缓存页都创建了一个对应的 控制块,包含这个缓存页的控制信息(所属的表空间编号、页号…)
Buffer Pool
中哪些页是空闲的、哪些页是已经被使用了的,InnoDB 将所有空闲的缓存页对应的控制块作为一个个节点放到一个链表中,这个链表就称为 free链表,每当需要从磁盘中加载一个页到 Buffer Pool
中,就从 free链表 中取一个空闲的缓存页,然后更改对应的控制块信息,再把该缓存页对应的节点从 free链表 中移除Buffer Pool
中(哈希算法),表空间号 + 页号 作为 key,缓存页作为 valueBuffer Pool
中某个缓存页的数据,那它就和磁盘上的页不一致了,这样的缓存页被称为 脏页Buffer Pool
中的页不一定被用到Buffer Pool
时,可能会把那些使用频率非常高的页从 Buffer Pool
中淘汰掉针对 预读 的页面可能不进行后续访问情况,InnoDB 规定,当磁盘上的某个页初次加载到 Buffer Pool
时,该缓存页对应的控制块会被放到 old 区域,这样针对 预读 到 Buffer Pool
却不进行后续访问的页面就会被逐渐从 old 区域 逐出,而不会影响 young 区域 中使用比较频繁的缓存页
对于 全表扫描 时,短时间内大量使用频率非常低的页面情况的优化,InnoDB 启用了一个 间隔时间 的系统变量(默认是 1 秒),对于某个被加载到 LRU链表 的某个页来说,如果第一次和最后一次访问该页面的时间间隔小于 1s,那么该页就不会被加入到 young 区域,也会被逐渐从 old 区域 逐出。
1/4
的后边,才会被移动到 LRU链表 的头部。Buffer Pool
本质上是 InnoDB 向操作系统申请的一块连续的内存空间,在多线程环境下,访问 Buffer Pool
中的各种链表都需要加锁处理之类的,在 Buffer Pool
特别大而且并发访问特别高的情况下,单一的 Buffer Pool
可能会影响请求处理的处理速度Buffer Pool
特别大的时候,我们可以把它们拆分成若干个小的 Buffer Pool
,每个 Buffer Pool
都称为一个 实例,每个 Buffer Pool
实例中都有各自独立的链表,互不干扰。目前只有 InnoDB 和 NDB 存储引擎支持
需要注意,如果某个事务中包含了修改使用 不支持事务的存储引擎的表,那么对该使用不支持事务的存储引擎的表所做的修改将 无法进行回滚
ROLLBACK 语句是我们程序员手动的去回滚事务时才去使用的,如果事务在执行过程中遇到了某些错误而无法继续执行的话,事务自身会自动的回滚
默认情况下,如果不显式使用 START TRANSACTION
或者 BEGIN
语句开启一个事务,那么每一条语句都算是一个独立的事务。这种特性称之为事务的 自动提交
如果想要关闭 自动提交 的功能,可以使用下面两种方法:
START TRANSACTION
或者 BEGIN
语句开启一个事务,这样在本次事务提交或者回滚前会暂时关闭掉自动提交的功能autocommit
的值设置为 off
,这样写入的多条语句就属于是同一个事务,直到我们显式的 COMMIT
或者 ROLLBACK
在我们显式的开启了一个事务或者将系统变量 autocommit
设置为 off
,时,事务就不会 自动提交,但是当我们输入了某些语句之后,事务就会偷偷的提交掉,就像我们输入了 COMMIT
语句一样,这种因为某些特殊语句而导致事务提交的情况称为 隐式提交,这些情况包括:
MySQL
系统数据库中的表,ALTER USER
、CREATE USER
之类的autocommit
为 on
,也会隐式提交前边语句所属的事务LOAD DATA
批量往数据库中导入数据时之类的InnoDB 存储引擎是以页为单位来管理存储空间的,我们进行的增删改查操作都是在访问页面,而在访问页面之前,需要把将要访问的页缓存到内存中的 Buffer Pool
中才可以进行访问,在一个事务提交之后,为了保证事务的 持久性 我们需要将该事务所修改的页面刷新到磁盘中,但是将该事务所修改的所有页面都刷新到磁盘中面临着两个问题:
所以 redo log 应运而生
InnoDB 是以组的形式写入 redo log
InnoDB 的设计者认为向某个索引对应的 B+ 树插入一条记录的这个过程必须是原子的,不能说插入一半之后就停止了。针对某个组的 redo log ,要么把全部的日志都恢复掉,要么一条也不恢复
MySQL 将对底层页面的一次原子访问的过程称之为一个 Mini-Transaction
,简称 mtr
一个事务可以包含若干条语句,每一条语句由若干个 mtr
组成,每一个 mtr
又可以包含若干条 redo log。
InnoDB的设计者为了解决磁盘过慢的问题引入了 Buffer Pool
,同理,写入日志也不能直接写到磁盘上,实际上,服务器启动时就向操作系统申请了一大片称之为 redo log buffer
的连续内存空间
向 redo log buffer
中写入 redo log 的过程是顺序的
我们说事务需要保证其 原子性 ,事务中的操作要么全部完成,要么什么也不做,但是有时候我们会遇到一些特殊的情况,比如说事务执行过程中突然断电,或者我们手动输入 ROLLBACK
等,都会导致事务执行到一半就结束,但是事务执行过程中可能已经修改了很多东西,为了保证事务的原子性,我们需要将这些修改过的东西恢复成原来的样子,这个过程就称之为 回滚,为了回滚所做的这些记录就称之为 undo log 。
一个事务可以是一个只读事务,或者是一个读写事务
幻读 强调的是一个事务按照某个相同的条件多次读取记录时,后读取时读到了之前没有读到的记录
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED |
Possible | Possible | Possible |
READ COMMITTED |
Not Possible | Possible | Possible |
REPEATABLE READ |
Not Possible | Not Possible | Possible |
SERIALIZABLE |
Not Possible | Not Possible | Not Possible |
注意:MySQL InnoDB 的
REPEATABLE READ
可以应用加锁读来保证避免幻读,这个加锁读使用到的机制就是Next-Key Locks
一个事务每次对记录进行改动,都会将旧值放到一条 undo log 中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被 roll_pointer
属性连接成一个链表,我们把这个链表称之为 版本链,版本链的头节点就是当前记录的最新值
对于使用 READ UNCOMMITTED
隔离级别的事务来说,由于可以读到未提交事务修改过的记录,所以直接读取记录的最新版本就好了
对于使用 SERIALIZABLE
隔离级别的事务来说,InnoDB 规定使用加锁的方式来访问记录
而对于 READ COMMITTED
和 REPEATABLE READ
隔离级别的事务来说,假如另一个事务修改了记录但是尚未提交,需要判断一下版本链中哪个版本是当前事务可见的。为此,InnoDB 提出了一个 ReadView 的概念.
ReadView 中包含一些关于 事务id 的属性,因为事务id是不断递增的,所以在一个事务访问某条记录时,所以在判断某条记录的某个版本对于当前事务是否可见时,应该根据以下步骤:
如果某个版本的数据对当前事务不可见,那就顺着版本链找到下一个版本的数据,再继续进行判断,依次类推,直到版本链中的最后一个版本,如果仍不可见,那么就意味着该条记录对该事务完全不可见,查询结果中就不包含该记录。
READ COMMITTED
隔离级别的事务中, 在每次读取数据前都会生成一个 ReadViewREPEATABLE READ
隔离级别的事务中, 只在第一次读取数据时生成一个 ReadView事务采用 MVCC
进行的读取操作称之为 一致性读,或者 一致性无锁读 ,有的地方也称之为 快照读,所有普通的 SELECT 语句在 READ COMMITTED
、REPEATABLE READ
隔离级别下都算是一致性读。
一致性读并不会对表中的任何记录做加锁操作,其他事务可以自由的对表中的记录做改动
Record Locks
与 Gap Locks
的结合体Insert Intention Locks