事务定义
一个事务会涉及大量的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,串行化)以及会产生的问题(脏读、不可重复读、幻读)是面试的高频问点,什么东西都需要积累,然后反复的温习,这样才能记忆和理解的更深刻。