1. 事务的隔离级别:
事务的隔离级别,总共有四种不同的隔离级别,每种解决不同的并发问题:
READ-UNCOMMITTED(读未提交)
事务可以读取其他事务未提交的数据(脏读),会有脏读、不可重复读和幻读的问题
示例:
-- 事务 A
BEGIN;
UPDATE user set age = age -1 where id = 1; -- 未提交
-- 事务B
BEGIN;
SELECT age from user where id = 1 ; -- 读到事务A未提交的age的数据(原始是18,现在读取到17)
COMMIT;
-- 事务A回滚
ROLLBACK;
结果就是事务B读取到了事务A未提交的脏数据,如果事务A回滚,那么事务B读取到的数据就是错的。
READ-COMMITTED(读已提交)
事务只能读取到其他事务已经提交的数据,这解决了脏读,但是还是会不可重复读和幻读
示例:
-- 事务 A
BEGIN;
UPDATE user set age = age -1 where id = 1;
COMMIT; -- 事务已提交,age 值变为 17
-- 事务B
BEGIN;
SELECT age from user where id = 1 ; -- 第一次查询可能读取到18,如果A没提交,也就是不会读取到未提交的数据
-- 事务A提交后读取
SELECT age from user where id = 1 ; -- 第二次查询可能读取到17,A已提交
COMMIT
这就会导致两次读取到的数据不一致,也就是不可重复读, 但是不会读取到未提交的数据了,避免了脏读。
REPEATABLE-READ(可重复读)
事务执行期间多次读取同一数据,结果保持一致,这解决了脏读和可重复读,但是会有幻读问题
示例
-- 事务A
BEGIN;
SELECT * FROM user WHERE age = 18; -- 假设返回2条记录
-- 事务B 插入数据并提交
BEGIN;
INSERT INTO user(age) VALUES (18)
COMMIT;
-- 事务A 再次查询
BEGIN;
SELECT * FROM user WHERE age = 18; -- 返回3条记录
事务A在第一次查询的时候有2条记录,等事务B提交以后事务A再查询有3条记录,也就是发生了幻读。
解决幻读的方法有多种
将事务隔离级别调整为SERIALIZABLE
在可重复读的事务级别下,给事务操作的这张表加锁。
在可重复读的事务级别下,给我事务操作的这张表加
Next-key Lock
(Record Lock
行锁 +Gap Lock
间隙锁),也就是不只是锁住每条记录,也锁住每条记录的间隙,让记录之间无法新增记录。
‼️: next-key lock
的确是解决了幻读问题,但是next-key lock
在并发情况下也经常会造成死锁。死锁检测和处理也会花费时间,一定程度上影响到并发量。
SERIALIZABLE(可串行化):最高的隔离级别,所有事务依次逐个执行,这样事务之间就完全不会产生干扰。
2. 多版本并发控制(Multi-Version Concurrency Control)
MVCC 是一种用于在多个并发事务同时读写数据库时,保持数据一致性和隔离性的技术。他是通过在每一行上维护多个版本的数据来实现的,当一个事务要对数据进行修改时,MVCC 会为改事务创建一个快照,而不是直接修改实际的数据行。
读操作(SELECT)
当一个事务进行读操作的时候,他会使用快照读取,他会选择一个不晚于事务创建时间的最新版本的数据作为快照数据,后续读取的都是这个快照数据,其他事务对数据行的修改不会影响当前事务的读操作。
写操作(UPDATE,DELETE,INSERT)
对于写操作,事务会为要修改的数据创建一个新版本,将修改后的数据写入新版本,新版本数据会携带当前事务的版本号,以便其他事务能够正确读取相应版本的数据。原始版本的数据仍然存在。
事务的提交
当一个事务提交时,他所作的修改就会成为数据库最新版本的数据,并且其他事务可见
当一个事务回滚时,所做的修改将会被撤销,其他事务不可见。
版本回收
为了防止数据库中的版本无限增长,MVCC会定期进行版本回收。回收机制会删除已经不再需要的旧版本数据,从而释放空间。
一致性非锁定读和锁定读
非锁定读:添加一个版本号或者时间戳,在更新数据的时候同时版本号+1或者更新时间戳。查询的时候,将当前可见的版本号和对应记录的版本号进行比较,如果记录的版本号小,表示该记录可见
InnoDB
存储引擎中MVCC
就是对一致性非锁定读的实现,读取一个正在执行更新操作的行时,不会等待行上的锁释放,而是去读取行的一个快照数据。锁定读:执行
select ... lock in share mode
select ... for update
、insert
updatedelete
操作时就是锁定读。在锁定读下,读取的数据是最新版本,所以也被称为当前读(current read)。锁定读会对读取到的记录加锁,select ... lock in share mode
的话加S锁,其他事务也可以加S
锁select ... for update
、insert
update
、delete
是对记录加X
锁,且其他事务不能加任何锁。InnoDB 在实现Repeatable Read
时,如果执行的是当前读,则会对读取的记录使用Next-key Lock
,来防止其它事务在间隙间插入数据。
评论区