一、事务为何物
事务(Transaction)是保障程序中一组操作的原子性的约束,它使事务中的所有操作都指向同一个结果,也就是要么所有的操作都执行成功,要么所有的操作都执行失败,不允许出现其他结果。例如银行转账,从A账户扣除金额,向B账户添加金额,这两个数据库操作的总和构成一个完整的逻辑过程,不可拆分,这个过程被称为一个事务。在MySQL中,目前只有InnoDB引擎支持事务。
二、事务的特性
数据库管理系统在写入或更新数据的过程中,为保证事务是正确可靠的,需要具备四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。
- 原子性(Atomicity):一个事物中的所有操作,要么全部完成,要么全部失败,不会在中间某个环节结束。若事务在执行过程中发生异常,所有的操作都会被回滚到事务开始前的状态,就像这个事务从没执行过一样。
- 一致性(Consistency):事务操作的数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定,也就是在事务开始之前和结束之后,数据库的完整性没有被破坏。
- 隔离性(Isolation):数据库允许多个事务并发执行,隔离性是为了防止多个事务并发执行导致数据的不一致,事务之间是相互隔离的。事务隔离有四种级别:未提交读(Read UnCommitted)、已提交读(Read Commited)、可重复读(Repeatable Read)、串行化(Serializable)
- 持久性:事务成功提交之后,对数据的修改是永久性的,即便系统故障也不会丢失。
三、为什么要有四种隔离级别
SQL标准定义了4种隔离级别用来限定不同的事务场景,按照隔离级别从低到高为:读未提交、读已提交、可重复读、串行化,级别越高,所支持的并发度越低。
不同的隔离级别会造成不同的影响,体现在数据上就是脏读、不可重复读和幻读。
- 脏读:A事务读取了B事务中未提交的数据,在A事务提交之前,B事务进行了回滚,此时A事务中的数据就不正确了,所以被定义为脏数据。
- 不可重复读:A事务在第一次读取之后到第二次读取之前,B事务对该数据进行了修改,导致A事务两次读取的数据不一致,这就是不可重复读
- 幻读:幻读一般发生在范围查询的情况下,A事务第一次读取一批数据,在第二次读取之前,B事务向数据库中插入了新的符合A事务查询条件的数据,此时A事务第二次读取出来的数据条数不一致,这种情况对于A事务来说就是出现了幻读。
事务隔离级别
- 读未提交(Read UnCommitted):该隔离级别下的事务可以看到其他事务未提交的执行结果,会引起脏读、不可重复读和幻读,在实际应用中几乎不会使用该级别的事务。
- 读已提交(Read Committed):这是大多数数据库系统的默认隔离级别(如Oracle、阿里云的MySQL)等,但不是官方MySQL默认的。它不允许事务看到未提交的事务中的数据,使事务只能看见已经提交的事务所做的改变。该隔离级别会引起不可重复读和幻读。
- 可重复读(Repeatable Read):这是官方MySQL的默认事务隔离级别,它确保同一事务多次读取的数据的一致性,解决了不可重复读的问题。该隔离级别解决的主要是对数据库进行UPDATE操作造成的数据改变,但还是会引起幻读的情况发生,在InnoDB存储引擎下默认提供MVCC(多版本并发控制)机制解决了幻读的问题。
- 串行化(Serializable):这是最高的隔离级别,串行的意思也就是每次只允许一个事务对数据进行操作,事务按照先来后到的规则进行排队一次执行,这样在事务之间就不会相互冲突,从而解决了幻读的问题。但是串行化执行事务的方式会严重影响事务的执行效率,高并发操作下会造成事务堆积和超时,一般在实际应用中很少使用,虽然它很安全。
通过上面我们了解了事务隔离级别,也知道每种隔离级别所解决的事情,做一下汇总:
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交 | Y | Y | Y |
读已提交 | N | Y | Y |
可重复读 | N | N | Y |
串行化 | N | N | N |
四、如何查看和设置数据库的隔离级别
-
查看数据库当前的事务隔离级别
1
select @@tx_isolation
-
修改数据库的事务隔离级别
修改语句格式:
1
set [global | session] transaction isolation level [read uncommitted | read committed | repeatable read | serializable]
session
:当前session内的事务global
:应用于之后新创建的session,已经存在的session不受影响1
set session transaction isolation level read committed
修改成功之后,我们再看一下当前的隔离级别已经被修改为RC了。
五、小🌰
-
创建一张表备用
1
2
3
4
5
6
7
8
9create table cc_isolation_test
(
id int auto_increment primary key,
name varchar(30) null
) engine=innodb default charset=utf8
comment '事务隔离级别测试表';
# 插入一条数据
insert into cc_isolation_test(name) values('cc'); -
RU级别
-
修改session的事务隔离级别为RU
打开两个session窗口,将事务隔离级别均修改为RU。
1
2
3
4# 修改隔离级别为RU
set session transaction isolation level read uncommitted ;
# 验证
select @@tx_isolation; -
脏读验证
-
在两个窗口中均开启一个事务,在A事务中进行查询操作,在B事务中进行更新操作但不提交
-
A事务:进行查询操作
1
2start transaction;
select * from cc_isolation_test;这时查到的数据为正常数据:
-
B事务:进行更新操作但不提交
1
2start transaction;
update cc_isolation_test set name='cc1' where id=1;执行完之后可以看一下我们的表中,数据是未被修改的,因为B事务尚未提交
-
A事务:再次进行查询操作
1
select * from cc_isolation_test;
查询出来的数据中,name竟然变成了cc1,也就是说A事务中读取到了B事务中尚未提交的数据,如果此时B事务回滚,A事务中name的值仍然是读到的cc1,也就出现了脏数据,所以RU级别下会出现脏读的问题。
-
图解
事务A 事务B start transaction; start transaction; select * from cc_isolation_test; update cc_isolation_test set name=‘cc1’ where id=1; select * from cc_isolation_test; rollback; commit;
-
-
不可重复读验证
上面演示脏读的过程中,在A事务中对数据进行了两次读取,且两次读取到的name的值不一致,所以RU也造成了不可重复读的问题。
-
幻读验证
-
A事务:进行查询操作
1
2start transaction;
select * from cc_isolation_test; -
B事务:进行插入操作但不提交
1
2start transaction;
insert into cc_isolation_test(name) values('cc1');事务未提交,我们的表中还没出现插入的新数据
-
A事务:再次进行查询操作
1
select * from cc_isolation_test;
查询出来两条数据,和之前查询的条数不一样,但是我们数据库中仅仅只有一条数据,这就是所谓的幻读,此时若将B事务回滚掉,A事务拿着B事务未提交的数据继续操作,定会出现问题。
-
图解
事务A 事务B start transaction; start transaction; select * from cc_isolation_test; insert into cc_isolation_test(name) values(‘cc1’); select * from cc_isolation_test; rollback; commit;
-
-
-
RC级别
-
修改session的事务隔离级别为RC
1
2
3
4# 修改隔离级别为RC
set session transaction isolation level read committed ;
# 验证
select @@tx_isolation;
-
脏读验证
操作步骤和RU的一致,但是结果却不相同,我们在B事务中对
id=1
的数据进行修改但是不提交事务,在A事务中是读取不到B事务对该条数据的修改,所以RC级别不会出现脏读的问题。 -
不可重复读验证
-
A事务:第一次查询
1
2start transaction;
select * from cc_isolation_test where id=1; -
B事务:修改数据并提交
1
2
3start transaction ;
update cc_isolation_test set name='cc1' where id=1;
commit ; -
A事务:第二次查询
1
2start transaction;
select * from cc_isolation_test where id=1;在A事务中读取到了B事务提交的数据,与第一次读取到的数据不一致,也就是说每次读取都是从数据库中读取最新的数据,这也证明了再RC级别下会出现不可重复读的问题。
-
图解
事务A 事务B start transaction; start transaction; select * from cc_isolation_test where id=1; update cc_isolation_test set name=‘cc1’ where id=1; commit; select * from cc_isolation_test where id=1; commit;
-
-
幻读验证
-
A事务:第一次查询
1
2start transaction;
select * from cc_isolation_test; -
B事务:修改数据并提交
1
2
3start transaction ;
insert into cc_isolation_test(name) values('cc2');
commit ; -
A事务:第二次查询
1
select * from cc_isolation_test;
两次查询的数据条数不同,在A事务中读取到了B事务新插入的数据,相对于第一次查询结果来说,出现了幻读的问题。
-
图解
事务A 事务B start transaction; start transaction; select * from cc_isolation_test; insert into cc_isolation_test(name) values(‘cc2’); commit; select * from cc_isolation_test; commit;
-
-
-
RR级别
-
修改session的事务隔离级别为RR
1
2
3
4# 修改隔离级别为RC
set session transaction isolation level read committed ;
# 验证
select @@tx_isolation; -
脏读验证
操作步骤和RC的一致,但是结果却不相同,我们在B事务中对
id=1
的数据进行修改但是不提交事务,在A事务中是读取不到B事务对该条数据的修改,所以RR级别不会出现脏读的问题。 -
不可重复读验证
-
A事务:第一次查询
1
2start transaction;
select * from cc_isolation_test where id=1; -
B事务:修改数据并提交
1
2
3start transaction ;
update cc_isolation_test set name='cc2' where id=1;
commit ; -
A事务:第二次查询
1
select * from cc_isolation_test where id=1;
通过两次读取之后发现在B事务提交前后读取到的数据是一致的,这样证明了RR级别是支持重复读的,nice~
-
图解
事务A 事务B start transaction; start transaction; select * from cc_isolation_test where id=1; update cc_isolation_test set name=‘cc2’ where id=1; commit; select * from cc_isolation_test where id=1; commit;
-
-
幻读验证
-
A事务:进行查询
1
2start transaction;
select * from cc_isolation_test where id=1; -
B事务:插入数据并提交
1
2
3start transaction ;
insert into cc_isolation_test(id, name) values(1, 'cc2');
commit ; -
A事务:插入数据
1
insert into cc_isolation_test(id, name) values(1, 'cc2');
主键冲突了,但是在A事务中确实没查询到id=1的数据,其实这个时候数据库中已经有了id=1的数据,但在A事务中却没有查询到,这就是幻读。
-
图解
事务A 事务B start transaction; start transaction; select * from cc_isolation_test where id=1; insert into cc_isolation_test(id, name) values(1, ‘cc2’); commit; insert into cc_isolation_test(id, name) values(1, ‘cc2’); commit;
-
-
-
Serializable级别
-
修改session的事务隔离级别为Serializable
1
set session transaction isolation level serializable;
-
操作
先开启事务A,进行查询,但不提交;再开启事务B,然后进行插入操作,会发现操作被阻塞了,如下图中insert语句最后的时间就是等待的时间,事务B必须在事务A提交或回滚之后才能继续执行,这也就是串行化的意义:同时只能有一个事务处于执行中,其他线程都要等待。并发度最低但安全性最高。
图解:
事务A 事务B start transaction; start transaction; select * from cc_isolation_test; insert into cc_isolation_test(name) values(‘cc2’); commit; select * from cc_isolation_test; commit;
-