存储引擎的选择
在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组合。
-
InnoDB:是Mysql的默认存储引擎,支持事务、外键。如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多更新、删除操作,那么InnoDB存储引擎是比较合适的选择。
-
MyISAM:如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。
-
MEMORY:将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。MEMORY的缺陷就是对表的大小有限制,太大的表无法缓存存在内存中,而且无法保障数据的安全性。
InnoDB:支持行级锁定,可以处理高并发的读写操作,减少锁冲突。支持更好的多版本并发控制(MVCC)。
MyISAM:支持表级锁定,对于高并发的写操作可能会导致锁冲突,影响性能。
InnoDB:具有崩溃恢复能力,通过 redo log 和 undo log 支持数据的持久性和事务恢复。
MyISAM:不具备完整的崩溃恢复机制,可能会在崩溃时丢失数据。
Mysql锁机制
锁的分类:
从对数据操作的类型(读\写)分:
- 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响。
- 写锁(排它锁):当前写操作没有完成前,它会阻断其他写锁和读锁。
从对数据操作的粒度分:
- 表锁
- 行锁
表锁(偏向读操作)
特点:偏向MyISAM
存储引擎,开销小,加锁快;无死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
手动增加表锁-读锁:
lock table 表名字 read;
查看表上加过的锁:
show open tables;
In_use
为1时表示已上锁
释放锁:
unlock tables;
通过LOCK TABLES
语句为表加上读锁(READ锁)。当表被加上读锁后,其他会话无法对其进行写操作,但仍可以读取该表。
-- 会话A: 加读锁
LOCK TABLES blogs READ;
-- 会话A: 执行读操作
SELECT * FROM blogs ;
-- +----+---------+
-- | id | value |
-- +----+---------+
-- | 1 | Alice |
-- | 2 | Bob |
-- | 3 | Charlie |
-- +----+---------+
-- 会话B: 尝试写操作(会被阻塞)
INSERT INTO blogs (id, value) VALUES (4, 'David');
-- 会话A: 释放读锁
UNLOCK TABLES;
-- 会话B: 插入操作被执行
-- (此时插入操作成功,表中将包含新插入的数据)
并且在会话A释放锁之前,会话A也不能查询其他没有锁定的表,也不能插入和修改当前锁定的表。
手动增加表锁-写锁
为mylock
表加write
锁(MylSAM
存储引擎的写阻塞读例子)
-- 会话A加写锁
LOCK TABLES mylock WRITE;
-- 会话A执行写操作(允许)
INSERT INTO mylock (id, value) VALUES (4, 'David');
-- 会话A执行读操作(读操作也允许,因为写锁同时拥有读权限)
SELECT * FROM mylock;
-- +----+---------+
-- | id | value |
-- +----+---------+
-- | 1 | Alice |
-- | 2 | Bob |
-- | 3 | Charlie |
-- | 4 | David |
-- +----+---------+
-- 会话B: 尝试读操作(会被阻塞)
SELECT * FROM mylock;
-- 会话B: 尝试写操作(会被阻塞)
INSERT INTO mylock (id, value) VALUES (5, 'Eve');
如何分析表锁定
可以通过检查table_locks_waited
和table_locks_immediate
状态变量来分析系统上的表锁定
这里有两个状态变量记录MySQL内部表级锁定的情况,两个变量说明如下:
Table_locks_immediate
:产生表级锁定的次数,表示可以立即获取锁的查询次数,每立即获取锁值加1 ;Table_locks_waited
(重点):出现表级锁定争用而发生等待的次数(不能立即获取锁的次数,每等待一次锁值加1),此值高则说明存在着较严重的表级锁争用情况;
此外,MyISAM的读写锁调度是写优先,这也是MyISAM
不适合做写为主表的引擎。因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞
行锁(偏写操作)
偏向InnoDB
存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
InnoDB
与MyISAM
的最大不同有两点:一是支持事务(TRANSACTION
);二是采用了行级锁
由于行锁支持事务,复习老知识:
- 事务(Transaction)及其ACID属性
- 并发事务处理带来的问题
- 事务隔离级别
数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。
行锁案例讲解
CREATE TABLE test_innodb_lock (a INT(11),b VARCHAR(16))ENGINE=INNODB;
INSERT INTO test_innodb_lock VALUES(1,'b2');
INSERT INTO test_innodb_lock VALUES(3,'3');
INSERT INTO test_innodb_lock VALUES(4, '4000');
INSERT INTO test_innodb_lock VALUES(5,'5000');
INSERT INTO test_innodb_lock VALUES(6, '6000');
INSERT INTO test_innodb_lock VALUES(7,'7000');
INSERT INTO test_innodb_lock VALUES(8, '8000');
INSERT INTO test_innodb_lock VALUES(9,'9000');
INSERT INTO test_innodb_lock VALUES(1,'b1');
CREATE INDEX test_innodb_a_ind ON test_innodb_lock(a);
CREATE INDEX test_innodb_lock_b_ind ON test_innodb_lock(b);
行锁定基本演示(两个会话同事更新同一行记录)
-- 会话A: 开始事务并更新数据
START TRANSACTION;
UPDATE test_innodb_lock SET b = 'updated' WHERE a = 1;
-- 会话B: 开始事务并尝试更新同一行数据(会被阻塞)
START TRANSACTION;
UPDATE test_innodb_lock SET a = 'blocked' WHERE a = 1;
-- 会话A: 提交事务
COMMIT;
-- 会话B: 更新操作被执行
-- 会话B: 提交事务
COMMIT;
-- 查看结果
SELECT * FROM test_innodb_lock;
- 行锁定:在InnoDB存储引擎中,行锁是在索引记录上实现的,因此对于没有索引的表,InnoDB将进行表锁。
- 事务管理:确保在使用事务时适当提交(COMMIT)或回滚(ROLLBACK),以避免长时间持有锁,从而影响其他事务的执行。
- 死锁检测:InnoDB有内置的死锁检测机制,当检测到死锁时,会自动回滚其中一个事务以解除死锁。
索引失效行锁变表锁
会话A: 开始事务并更新数据
START TRANSACTION;
UPDATE test_innodb_lock SET a= 1 WHERE b = 4000;
会话B: 开始事务并尝试更新不同行数据(会被阻塞)
START TRANSACTION;
UPDATE test_innodb_lock SET b = 'blocked' WHERE a = 5;
会话A: 提交事务
COMMIT;
会话B: 更新操作被执行
会话B: 提交事务
COMMIT;
查看结果
SELECT * FROM test_innodb_lock;
b
是字符串类型,虽 MySQL
会自动将 4000
数型转换字符串型但此举会让索引失效,行锁升级为表锁
什么是间隙锁
间隙锁(Gap Lock
)是 InnoDB
在可重复读(REPEATABLE READ
)隔离级别下实现多版本并发控制(MVCC
)的一种锁机制。它会锁定一个范围内的所有记录,但不包括范围两端的记录。这种锁可以防止幻读现象的发生。
间隙锁阻塞的例子
为了演示间隙锁的阻塞情况,我们可以通过两个事务来进行操作。以下是一个详细的例子:
- 创建一个测试表并插入一些初始数据:
CREATE TABLE test_gap_lock (
id INT PRIMARY KEY,
value VARCHAR(50)
) ENGINE=InnoDB;
INSERT INTO test_gap_lock (id, value) VALUES (1, 'A');
INSERT INTO test_gap_lock (id, value) VALUES (3, 'B');
INSERT INTO test_gap_lock (id, value) VALUES (5, 'C');
- 启动第一个事务并执行一个范围查询:
-- Session 1
START TRANSACTION;
SELECT * FROM test_gap_lock WHERE id > 1 AND id < 5 FOR UPDATE;
在这个查询中,InnoDB
会在 id = 1
和 id = 5
之间的间隙上加上一个间隙锁,锁定的范围为 (1, 5)
。这意味着在这个范围内的任何插入操作都会被阻塞。
- 启动第二个事务并尝试在间隙中插入一条记录:
-- Session 2
START TRANSACTION;
INSERT INTO test_gap_lock (id, value) VALUES (4, 'D');
此时,第二个事务会被阻塞,因为它试图在第一个事务的间隙锁范围内插入一条记录。
- 提交或回滚第一个事务:
-- Session 1
COMMIT; -- 或者 ROLLBACK;
- 第二个事务现在可以继续:
-- Session 2
-- 如果第一个事务提交或回滚后
-- 此时插入操作会继续并成功执行
INSERT INTO test_gap_lock (id, value) VALUES (4, 'D');
COMMIT;
Innodb
存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一些,但是在整体并发处理能力方面要远远优于MyISAM的表级锁定的。当系统并发量较高的时候,Innodb
的整体性能和MylISAM相比就会有比较明显的优势了。
如何分析行锁定?
通过检查lnnoDB_row_lock
状态变量来分析系统上的行锁的争夺情况:
show status like 'innodb_row_lock%';
mysql> show status like 'innodb_row_lock%';
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0 |
| Innodb_row_lock_time | 0 |
| Innodb_row_lock_time_avg | 0 |
| Innodb_row_lock_time_max | 0 |
| Innodb_row_lock_waits | 0 |
+-------------------------------+-------+
5 rows in set (0.00 sec)
对各个状态量的说明如下:
Innodb_row_lock_current_waits
:当前正在等待锁定的数量;Innodb_row_lock_time
:从系统启动到现在锁定总时间长度;Innodb_row_lock_time_avg
:每次等待所花平均时间;Innodb_row_lock_time_max
:从系统启动到现在等待最常的一次所花的时间;Innodb_row_lock_waits
:系统启动后到现在总共等待的次数;
尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析(Show Profile)系统中为什么会有如此多的等待,然后根据分析结果着手指定优化计划。
优化建议
尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁。
合理设计索引,尽量缩小锁的范围
尽可能较少检索条件,避免间隙锁
尽量控制事务大小,减少锁定资源量和时间长度
尽可能低级别事务隔离
事务
当涉及到数据库操作时,事务(Transaction)是一组SQL语句的执行,这些语句要么全部成功执行,要么全部失败回滚。MySQL中的事务提供了一种机制,用于确保数据库的一致性、完整性和持久性。以下是一些关于MySQL中事务的重要概念和特点:
1. ACID特性:
-
原子性(Atomicity): 事务中的所有操作要么全部提交成功,要么全部回滚失败,不会出现部分执行的情况。
-
一致性(Consistency): 事务执行前后,数据库从一个一致状态转移到另一个一致状态,保证数据的完整性约束不会被破坏。
-
隔离性(Isolation): 多个事务并发执行时,每个事务的操作应该被隔离开来,相互之间不会产生影响,防止数据并发问题,比如脏读、不可重复读、幻读等。
-
持久性(Durability): 一旦事务提交成功,其所做的修改将永久保存在数据库中,即使系统崩溃,数据也不会丢失。
2. 事务的使用:
-
BEGIN、COMMIT和ROLLBACK: 事务开始于BEGIN语句,结束于COMMIT或ROLLBACK语句。COMMIT用于提交事务,ROLLBACK用于回滚事务。
-
AUTOCOMMIT模式: MySQL默认处于AUTOCOMMIT模式,即每个SQL语句都被当作一个单独的事务执行。可以使用
SET AUTOCOMMIT = 0
来关闭AUTOCOMMIT,这样一系列的SQL语句可以作为一个事务执行。 -
事务控制语句: MySQL提供了诸如
START TRANSACTION
、SAVEPOINT
等语句用于控制事务的开始、保存点的设置等。
3. 并发控制:
-
锁机制: MySQL通过锁来实现并发控制,可以通过设置不同的事务隔离级别(如读未提交、读已提交、可重复读、串行化)来控制事务的隔离性,从而解决并发带来的问题。
-
MVCC(多版本并发控制): MySQL使用MVCC来提高并发性能,通过保存数据在不同时间点的版本来支持事务的隔离性,避免了读取操作被阻塞。
4. 事务隔离级别:
- MySQL支持不同的事务隔离级别,如:READ UNCOMMITTED(读未提交)、READ COMMITTED(读已提交)、REPEATABLE READ(可重复读)、SERIALIZABLE(串行化)。不同隔离级别提供不同的并发控制和一致性保证。
MySQL中的事务功能提供了强大的数据操作保证,但在设计和实现时需要注意合理设置事务边界、选择合适的隔离级别以及优化事务的并发性能。
ACID是什么? 可以详细说一下吗?
- 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
- 一致性(Consistency): 事务完成时,必须使所有的数据都保持一致状态。
- 隔离性(lsolation): 数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行
- 持久性(Durability): 事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
不可重复读侧重于修改
,幻读侧重于新增
或删除
。解决不可重复读的问题只需锁住满足条件的行
,解决幻读需要锁表
X:解决 ✔:不能解决
面试官:MySQL主从同步原理
候选人:MySQL主从复制的核心就是二进制日志(DDL(数据定义语言)语句和 DML(数据操纵语言)语句),它的步骤是这样的:
.
第一:主库在事务提交时,会把数据变更记录在二进制日志文件 Binlog 中。第二:从库读取主库的二进制日志文件 Binlog ,写入到从库的中继日志 Relay Log 。
第三:从库重做中继日志中的事件,将改变反映它自己的数据
-
缓冲池(buffer pool):主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增删改查操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从磁盘加载并缓存,以一定频率刷新到磁盘,从而减少磁盘10,加快处理速度
-
数据页(page):是lnnoDB 存储引擎磁盘管理的最小单元,每个页的大小默认为 16KB。页中存储的是行数据
redo log
重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性
该日志文件由两部分组成:重做日志缓冲
(redo log bufer)以及--
(redo log file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中,用于在刷新脏页到磁盘,发生错误时,进行数据恢复使用。
具体来说,redo log 有以下主要用途:
事务持久性: 当事务提交时,数据库会将事务所做的修改操作记录到 redo log 中,而不是直接写入到磁盘上的数据页。这样可以确保即使数据库崩溃,事务所做的修改也不会丢失,因为这些修改可以通过重新应用 redo log 来恢复。
故障恢复: 在数据库发生故障或崩溃时,可以使用 redo log 来恢复未完成的事务和修改。通过重新应用 redo log,数据库可以将未持久化的修改重新应用到数据页上,从而将数据库恢复到故障发生前的状态。
提高性能: redo log 的记录方式通常是顺序写入,这比随机写入到数据页要快。因此,通过将修改操作记录到 redo log,可以减少随机写入的操作,提高数据库的写入性能。
InnoDB 引擎会在适当的时候,将这redolog中记录的操作更新到表中数据对应的page页所在的物理磁盘上,而这个更新往往是在MySQL服务比较空闲的时候去刷新到磁盘,此时才是真正的把更新的数据内容刷新到对应的page页中。
而这个更新redolog日志中的内容到真正的表数据对应的page页的刷盘操作通常比费时的,需要从磁盘中找到对应的page页,还涉及到分页或合并的各种操作,属于随机IO写入性能太差,所以MySQL没有在执行更新操作的时候,并没有直接去更新真正的数据页中的内容,而只是更新了缓存和记录的redolog日志。
先写日志,再写磁盘,这就是很多软件在提高写的性能的时候所使用的WAL(write ahead logging)预写日志的功能。
undo log
回滚日志,用于记录数据被修改前的信息,作用包含两个:提供回滚 和 MVCC(多版本并发控制)。undolog和redolog记录物理日志不一样,它是逻辑日志。
- 可以认为当
delete
一条记录时,undo log中会记录一条对应的insert记录,反之亦然 - 当update一条记录时,它记录一条对应相反的update记录。当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。
undo log可以实现事务的一致性和原子性
面试官:undo log和redo log的区别
候选人:好的,其中redo log日志记录的是数据页的物理变化,服务宕机可用来同步数据,而undo log 不同,它主要记录的是逻辑日志,当事务回滚时,通过逆操作恢复原来的数据,比如我们删除一条数据的时候,就会在undo log日志文件中新增一条delete语句,如果发生回滚就执行逆操作;
redo log保证了事务的持久性,undo log保证了事务的原子性和一致性
bin log 和 redo log的区别
其中binlog主要用来做数据备份,数据同步,数据恢复,记录的是逻辑变化
redo log重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性,数据库奔溃时可以用redolog来恢复未完成的数据,保证数据的完整性,Redo Log可以把未提交的事务回滚,把已提交的事务进行持久化
MVCC
MVCC 多版本并发控制是 「维持一个数据的多个版本,使得读写操作没有冲突」 的概念,只是一个抽象概念,并非实现
是为了在读取数据时不加锁来提高读取效率和并发性的一种手段。它的底层实现主要是分为了三个部分,第一个是隐藏字段
,第二个是undo log
日志,第三个是readView
读视图
MVCC 能解决什么问题,好处是?
数据库并发场景有三种,分别为:
- 读-读:不存在任何问题,也不需要并发控制
- 读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
- 写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失
MVCC 带来的好处是?
多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。 所以 MVCC 可以为数据库解决以下问题
在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题
在读已提交和可重复读隔离级别下的快照读,都是基于MVCC实现的!
隐式字段
每行记录除了我们自定义的字段外,还有数据库隐式定义的 DB_TRX_ID
, DB_ROLL_PTR
, DB_ROW_ID
等字段
- DB_TRX_ID
6 byte,最近修改(修改/插入)事务 ID:记录创建这条记录/最后一次修改该记录的事务 ID - DB_ROLL_PTR
7 byte,回滚指针,指向这条记录的上一个版本(存储于 rollback segment 里) - DB_ROW_ID
6 byte,隐含的自增 ID(隐藏主键),如果数据表没有主键,InnoDB 会自动以DB_ROW_ID产生一个聚簇索引
undo日志
undo log 主要分为两种:
- insert undo log
代表事务在 insert 新记录时产生的 undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃 - update undo log
事务在进行 update 或 delete 时产生的 undo log ; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被 purge 线程统一清除
对 MVCC 有帮助的实质是 update undo log ,undo log 实际上就是存在 rollback segment 中旧记录链,它的执行流程如下:
一、 比如一个有个事务插入 persion 表插入了一条新记录,记录如下,name 为 Jerry , age 为 24 岁,隐式主键是 1,事务 ID 和回滚指针,我们假设为 NULL
二、 现在来了一个事务 1 对该记录的 name 做出了修改,改为 Tom
- 在
事务 1
修改该行(记录)数据时,数据库会先对该行加排他锁 - 然后把该行数据拷贝到
undo log
中,作为旧记录,既在undo log
中有当前行的拷贝副本 - 拷贝完毕后,修改该行name为Tom,并且修改隐藏字段的
事务 ID
为当前事务 1 的 ID, 我们默认从 1 开始,之后递增,回滚指针指向拷贝到undo log
的副本记录,既表示我的上一个版本就是它 - 事务提交后,释放锁
三、 又来了个事务 2 修改 person 表的同一个记录,将age修改为 30 岁
- 在
事务2
修改该行数据时,数据库也先为该行加锁 - 然后把该行数据拷贝到
undo log
中,作为旧记录,发现该行记录已经有 undo log 了,那么最新的旧数据作为链表的表头,插在该行记录的undo log
最前面 - 修改该行 age 为 30 岁,并且修改隐藏字段的事务 ID 为当前事务 2 的 ID, 那就是 2 ,回滚指针指向刚刚拷贝到 undo log 的副本记录
- 事务提交,释放锁
从上面,我们就可以看出,不同事务或者相同事务的对同一记录的修改,会导致该记录的undo log成为一条记录版本线性表,既链表,undo log 的链首就是最新的旧记录,链尾就是最早的旧记录
Read View 读视图
什么是 Read View?
当我们用select读取数据时,这一时刻的数据会有很多个版本(例如上图有四个版本),但我们并不知道读取哪个版本,这时就靠readview
来对我们进行读取版本的限制,通过readview
我们才知道自己能够读取哪个版本。
什么是 Read View,说白了 Read View 就是事务进行快照读
操作的时候生产的读视图 (Read View)
,在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的 ID
所以我们知道 Read View
主要是用来做可见性判断的, 即当我们某个事务执行快照读
的时候,对该记录创建一个 Read View
读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log
里面的某个版本的数据。
Read View遵循一个可见性算法,主要是将要被修改的数据的最新记录中的 DB_TRX_ID
(即当前事务 ID )取出来,与系统当前其他活跃事务的 ID 去对比(由 Read View 维护),如果 DB_TRX_ID
跟 Read View
的属性做了某些比较,不符合可见性,那就通过 DB_ROLL_PTR
回滚指针去取出 Undo Log
中的 DB_TRX_ID
再比较,即遍历链表的 DB_TRX_ID
(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的 DB_TRX_ID
, 那么这个 DB_TRX_ID
所在的旧记录就是当前事务能看见的最新老版本
RC , RR 级别下的 InnoDB 快照读有什么不同?
正是 Read View 生成时机的不同,从而造成 RC , RR 级别下快照读的结果的不同
- 在 RR 级别下的某个事务的对某条记录的第一次快照读会创建一个快照及
Read View,
将当前系统活跃的其他事务记录起来,此后在调用快照读的时候,还是使用的是同一个Read View
,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View
,所以对之后的修改不可见; - 即 RR 级别下,快照读生成
Read View
时,Read View 会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。而早于Read View
创建的事务所做的修改均是可见 - 而在 RC 级别下的,事务中,每次快照读都会新生成一个快照和
Read View
, 这就是我们在 RC 级别下的事务中可以看到别的事务提交的更新的原因
总之在 RC 隔离级别下,是每个快照读都会生成并获取最新的 Read View;而在 RR 隔离级别下,则是同一个事务中的第一个快照读才会创建 Read View, 之后的快照读获取的都是同一个 Read View。
总结
隐藏字段是指:在mysql中给每个表都设置了隐藏字段,有一个是trx_id
(事务id),记录每一次操作的事务id,是自增的;另一个字段是roll_pointer
(回滚指针),指向上一个版本的事务版本记录地址
undo log
主要的作用是记录回滚日志,存储老版本数据,在内部会形成一个版本链,在多个事务并行操作某一行记录,记录不同事务修改数据的版本,通过roll_pointer
指针形成一个链表
readView解决的是一个事务查询选择版本的问题,在内部定义了一些匹配规则和当前的一些事务id判断该访问那个版本的数据,不同的隔离级别快照读是不一样的,最终的访问的结果不一样。
- 如果是
rc
隔离级别,每一次执行快照读时生成ReadView
- 如果是
rr
隔离级别仅在事务中第一次执行快照读时生成ReadView
,后续复用