事务就是一组原子性的SQL查询,或者说一个独立的工作单元。事务内的语句,要么全部执行成功,要么全部执行失败。
ACID
原子性(atomicity)
一个事务必须被视为一个不可分割的最小工作单元,要么全部成功,要么全部失败。
一致性(consistency)
数据库总是从一个一致性的状态转换到另一个一致性的状态。如果事务最终没有提交,事务中所做的修改也不会保存到数据库中。
隔离性(isolation)
通常来说,一个事物所做的修改在最终提交以前,对其他事务是不可见的。隔离性取决于 隔离级别 的设定。
持久性(durability)
事务一旦结束,数据就持久到数据库,即使提交后,数据库发生崩溃,也不会丢失提交的数据。
隔离级别
数据库是一个客户端/服务器架构的软件,每个客户端与服务器连接后,就会产生一个session(会话),客户端和服务器的交互就是在session中进行的,理论上来说,如果服务器同时只能处理一个事务,其他的事务都排队等待,当该事务提交后,服务器才处理下一个事务,这样才真正具有“隔离性”,什么问题都没有了,但是如果是这样,性能就太差了,在性能和隔离性之间,只能做一些平衡,所以数据库提供了好几个隔离级别供我们选择。
在讲隔离级别之前,我们先来看看事务并发执行会遇到什么问题。
为了保证下面的叙述可以顺利进行,我们要先建一张表:
CREATE TABLE `student` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL COMMENT '姓名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`grade` int(11) DEFAULT NULL COMMENT '年级',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
问题
脏写
| sessionA | sessionB |
|---|---|
| begin | |
| begin | |
| update student set name ='landscape' where id = 2; | |
| update student set name = 'golandscape' where id = 2; | |
| rollback | |
| commit |
sessionA和sessionB开启了一个事务;
sessionB把id=2的name修改成了“landscape”;
sessionA把id=2的name修改成了“golandscape”;
sessionB回滚了事务;
sessionA提交了事务。
如果sessionB在回滚事务的时候把sessionA的修改也给回滚了,导致sessionA的提交丢失了,这种现象就被称为“脏写”。sessionA会一脸懵逼,我明明修改了数据,也提交了数据,为什么数据没有变化呢。
脏读
| sessionA | sessionB |
|---|---|
| begin | |
| begin | |
| update student set name = 'landscape' where id = 2; | |
| select * from student where id = 2;(如果此时读到的name是 “landscape” ,则说明发生了脏读) | |
| commit | |
| rollback |
sessionA和sessionB开启了一个事务;
sessionB把id=2的name修改成了“landscape”,此时还未提交;
sessionA查询了id=2的数据,如果读出来的数据的name是“landscape”,也就是读到了sessionB还没有提交的数据,就被称为“脏读”。
不可重复读
| sessionA | sessionB |
|---|---|
| begin | |
| begin | |
| select * from student where id = 2;(假如此时查询出来的name是 “landscape”) | |
| update student set name= “golandscape” where id = 2; | |
| commit | |
| select * from studen where id = 2;(如果此时查询出来的name是 “golandscape”,同一个事务中,查询同一行数据,前后读到的数据不一致,就被称为 “不可重复度”) | |
| commit |
sessionA和sessionB开启了一个事务;
sessionA查询id=2的数据,假如name是“landscape”,
sessionB把id=2的name修改成了“golandscape”,随后提交了事务;
sessionA再一次查询了id=2的数据,如果name是“golandscape”,说明在同一个事务中,sessionA前后读到的数据不一致,就被称为“不可重复读”。
幻读
| sessionA | sessionB |
|---|---|
| begin | |
| begin | |
| select * from student where name = 'landscape'(假设此时读到了一条记录) | |
| INSERT INTO test.student(name,age,grade) VALUES('landscape',18,3) | |
| commit | |
| select * from student where name = 'landscape'(如果此时读到了两条记录,就被称为 “幻读”) | |
| commit |
sessionA和sessionB开启了一个事务;
sessionA查询name=“landscape”的数据,假设此时读到了一条记录;
sessionB又插入一条name=“landscape”的数据,随后提交;
seesionA再一次查询name=“landscape”的数据,如果此时读到了两条记录,第二次查询读到了第一次查询未查询出来的数据,就被称为“幻读”。
我们知道了在并发执行事务的时候,会遇到什么问题,有些问题比较严重,有些问题比较轻微,一般来说,我们认为按照严重性排序是这样的:脏写>脏读>不可重复读>幻读
在SQL标准定义中,设定了四种隔离级别,来解决上述的问题:
- 未提交读(READ UNCOMMITTED): 最低的隔离级别,会有“脏读”、“不可重复读”,“幻读”三个问题。
- 读已提交(READ COMMITTED): SQLServer默认隔离级别,可以避免“脏读”,会有“不可重复读”,“幻读”两个问题。
- 可重复读(REPEATABLE READ): 可以避免“脏读”,“不可重复读”两个问题,会有“幻读”问题。 MySQL默认隔离级别,但是在MySQL中,此隔离级大部分场景下是可以避免“幻读”问题。
- 串行化(SERIALIZABLE): 所有的问题都不会发生。
脏写的问题实在太严重了,在任何隔离级别下,都不会有脏写的问题!
在MYSQL中,通过MVCC(多版本并发控制)的ReadView机制来实现隔离级别控制