什么是事务
访问并可能更新数据库中各种数据项的一个程序执行单元
事务的特性
原子性
事务中的所有操作作为一个整体像原子一样不可分割,要么全部成功,要么全部失败
一致性
事务的执行结果必须使数据库从一个一致性状态到另一个一致性状态
隔离性
并发情况下,主要会产生问题的是这个 并发执行的事物不会相互影响,其对数据库的影响和它们串行执行时一样
持久性
事务一旦提交,其对数据库的更新就是持久的。任何事务或系统故障都不会导致数据丢失
并发异常
丢失更新
丢失更新是指事务覆盖了其他事务对数据已提交的修改,导致其他事务的修改好像丢失了一样
脏读
一个事务读取了另一个事务未提交的数据
幻读
事务读取某个范围的数据时,因为其他事务的操作导致前后两次读取的结果不一致
不可重复读
一个事务对同一数据读取结果前后不一致
幻读和不可重复读的区别是幻读是对数据的统计工作,不可重复读针对的是一条数据的两次查询
异常问题的演示
数据库版本
-
查看数据库版本
select version(); -
查看默认的隔离级别
select @@transaction_isolation; -
为了演示,将隔离级别设置为“读未提交”
set SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
脏读
- 开启两个控制台,并都开启事务:
start transaction; - 两个控制台中查看数据:
select * from account where id=1; - 控制台一更新数据但不提交:
update account set balance=balance+200 where id=1; - 控制台二查看数据:
select * from account where id=1;发现可以读到事务一未提交的数据 - 控制台一回滚:
rollback; - 控制台二更新并提交:
update account set balance=balance-1000; - 控制台二
commit; select * from account where id=1;发现id为1的数据balance值为200,事务的回滚操作并没有生效,而且被另一个事务覆盖了。
幻读
- 控制台一读数据:a.
start transaction;b.select sum(balance) from account; - 控制台二修改数据并提交事务:a.
start transaction;b.insert into account values (4, 'alphago', 1000)c.commit; - 控制台一再次读数据:
select sum(balance) from account;两次读取sum的结果不同
不可重复读
- 控制台一读数据:a.
start transaction;b.select * from account where id=1; - 控制台二插入数据并提交事务:a.
start transaction;b.update account set balance=balance+200 where id=1;c.commit; - 控制台一再次读数据:
select * from account where id=1;结果发现控制台一两次select的结果不同,出现了不可重复读
事务的隔离级别
读未提交
读已提交
- Oracle默认级别
- 解决脏读问题,不可重复读和幻读没有解决
可重复读
- mysql默认级别
- 解决脏读、不可重复读问题,不能解决幻读
可串行化
正常情况下可重复读仍会出现幻读,但是当mysql设置为可重复读隔离级别时,mysql利用锁机制已经解决了脏读、幻读、不可重复读三个问题
丢失更新的解决方法
悲观锁机制
-
假定这样的问题是高概率的,最好一开始就锁住,免得更新老是出错
-
添加共享锁方式:
select * from account lock in shard mode;事务A在用共享锁锁住一些数据时,事务B可以查询该数据,如果要修改,必须等事务A提交以后才能操作 -
添加排它锁方式:
select * from account for update;事务A在用排他锁锁住一些数据时,事务B不能对该数据进行任何操作,包括读,必须等事务A提交以后才能操作
乐观锁机制
- 假定这样的问题是小概率的,最后一步做更新的时候再锁住,免得锁住的事件太长影响其他人做有关操作
- 在表中增加一个类型是timestamp字段,并将其设置只要该表进行插入或修改操作时都会更新该字段为最新时间
- 在修改数据时通过检查timestamp是否改变判断出当前更新基于的查询是否已经是过时的版本