作为单个逻辑单元执行的一系列操作,要么完全执行要么全部都不执行,咋们接下来讲讲数据库事务的四大特性。
其实跟程序事务特性没啥区别,即所谓的ACID
这里有两个部分
根据我们前面所学的知识MySQL会利用锁机制,创建出来不同的事物隔离级别,咋们将按照事物隔离级别从低到高的顺序讲解
首先我们先来看看事物并发所可能产生的问题
由于现在主流数据库都会为我们自动加上来这种更新丢失的情况,所以我们在数据库层面上不好模拟了。咋们大致yy一下它出问题的过程。
上面就是一个更新丢失问题的场景。更新丢失问题在数据库层面不好模拟的原因就是mysql的innodb引擎各种事务隔离级别在数据库层面上几乎已经避免了这种现象的发生。因此大家想模拟的话可以用介入应用程序访问数据库的场景模拟一下。这里就不讲解了。
接下来的事物并发产生的问题才是我们要关注的问题
2.脏读——read-committed 事物隔离级别以上可以避免 (一个事物读到另一个事物,未提交的更新数据,而脏读问题可以在已提交读事务隔离级别以上去避免)
我们操作一波,先查看当前的事物隔离级别
select @@tx_isolation;
可以发现咋们mysql的InnoDB它默认的事物隔离级别是repetable-read 关于该事物隔离级别,我们一会去讲。
我们把事物个隔离级别调到最低
设置为未提交读,它是允许事务其它事物读取到未提交的数据,这样就可能引起脏读。我们可以通过以下的语句去设置,事物的隔离级别。
set seesion transation isolation level read uncommited; #设置数据库隔离级别为读未提交
我们再执行
select @@tx_isolation; # 执行结果 read uncommited
同时新建了一张表名为 account_innodb 的表。这张表示基于innodb引擎创建的 ,因为呢只有innodb它才支持事物嘛。
create table 'account_innodb'(
'id' int(2) NOT NULL AUTO_INCREMENT,
'name' varchar(10) DEFAULT NULL,
'balance' int (3) DEFAUILT '0',
PRIMARY KEY('id')
)engine=InnoDB AUTO_INCREMENT=5 CHARSET=UTF-8
这里我们不需要关闭自动提交只需要将事物隔离级别降到最低,同时创建好了测试表和数据。那咋们来复现一下脏读
这里我们用id 为 1的数据制造脏读,这里我们用到两个session两个session的事物都是read uncommited
我们先对这两个session开启一个事物
start transation; # 开启事物
session 1从该账户中取出 100 块钱,但是并未提交事物
# session 1
update account_innodb set balace = 1000 - 100 where id = 1; # 取出100块钱
select * account_innodb where id = 1; # 查到是900
# 这时我们还未提交事务
#session2
select * from account_innodb wher id = 1; # 查询账户有多少钱 这时是 900 ,但是我们的session1还未#提交,读进来竟然变成了最新的值 这个就是我么所谓的脏读了
#脏读的后果,这时我们的session读进来是900它就以为900是正确的数据了,sesion 1 它并不提交而是回滚了,可能#是网络或者是其他原因
#session 1
rollback;
#回滚之后
select * account_innodb where id = 1; # 查到是1000
# session 2
# 这时候不知道session 1已经回滚了此时执行
update account_innodb set balance = 900 + 200 where id = 1;
#更新之后直接提交事物
commit;
select * account_innodb where id = 1; # 查到是11000 此时数据变成他所预期的1100它的执行是成功的,#并且对于session1来讲它的事物也是成功地得到回滚,大家各自的角度都看不出什么问题来,但是对于拥有上帝视角的我们来说,问题就大了,原先有1000块钱,取款失败了,存款成功了应该是1200才对结果莫名其妙就少了1000块钱。这个就是脏读带来的恶果
那么如何避免脏读带来的恶果呢,只需要把事物隔离级别设置成 read_commited 就是 read_uncommited 上面一个级别,read_commited 即提交读。这样规定只能读到其他其它事物已提交的内容。该事物的隔离级别也是oracle默认的隔离级别,咋们可以试试对两个session都设置成该隔离级别。
set seesion transation isolation level read commited; #设置数据库隔离级别为读已提交
我们先对这两个session开启一个事物
start transation;
经过我们上面的操作现在id为1的数据余额已经变成了1100块钱
#session1
update account_innodb set balance = 1100 - 100 where id = 1;
select * from account_innodb where id = 1; # 余额1000
#session 2
select * from account_innodb where id = 1; # 1100 这里我们可以注意到并未像先前那样 在read_uncommited 隔离级别下,读到session 1未提交的数据,即并未读到未提交的1000这样就避免了脏读问题,
接下来历史的过程继续重演
#session 1
rollback;
#session2
update account_innodb set balance = 1100 + 200 where id = 1; #往账户里存入200块钱
commmit;
select * from account_innodb where id = 1; # 1300 从我们上帝视角来安这次数据是正确的,原本账户上有1100元钱,而session1回滚了,什么都没做session2成功存入了200块钱最终 1100 + 200 = 1300,因此read_uncommited 作为事务隔离级别的最低那一等。允许事务读取事务未提交的数据,是避免不了脏读的。而read_commited 则完美解决,这样就避免了脏读的发生。
3.不可重复读——repeatable-read 事务隔离级别以上可以避免 (它的含义是事务A多次读取同一数据,事务B在事务A读取的过程中,做了更新并提交,导致事务A多次读取同一数据时结果不一致)
咋们来模拟一下实际的情况,上面我们的两个session都设置成了因此read_uncommited了。
两个session同时start
start transation;
我们还是操作id为1的数据
#session 1
select * from account_innodb where id = 1; #1300
#session 2
update account_innodb set balance = balance + 300;
select * from account_innodb where id = 1; #1600
此时session 2还没提交
#session 1
select * from account_innodb where id = 1; #1300 由于我们的read_uncommited避免了脏读索引所以着了balance查到的还是 1300
#session 2
commit; #此时提交修改
#session 1
select * from account_innodb where id = 1; #1600 此时我们的session1什么改动都没有,结果硬生生的就变成1600了
commit; #此时提交修改
这就是不可重复读数据,session1在当前事务内一直去读数据,发现读的几次中结果竟然不一致,这样如果拿着现在读取出来的数据去进行操作,因为现在的数据也不一定是最终的数据,索引可能会造成数据混乱。
4.可重复读
retableread 顾名思义就是支持多次重复读,是InnoDB默认的事务隔离级别。
设置隔离级别
#session1
set session transaction isolation level repeatable read;
#session2
set session transaction isolation level repeatable read;
session1和session2同时开启事物
start transaction;
#session1
select * from account_innodb where id = 1; # 此时balance 是1600
#session2
select * from account_innodb where id = 1; # 此时balance 是1600
update account_innodb set balance = balance + 400 where id = 1;
select * from account_innodb where id = 1; # 此时balance 是2000 此时session2未commit
#session1 session1查账户
select * from account_innodb where id = 1; # 此时balance 是1600 此时也是避免了脏读,读到了未变化的余额即1600
#session2 此时做提交操作
commit;
#session1 session1再查账户
select * from account_innodb where id = 1; # 此时balance 是1600 此时也是避免了脏读,读到了未变化的余额即1600,这要就保证了不管其他的事物如何提交,自己用的那份还是不变
#此时session1再对数据做更改
update account_innodb set balance = balance + 400 where id = 1;
#session1提交事物
commit;
#再次查询 发现结果并不是我们设想的1500这个错误的结果,而是1900这个正确的结果
select * from account_innodb where id = 1; # 1900
5.幻读 它的含义是事务A读取与搜索条件相匹配的若干行,事物B以插入或者删除行等方式,来修改事物A的结果集,导致事务A看起来像出现幻觉一样。
咱们来模拟一下
现在session1和session2的事物隔离级别都设置成 read commited;
set session transaction isolation level read commited;
之前我们是对一行数据做操作,现在我们是对数据库的所有记录做操作。
#session1和session2同时开启事务
start transaction;
#此时我们在session1里面用当前读来获取当前事务提交的最新的数据,关于当前读和快照读我们后面马上讲到
select * from account_innodb lock in share mode;
#此时我们在session2插入一条新的记录
insert into account_innodb values(4,"newman",500);
#然后在session1进行全表更新操作,上一次session查到有3条数据,不知道sesssion2插入了一条新的数据。
update account_innodb set balance = 1000;
#此时,sesion1确成功更新了4条记录,此时它很懵逼,我不是只有3条记录吗?? 此时第四条数据就是幻行,此时就像出现了幻觉一样。如果session2删除了一条数据,此时也会出现幻读。
#那怎样避免幻读?就是将事务隔离级别调到最高的级别
#将session1和session2的事物隔离级别设置成serializale;
set sesstion transaction isolation level serializable;
#紧接着我们再重复一次刚刚的操作。
#session1和session2同时开启事务
start transaction;
#此时我们在session1里面用当前读来获取当前事务提交的最新的数据,关于当前读和快照读我们后面马上讲到
select * from account_innod; #在serializable隔离级别下我们所有的,sql执行会自动加上锁
#此时我们在session2插入一条新的记录
insert into account_innodb values(5,"noldman",600); #此时在serializable隔离级别下,我们的操作被block住了。需要session1执行commit或者rollback才能执行,这样就避免了幻读的发生。
我们发现更新丢失是在数据库层面的各个隔离级别下都很不会发生的,它发生的场景是应用程序层面,所以我们要在应用程序层面避免。
我们重点来看看后面的几种问题:
读未提交:read uncommited 会发生脏读,不可重复读,幻读。
读已提交:read commited 避免了脏读,避免不了不可重复读以及幻读。
可重复读:避免了脏读和不可重复读,但是理论上避免不了幻读,而是用了某种巧妙的方式规避了幻读。
串行化:避免了脏读,不可重复读,幻读。
不可重读读和幻读两兄弟看上去非常的类型,其实不然不可重复读,侧重于对同一数据 的修改。幻读则侧重于新增或者删除。
我们发现更新丢失是在数据库层面的各个隔离级别下都很不会发生的,它发生的场景是应用程序层面,所以我们要在应用程序层面避免。
我们重点来看看后面的几种问题:
读未提交:read uncommited 会发生脏读,不可重复读,幻读。
读已提交:read commited 避免了脏读,避免不了不可重复读以及幻读。
可重复读:避免了脏读和不可重复读,但是理论上避免不了幻读,而是用了某种巧妙的方式规避了幻读。
串行化:避免了脏读,不可重复读,幻读。
不可重读读和幻读两兄弟看上去非常的类型,其实不然不可重复读,侧重于对同一数据 的修改。幻读则侧重于新增或者删除。
这里有的人会说我们把事物隔离级别设置成最高的串行化那不是所有问题都避免了吗,为什么还要给出一些没法避免某些或者上述全部事物并发问题的隔离级别?其实是处于性能考虑,事物隔离级别越高,安全性越高,串行化执行越严重,这样就降低了数据库的并发度。因此大家了可以根据业务的需要去修改事物隔离级别。oracle默认为read commited,mysql默认为repeatable read;