MySQL - 事务(ACID)的隔离级别 - InnoDB锁机制

本文深入解析MySQL的事务特性,包括ACID属性、四种隔离级别(未提交读、已提交读、可重复读、可串行化)的运作机制及应用场景。并通过实操测试,直观展示各隔离级别下可能出现的脏读、不可重复读和幻读问题。同时,文章详述了InnoDB存储引擎下的MVCC(多版本并发控制)机制,以及其在增删查改操作中的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

事务的 ACID

Mysql 的四种隔离级别

测试下 隔离 级别

测试未提交读

测试已提交读

测试可重复读

测试可串行化

InnoDB锁机制

什么是MVCC?

特点

基本原理

基本特征

InnoDB存储引擎MVCC的实现策略

MVCC下InnoDB的增删查改是怎么work的

关于Mysql中MVCC的总结


什么是事务?

专业解释: 自己百度,很详细...  事务 -- 百度百科

个人解释:去银行转1W给老婆,只会有2种结果,① 老婆收到了1W,自己的账上少了1W,②老婆没收到,自己的钱也没少 要么成功,要么失败。

 

事务的 ACID

  • Atomicity:原子性,最小不可拆分 全部成功或全部失败
  • Consistency:一致性,从一个一致状态到另一个一致状态
  • Isolation:隔离性,多个事务之间互不影响
  • Durability:持久性,事务完成后数据提交到数据库中持久保存

 

Mysql 的四种隔离级别

Read Uncommitted(未提交读)

在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。

Read Committed(已提交读)

这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。

Repeatable Read(可重读)

这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。

MVCC机制:通过为每个数据行增加两个隐含值得方式来实现。这两个隐含值记录了行的创建时间,以及过期时间。每一行存储时间发生时的系统版本号。每一次开始一个新事务时版本号会自动加1,每个事务都会保存开始时的版本号,每个查询根据事务的版本号来查询结果

MVCC只在 Read Committed 和 Repeatable Read 两个隔离级别下工作

Serializable(可串行化)

这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

这四种隔离级别采取不同的锁类型来实现,若读取的是同一个数据的话,就容易发生问题。例如:

  • 脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个回滚操作,则后一个事务所读取的数据就会是不正确的。

  • 不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。

  • 幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就有几列数据是未查询出来的,如果此时插入和另外一个事务插入的数据,就会报错。

在MySQL中,实现了这四种隔离级别,分别有可能产生问题如下所示:

隔离级别脏读不可重复读幻读
Read Uncommitted(未提交读)
Read Committed(已提交读)×
Repeatable Read(可重读)->{Mysql的默认}××
Serializable(可串行化)×××

 

一般在 Innodb引擎下 选用Repeatable Read(可重读)级别 ,通过MVCC 来控制并发操作

 

测试下 隔离 级别

DB环境: Mysql 5.7.24

CREATE table test (
	id INT ( 11 ) NOT NULL PRIMARY KEY auto_increment, 
	`name`	VARCHAR ( 20 ) NOT NULL DEFAULT '' ) 
	ENGINE = INNODB DEFAULT CHARSET = utf8

数据:

查看隔离级别

show variables like 'tx_isolation';

设置隔离级别 (未提交读-已提交读-可重复读-可串行化)

set session transaction isolation level read uncommitted;
set session transaction isolation level read committed;
set session transaction isolation level repeatable read;
set session transaction isolation level serializable;

测试未提交读

因为Mysql 默认的隔离级别为 可重复读 所以 设置为 未提交读

事务A事务B
mysql> show variables like'tx_isolation';
+---------------+------------------+
| Variable_name | Value            |
+---------------+------------------+
| tx_isolation  | READ-UNCOMMITTED |
+---------------+------------------+
1 row in set, 1 warning (0.00 sec)


mysql> select * from test;
+----+--------+
| id | name   |
+----+--------+
|  1 | tom    |
|  2 | jerry  |
|  3 | speike |
+----+--------+
3 rows in set (0.00 sec)

# 开启事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)




mysql> select * from test;
+----+-------+
| id | name  |
+----+-------+
|  1 | tom   |
|  2 | jerry |
|  3 | dog   |
+----+-------+
3 rows in set (0.00 sec)



mysql> select * from test;
+----+--------+
| id | name   |
+----+--------+
|  1 | tom    |
|  2 | jerry  |
|  3 | speike |
+----+--------+
3 rows in set (0.00 sec)
mysql> show variables like'tx_isolation';
+---------------+------------------+
| Variable_name | Value            |
+---------------+------------------+
| tx_isolation  | READ-UNCOMMITTED |
+---------------+------------------+
1 row in set, 1 warning (0.00 sec)


mysql> select * from test;
+----+--------+
| id | name   |
+----+--------+
|  1 | tom    |
|  2 | jerry  |
|  3 | speike |
+----+--------+
3 rows in set (0.00 sec)

# 开启事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update test set name='dog' where id=3;
Query OK, 1 row affected (0.00 sec)










#事务回滚
mysql> rollback;
Query OK, 0 rows affected (0.01 sec)

经过上面的实验可以得出结论,事务B更新了一条记录,但是没有提交,此时事务A可以查询出未提交记录。造成脏读现象。未提交读是最低的隔离级别。

测试已提交读

事务A事务B
mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like'tx_isolation';
+---------------+----------------+
| Variable_name | Value          |
+---------------+----------------+
| tx_isolation  | READ-COMMITTED |
+---------------+----------------+
1 row in set, 1 warning (0.00 sec)

mysql> select * from test;
+----+--------+
| id | name   |
+----+--------+
|  1 | tom    |
|  2 | jerry  |
|  3 | speike |
+----+--------+
3 rows in set (0.00 sec)

# 开启事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)




mysql> select * from test;
+----+--------+
| id | name   |
+----+--------+
|  1 | tom    |
|  2 | jerry  |
|  3 | speike |
+----+--------+
3 rows in set (0.00 sec)



mysql> select * from test;
+----+-------+
| id | name  |
+----+-------+
|  1 | tom   |
|  2 | jerry |
|  3 | dog   |
+----+-------+
3 rows in set (0.00 sec)
mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like'tx_isolation';
+---------------+----------------+
| Variable_name | Value          |
+---------------+----------------+
| tx_isolation  | READ-COMMITTED |
+---------------+----------------+
1 row in set, 1 warning (0.00 sec)

mysql> select * from test;
+----+--------+
| id | name   |
+----+--------+
|  1 | tom    |
|  2 | jerry  |
|  3 | speike |
+----+--------+
3 rows in set (0.00 sec)

# 开启事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update test set name='dog' where id=3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0









#提交事务
mysql> commit;
Query OK, 0 rows affected (0.01 sec)

经过上面的实验可以得出结论,已提交读隔离级别解决了脏读的问题,但是出现了不可重复读的问题,即事务A在两次查询的数据不一致,因为在两次查询之间事务B更新了一条数据。已提交读只允许读取已提交的记录,但不要求可重复读。

测试可重复读

事务A事务B

mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like'tx_isolation';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
1 row in set, 1 warning (0.00 sec)

mysql> select * from test;
+----+--------+
| id | name   |
+----+--------+
|  1 | tom    |
|  2 | jerry  |
|  3 | speike |
+----+--------+
3 rows in set (0.00 sec)

# 开启事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)














mysql> select * from test;
+----+--------+
| id | name   |
+----+--------+
|  1 | tom    |
|  2 | jerry  |
|  3 | speike |
+----+--------+
3 rows in set (0.00 sec)

 

 

 


mysql> select * from test;
+----+--------+
| id | name   |
+----+--------+
|  1 | tom    |
|  2 | jerry  |
|  3 | speike |
+----+--------+
3 rows in set (0.00 sec)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

mysql> select * from test;
+----+--------+
| id | name   |
+----+--------+
|  1 | tom    |
|  2 | jerry  |
|  3 | speike |
+----+--------+
3 rows in set (0.00 sec)

 

#提交事务

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

 

mysql> select * from test;
+----+-------+
| id | name  |
+----+-------+
|  1 | tom   |
|  2 | jerry |
|  3 | dog   |
|  4 | cat   |
+----+-------+
4 rows in set (0.00 sec)

mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like'tx_isolation';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
1 row in set, 1 warning (0.00 sec)

mysql> select * from test;
+----+--------+
| id | name   |
+----+--------+
|  1 | tom    |
|  2 | jerry  |
|  3 | speike |
+----+--------+
3 rows in set (0.00 sec)

# 开启事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update test set B2name='dog' where id=3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from test;
+----+-------+
| id | name  |
+----+-------+
|  1 | tom   |
|  2 | jerry |
|  3 | dog   |
+----+-------+
3 rows in set (0.00 sec)









 

#提交事务
mysql> commit;
Query OK, 0 rows affected (0.01 sec)

 

 

 

 

 

 

 

 

 


mysql> insert into test values(4,'cat');
Query OK, 1 row affected (0.01 sec)

mysql> select * from test;
+----+-------+
| id | name  |
+----+-------+
|  1 | tom   |
|  2 | jerry |
|  3 | dog   |
|  4 | cat   |
+----+-------+
4 rows in set (0.00 sec)

由以上的实验可以得出结论,可重复读隔离级别只允许读取已提交记录,而且在一个事务两次读取一个记录期间,其他事务部的更新该记录。但该事务不要求与其他事务可串行化。例如,当一个事务可以找到由一个已提交事务更新的记录,但是可能产生幻读问题(注意是可能,因为数据库对隔离级别的实现有所差别)。像以上的实验,就没有出现数据幻读的问题。

测试可串行化

事务A事务B

mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like'tx_isolation';
+---------------+--------------+
| Variable_name | Value        |
+---------------+--------------+
| tx_isolation  | SERIALIZABLE |
+---------------+--------------+
1 row in set, 1 warning (0.00 sec)

mysql> select * from test;
+----+-------+
| id | name  |
+----+-------+
|  1 | tom   |
|  2 | jerry |
|  3 | dog   |
|  4 | cat   |
+----+-------+
4 rows in set (0.00 sec)

#开启事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)




 


#一直处于等待状态
mysql> select * from test;



#事务B 提交后 才会显示
+----+--------+
| id | name   |
+----+--------+
|  1 | tom    |
|  2 | jerry  |
|  3 | dog    |
|  4 | flycat |
+----+--------+
4 rows in set (0.00 sec)

#提交事务
mysql> commit;
Query OK, 0 rows affected (0.01 sec)

mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like'tx_isolation';
+---------------+--------------+
| Variable_name | Value        |
+---------------+--------------+
| tx_isolation  | SERIALIZABLE |
+---------------+--------------+
1 row in set, 1 warning (0.00 sec)

mysql> select * from test;
+----+-------+
| id | name  |
+----+-------+
|  1 | tom   |
|  2 | jerry |
|  3 | dog   |
|  4 | cat   |
+----+-------+
4 rows in set (0.00 sec)

#开启事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update test set name='flycat' where id=4;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0


 


#提交事务
mysql> commit;
Query OK, 0 rows affected (0.01 sec)

serializable完全锁定字段,若一个事务来查询同一份数据就必须等待,直到前一个事务完成并解除锁定为止。是完整的隔离级别,会锁定对应的数据表格,因而会有效率的问题。

 

InnoDB锁机制

什么是MVCC?

英文全称为Multi-Version Concurrency Control,翻译为中文即 多版本并发控制。在小编看来,他无非就是乐观锁的一种实现方式。在Java编程中,如果把乐观锁看成一个接口,MVCC便是这个接口的一个实现类而已。

Mysql中MVCC的使用及原理详解

 

特点

1.MVCC其实广泛应用于数据库技术,像Oracle,PostgreSQL等也引入了该技术,即适用范围广

2.MVCC并没有简单的使用数据库的行锁,而是使用了行级锁,row_level_lock,而非InnoDB中的innodb_row_lock.

基本原理

MVCC的实现,通过保存数据在某个时间点的快照来实现的。这意味着一个事务无论运行多长时间,在同一个事务里能够看到数据一致的视图。根据事务开始的时间不同,同时也意味着在同一个时刻不同事务看到的相同表里的数据可能是不同的。

基本特征

  • 每行数据都存在一个版本,每次数据更新时都更新该版本。
  • 修改时Copy出当前版本随意修改,各个事务之间无干扰。
  • 保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback)

InnoDB存储引擎MVCC的实现策略

在每一行数据中额外保存两个隐藏的列:当前行创建时的版本号和删除时的版本号(可能为空,其实还有一列称为回滚指针,用于事务回滚,不在本文范畴)。这里的版本号并不是实际的时间值,而是系统版本号。每开始新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询每行记录的版本号进行比较。

每个事务又有自己的版本号,这样事务内执行CRUD操作时,就通过版本号的比较来达到数据版本控制的目的。

MVCC下InnoDB的增删查改是怎么work的

1.插入数据(insert):记录的版本号即当前事务的版本号

执行一条数据语句:insert into testmvcc values(1,"test");

假设事务id为1,那么插入后的数据行如下:

Mysql中MVCC的使用及原理详解

 

2、在更新操作的时候,采用的是先标记旧的那行记录为已删除,并且删除版本号是事务版本号,然后插入一行新的记录的方式。

比如,针对上面那行记录,事务Id为2 要把name字段更新

update table set name= 'new_value' where id=1;

Mysql中MVCC的使用及原理详解

 

3、删除操作的时候,就把事务版本号作为删除版本号。比如

delete from table where id=1;

Mysql中MVCC的使用及原理详解

 

4、查询操作:

从上面的描述可以看到,在查询时要符合以下两个条件的记录才能被事务查询出来:

1) 删除版本号未指定或者大于当前事务版本号,即查询事务开启后确保读取的行未被删除。(即上述事务id为2的事务查询时,依然能读取到事务id为3所删除的数据行)

2) 创建版本号 小于或者等于 当前事务版本号 ,就是说记录创建是在当前事务中(等于的情况)或者在当前事务启动之前的其他事物进行的insert。

(即事务id为2的事务只能读取到create version<=2的已提交的事务的数据集)

补充:

1.MVCC手段只适用于Msyql隔离级别中的读已提交(Read committed)和可重复读(Repeatable Read).

2.Read uncimmitted由于存在脏读,即能读到未提交事务的数据行,所以不适用MVCC.

原因是MVCC的创建版本和删除版本只要在事务提交后才会产生。

3.串行化由于是会对所涉及到的表加锁,并非行锁,自然也就不存在行的版本控制问题。

4.通过以上总结,可知,MVCC主要作用于事务性的,有行锁控制的数据库模型。

关于Mysql中MVCC的总结

客观上,我们认为他就是乐观锁的一整实现方式,就是每行都有版本号,保存时根据版本号决定是否成功。

但由于Mysql的写操作会加排他锁(前文有讲),如果锁定了还算不算是MVCC?

了解乐观锁的小伙伴们,都知道其主要依靠版本控制,即消除锁定,二者相互矛盾,so从某种意义上来说,Mysql的MVCC并非真正的MVCC,他只是借用MVCC的名号实现了读的非阻塞而已。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值