第一节:事务的并发处理ACID
Atomicity 原子性
Consistency 一致性
Isolation 隔离性
Durability 持久性
第二节:事务并发可能出现的问题
2.1第一类丢失更新(Lost Update)
说明:事务B的更新丢失。撤销一个事务影响到另外一个事务
时间 | 取款事务A | 存款事务B |
T1 | 开始事务 |
|
T2 |
| 开始事务 |
T3 | 查询账户余额1000元 |
|
T4 |
| 查询账户余额1000元 |
T5 |
| 汇入100元把余额改成1100元 |
T5 |
| 提交事务 |
T7 | 取出100把余额变成900 |
|
T8 | 撤销事务 |
|
T9 | 余额恢复1000元(丢失更新) |
|
2.2脏读(ditrty read)
说明:读到了别的事务还提交的事务,此时B事务还未提交,中间的事务可能回滚,所以读到数据可能不对。
时间 | 取款事务A | 存款事务B |
T1 | 开始事务 |
|
T2 |
| 开始事务 |
T3 |
|
|
T4 |
| 查询账户余额1000元 |
T5 |
| 汇入100元把余额改成1100元 |
T5 | 查询账户余额1100元 (读取的脏数据) |
|
T7 |
| 回滚 |
T8 | 取款1100元 |
|
T9 | 提交事务失败 |
|
2.3不可重复读(non-repeatable read)
当前事务已经读取的数据记录,被其他事务修改或删除
说明:同一个事务中读取两遍的值不一致,此时无法确定按哪个值来处理。
时间 | 取款事务A | 存款事务B |
T1 | 开始事务 |
|
T2 | 查询账户余额1000元 |
|
T3 |
| 开始事务 |
T4 |
| 汇入100元账户余额改为1100元 |
T5 |
| 提交事务 |
T5 | 查询账户余额1100元 |
|
T7 |
|
|
T8 | 提交事务 |
|
2.4第二类丢失更新(second lost update)
说明:不可重复读的特殊情况,本来A事务在提交之前应该再次读一遍数据,读取的数据还是和第一次是不一致的。
时间 | 取款事务A | 存款事务B |
T1 |
| 开始事务 |
T2 | 开始事务 |
|
T3 |
| 查询账户余额1000元 |
T4 | 查询账户余额1000元 |
|
T5 |
| 取款100元把余额改成900元 |
T5 |
| 提交事务 |
T7 | 汇入100元 |
|
T8 | 提交事务 |
|
T9 | 余额改成1100元 |
|
2.5幻读(phantom read)
说明:此时两次机查询的数据不一致,同不可重复读,两次查询出来的数据不一致。幻读为插入和删除的操作,以上为更新的操作。
其他事务插入了新的数据,当前事务以相同的查询条件,在那个事务插入数据之前和之后查询数据,得到的数据记录的条数不一样。
时间 | 查询学生事务A | 查询学生事务B |
T1 | 开始事务 |
|
T2 |
| 开始事务 |
T3 | 查询学生为10人 |
|
T4 |
| 插入1个学校 |
T5 | 查询学生为11人 |
|
T5 |
| 提交事务 |
T7 | 提交事务 |
|
第三节:JDBC事务隔离级别
(1):TRNSACTION_NONE:不支持事务
(2):TRNSACTION_READ_COMMITTED:防止脏读和幻读,但是不能限制不可重复读,因为不可重复读是读取已经提交的事务,幻读类似
(3):TRNSACTION_READ_UNCOMMITTED:允许脏读,幻读,不可重复读
(4):TRNSACTION_REPEATABLE_READ(可重复读):MYSQL默认设置级别,即把这条数据读取出来以后加把锁,其他事务不能更新这条数据的值,等我这个事务处理完成后其他事务才能对该条数据做更新,select xxx rom xxxtable where xxx for update,数据库为其读取的这条记录加把锁,这条事务没提交之前,其他事务就不能更新这条记录
(5):TRNSACTION_SERIALIZABLE:无论多少事务来都不并发,一个一个执行
第四节:悲观锁和乐观锁
4.1悲观锁和乐观锁说明
利用悲观锁和乐观锁解决不可重复读(non-repeatable read)问题设成READ_COMMITTED通过hibernate实现。
悲观锁:通过数据库加锁(for update)
乐观锁:1:事务取出数据库中一条记录的时候为其设置一个version号
2:如果有事务更新了这条数据version版本号自动加一
3:事务把数据更改后检查版本号是否是刚取出来的那个version,如果不一样事务 回滚,否则会冲掉人家修改后的数据。
4.2 Hiernate悲观锁和乐观锁实例
Session session1 = sf.openSession();
Session session2 = sf.openSession();
session1.beginTransaction();
Account a1 = (Account )session1.load(Account .class,1); //把数据库记录load出来
session2.beginTransaction();
Account a2 = (Account )session2.load(Account .class,1);
a1.setBalance(100);
a2.setBalance(300);
session1.getTransaction.commit();
session2.getTransaction.commit();