Mysql事务

3,076 阅读9分钟

事务定义

一个事务会涉及大量的cpu操作和IO操作,这些操作会被打包成一个执行单元,要么同时完成,要么同时都不完成。

事务是一组原子性的sql命令或者说是一个独立的工作单元,如果其中任何一条sql语句因为崩溃或者其他原因执行失败,那么该组所有的sql语句都不会执行。如果没有显示启动事务,数据库会根据autocommit的值,默认每条sql操作都会自动提交。

事务的特性ACID

原子性(A)

一个事务中的所有操作,要么都完成,要么都不执行,对于一个事务来说,不可能只执行其中的一部分。

一致性(C)

数据库总是从一个一致性的状态转换到另一个一致性的状态

隔离性(I)

一个事务所做的修改在最终提交之前,对其他事务是不可见的。多个事务之间的操作相互不影响。每降低一个事务的隔离级别都能提高数据库的并发,但同时不安全性就增加了。关于事务的隔离级别后续还要详细讨论。这里简单介绍一下:

  • 读未提交:其他事务未提交就可以读。
  • 读已提交:其他事务只有提交了才能读。
  • 可重复读:只管自己启动事务时候的状态,不接受其他事务的影响。
  • 串行化:按照顺序提交事务保证了数据的安全性,但无法实现并发。

持久性(D)

事务一旦提交,就要更新到数据库中,不能回滚。就算服务器宕机,仍然需要在下次启动的时候自动执行事务中的sql命令,体现到数据库中。

数据库事务隔离界别

介绍

隔离界别 脏读 不可重复读 幻读
未提交读(Read uncommited) 可能 可能 可能
已提交读(Read commited) 不可能 可能 可能
可重复读(Repeatable read) 不可能 不可能 可能
串行化(Serializable) 不可能 不可能 不可能
  • 未提交读(Read uncommited):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据。
  • 已提交读(Read commited):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别。
  • 可重复读(Repeatable read):可重复读。在同一事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,改级别消除了不可重复读,但还是存在幻象读,但InnoDB解决了幻读。
  • 串行化:完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。

准备数据

准备一个账户表,表里三个字段,分别是主键id,账户名和金额

create table account(
	id int(11) not null auto_increment,
	customer_name varchar(255) not null,
	money decimal(10,2) not null,
	primary key(id),
	unique uni_name using btree (customer_name)
) ENGINE = 'InnoDB' AUTO_INCREMENT=10 COMMENT = '账户表';

验证RU(未提交读)

1、开启两个会话,设置隔离级别为read uncommited

set session transaction isolation level read uncommitted;

select @@session.tx_isolation;


2、会话1开启事务

3、会话2开启事务并插入一条数据

4、会话1开始查询数据库,查到了会话2事务未提交的数据

5、会话2如果此时回滚了,会话1就查不到了

小结:在RU模式下,一个事务可以读取到另一个未提交事务的数据,导致了脏读。如果另一个事务回滚了,就会造成数据的不一致性。RU是事务隔离级别中最低的。

RC(读提交)

1、将会话1和会话2的隔离级别设置成读提交模式

set session transaction isolation level read committed;

2、会话1和会话2都开启事务,会话1查询当前数据

3、会话2更新数据但未提交,会话1查询当前数据还是没改

4、但是会话2此时事务提交,会话1查询当前数据变了

小结:在RC模式下,我们发现,当另一个事务没有提交数据修改时,当前事务时读不到修改后的数据的,这就避免了读未提交模式的脏读。但有一个问题,在当前事务中,两次select的数据不一样,这就存在了不可重复读的问题。PS:RC隔离级别是Oracle数据库默认的隔离级别。

RR(可重复读)

1、将会话1和会话2的隔离级别都设置成可重复读

set session transaction isolation level repeatable read;

2、会话1开启事务并查询当前数据,会话2开启事务并更新数据

3、此时会话1查询数据还是之前的数据

4、然后会话2提交事务,会话1查询数据还是之前的数据,这表示会话2的更改并没有影响当前的事务,可以重复读取。

5、此时会话1提交当前事务,并再次读取数据,发现其他事务改了数据

小结:在RR模式下,我们解决了不可重复读的问题,即在这种隔离级别下,一个事务中我们能够保证获取一样的数据(即使有其他事务正在改当前的数据行)。但是无法避免幻读,幻读简单的解释就是在数据有新增的时候,也无法保证两次得到的数据不一致但是不同数据库对不同的RR级别有不同的实现,有时候加上间隙锁来避免幻读。

InnoDB解决了幻读

结论

首先说结论:在RR的隔离级别下,InnoDB使用MVCC和next-key locks(间隙锁)解决幻读。MVCC解决的是普通读(快照读)的幻读,间隙锁解决的是当前读情况下的幻读。

幻读是什么

(首先声明:在RR模式下,引擎是InnoDB,无法产生以下效果。)
1、事务1先执行,但未提交

start transaction;
update account set account=1000 where id>10;

结果为:OK row xx表名成功影响多少行 2、事务2后执行,并且提交

start transaction;
insert into account(customer_name, money) values('李云龙',10000);
commit;

3、事务1再select一下,结果集为:

select * from account;

··· 李云龙,10000
这时,事务1蒙了,不是已经将id>10的所有人的金额都更新成1000了吗,怎么多出一个人金额是10000?这是已提交事务2对事务1产生的影响,这个影响就叫做幻读。

幻读和不可重复读的区别

不可重复读:多次读取一条记录,发现该记录中某些列值被修改过。
幻读:只要是说多次读取一个范围内的记录(包括直接查询所有记录结果或者做聚合统计),发现结果不一致(一般指的是记录增多,记录的减少应该也算是幻读)。

怎么解决幻读

当前读
所谓当前读,指的是加锁的select(S锁或者X锁),update或者delete语句。在RR的事务隔离级别下,数据库会使用间隙锁来锁住本条记录以及索引区间。例如:
select * from account where id>10 for update;锁住的就是id=10的记录以及id>10的这个区间范围,避免范围间插入记录,以避免产生幻影行记录。
普通读
因为普通读是不会加锁的读,所以解决幻读的手段是MVCC。MVCC会给每行元祖加一些辅助字段,记录创建版本号和删除版本号。在每一个事务启动的时候,都有一个唯一的递增的版本号。每开启一个新事务,事务的版本号就会递增。MYSQL默认的隔离级别(可重复读Repeatable Read)下,增删改查就变成下面这样:

  • select:读取创建版本小于或等于当前事务版本号,并且删除版本为空或大于当前事务版本号的记录。这样可以保证在读取之前记录是存在的。
  • insert:将当前事务的版本号保存至行的创建版本号。
  • update:新插入一行,并以当前事务版本号作为新行的创建版本号,同时将原纪录行的删除版本号设置为当前事务版本号。
  • delete:将当前事务版本号保存至行的删除版本号。 有点绕,总结下来就是每行多了两个版本号,一个创建版本号和一个删除版本号,都是保存事务的版本号。下面举个例子理解一下: 例如我插入一条记录,事务id=1,那么记录如下:
id name createversion deleteversion
1 马云 1

如果我把name更新成刘强东,事务id=2,那么记录就变成

id name createversion deleteversion
1 马云 1 2
2 刘强东 2

=> 可以看出,原来的元祖的deleteversion为更新的这个事务的id,并且新增一条
如果删除的话,假设事务id=3,删除id=2的记录

id name createversion deleteversion
1 马云 1 2
2 刘强东 2 3

关键点来了,如果我现在读取的话,需要同时满足两个条件:
1、读取创建版本小于或等于当前事务的版本号,这意味着数据在这个事务之前被创建
2、删除版本为空或者大于当前事务版本号的记录。这意味着删除操作在这个事务之后发生
所以当事务1插入一条记录,事物2更新这条记录并删除这条记录之后,事务1再次查询这条记录,只有id=1的记录满足这两个条件,所以结果为:(避免了幻读)

id name createversion deleteversion
1 马云 1 2

串行化

所有事务串行执行,是最高的隔离级别,性能最差

总结

这篇文章就到这了,总的来说数据库事务的四个特性ACID和四种隔离级别(RU,RC,RR,串行化)以及会产生的问题(脏读、不可重复读、幻读)是面试的高频问点,什么东西都需要积累,然后反复的温习,这样才能记忆和理解的更深刻。