MYSQL 5分钟掌握InnoDB事务(三)

119 阅读5分钟

事务就是一组原子性的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;

问题

脏写

sessionAsessionB
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会一脸懵逼,我明明修改了数据,也提交了数据,为什么数据没有变化呢。

脏读

sessionAsessionB
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还没有提交的数据,就被称为“脏读”。

不可重复读

sessionAsessionB
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前后读到的数据不一致,就被称为“不可重复读”。

幻读
sessionAsessionB
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机制来实现隔离级别控制