【数据库】MySQL事务详解
事务的隔离级别
- 读未提交(read-uncommitted):最低级的隔离级别,允许其他事务读到未提交的值;
- 读已提交(read-committed):事务只能读取到其他事务提交的数据;
- 可重复读(repeatable-read):对同一条数据多次读取结果都是一样(mysql默认隔离级别);
- 串行化(serializable):最高的隔离级别,所有事务穿行执行,事务间不会产生干扰
隔离级别 | 存在的问题 |
---|---|
读未提交 | 脏读、幻读、不可重复读 |
读已提交 | 幻读、不可重复读 |
可重复读 | 幻读 |
串行化 | 无 |
注:MySQL的Innodb 存储引擎默认隔离级别是 可重复读,并且不存在幻读的情况。
不可重复读和幻读的区别
- 不可重复读:重点在于
update
和delete
。 - 幻读:重点在于
insert
。
读已提交隔离级别
-
该隔离级别下,读数据,不进行加锁,增删改的时候是添加行锁的
以下table表为例 (id是主键,再无其他索引)id name age 1 A 12 2 B 15 3 C 16
MySQL默认隔离级别可重复读,修改隔离级别
SET session transaction isolation level read committed;
更新数据
事务A | 事务B |
---|---|
begin; | begin; |
update table set name='A1' where id=1; | update table set name='A2' where id=1; |
commit; |
为了防止并发过程冲修改冲突,mysql在事务A中给 id=1
的数据加了行锁,并且一直不commit,事务B因为执行更新语句,拿不到锁就会一直阻塞,直到超时。
因为id是有索引,所以在执行事务时mysql会给相应的数据行加行锁,如果调用update table set age=10 where name='A';
的语句,会怎么样呢?
MySQL会给表中所有的数据行添加行锁,再执行完过滤条件后将不满足条件的行锁释放;这样做的原因是name字段并没有索引,MySQL并不知道哪些数据行是name='A'的。
读取数据
事务A | 事务B |
---|---|
begin; | begin; |
1.select * from table where id=1; | 2. update table set name='A2' where id=1; |
commit | |
3. select * from table where id=1; | |
commit; |
- 事务A 第一次读取到数据
1,A,12
- 事务B 将
id=1
的数据进行修改 - 事务A 第二次读取到数据
1,A1,12
在读已提交隔离级别下,事务A读取到了事务B提交的数据,导致了不可重复读。
MySQL的读已提交,可重复读都是基于MVCC(多版本并发控制)来搞的,为什么可重复读在查询事务没有结束前不会读到别人提交的数据,读已提交会呢?
- 可重复读:实现原理是在执行第一条SELECT语句时,创建一个快照(read view),事务未提交前都是依据者个快照来进行的
- 读已提交:事务开始后,每执行一次SELECT都会创建一个新的快照。
可重复读
事务A | 事务B | 事务C |
---|---|---|
begin; | begin; | begin; |
1 select * from table where id in (1,3) | ||
2 update table set name=’A2’ where id=1 | ||
commit; | ||
insert into table values(4,’D’,20); | ||
commit; | ||
3 select * from table where id in (1,3) | ||
commit; |
- 事务A 第一次读取到数据
1,A,12
,3,C,16
- 事务B 将
id=1
的数据进行修改 - 事务A 第二次读取到数据
1,A,12
,3,C,16
和第一次读取结果是一样的,证明是可重复读的,没有读到事务B的更新和事务C的新增
在可重复读隔离级别下,事务A第二次读取与第一次读取完全相同,所以是可重复读的。
可重复读和读已提交实现原理
这两个隔离级别的都是依赖MVCC来实现的,各位有兴趣的话可以看下这篇