事务并发包括:
- 第一类丢失更新:撤销一个事务时,把其他事务已提交的更新数据覆盖。
- 脏读:一个事务读到另一个事务未提交的更新数据。
- 虚读:一个事务读到另一个事务已提交的新插入的数据。
- 不可重复读:一个事务读到另一个事务已提交的更新数据。
- 第二类丢失更新:是不可重复读的特例,一个事务覆盖另一个事务已提交的更新数据。
第一类更新丢失 | ||
时间 | 取款事务 | 转账事务 |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户的存款余额为1000元 | |
T4 | 查询账户的存款余额为1000元 | |
T5 | 汇入100元,把存款余额改为1100元 | |
T6 | 提交事务 | |
T7 | 取出100元,把存款余额改为900元 | |
T8 | 撤销事务,账户的存款余额恢复为1000元 |
脏读 | ||
时间 | 取款事务 | 转账事务 |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户的存款余额为1000元 | |
T4 | 取出100元,把存款余额改为900元 | |
T5 | 查询账户的存款余额为900元(脏读) | |
T6 | 撤销事务,账户的存款余额恢复为1000元 | |
T7 | 汇入100元,把存款余额改为1000元 | |
T8 | 提交事务 |
虚读 | ||
时间 | 取款事务 | 转账事务 |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 统计网站注册总数为10000人 | |
T4 | 注册一个新用户 | |
T5 | 提交事务 | |
T6 | 统计网站注册总数为10001人(虚读) | |
T7 | 到底是哪个统计数据有效,不确定 | |
T8 |
不可重复读 | ||
时间 | 取款事务 | 转账事务 |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户的存款余额为1000元 | |
T4 | 查询账户的存款余额为1000元 | |
T5 | 取出100元,把存款余额改为900元 | |
T6 | 提交事务 | |
T7 | 查询账户的存款余额为900元 | |
T8 | 存入账户100元,到底是把余额修改为1000元还是1100元? |
第二类丢失更新 | ||
时间 | 取款事务 | 转账事务 |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户的存款余额为1000元 | |
T4 | 查询账户的存款余额为1000元 | |
T5 | 取出100元,把存款余额改为900元 | |
T6 | 提交事务 | |
T7 | 存入账户100元,把余额改为1100元 | |
T8 | 提交事务 |
- 解决事务并发,采用共享锁和独占锁
解决事务并发问题 | ||
资源上已经放置的锁 | 第二个事务进行读操作 | 第二个事务进行更新操作 |
无 | 立即获得共享锁 | 立即获取独占锁 |
共享锁 | 立即获得共享锁 | 等待第一个事务解除共享锁 |
独占所 | 等待第一个事务解除独占锁 | 等待第一个事务解除共享锁 |
- 死锁例子
事务1
begin;
update customers set name='tom' where id=1;
update orders set oreder_number='tom_order001' where Id=1;
commit;
事务2
update orders set oreder_number='jack_order001' where Id=1;
update customers set name='tom' where id=1;
commit;
死锁 | ||
时间 | 事务1 | 事务2 |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | update customers set name='tom' where id=1; 对customers表中ID为1的记录放置独占锁, 只有当整个事务结束才会解除该锁 | |
T4 | update orders set oreder_number='jack_order001' where Id=1; 对order表中ID为1的记录放置独占锁, 只有当整个事务结束才会解除该锁 | |
T5 | update orders set oreder_number='tom_order001' where Id=1; 等待事务2解除对order表中ID为1的记录放置独占锁 | |
T6 | update customers set name='tom' where id=1; 等待事务1解除对customers表中ID为1的记录放置独占锁 |
死锁避免法则:
- 合理安排表访问顺序
- 使用短事务
- 如果对数据的一致性要求不是很高,可以允许脏读。脏读不需要对数据资源加锁,可以避免锁冲突。
- 如果可能的话,错开多个事务访问相同的数据资源的时间,以防止锁冲突。
- 使用尽可能地的事务隔离级别。
事务隔离级别 | |||||
隔离级别 | 是否出项第一类丢失更新 | 是否出现脏读 | 是否出现虚读 | 是否出现不可重复读 | 是否出现第二类丢失更新 |
Serializable | 否 | 否 | 否 | 否 | 否 |
Repeatable Read | 否 | 否 | 是 | 否 | 否 |
Read Commited | 否 | 否 | 是 | 是 | 是 |
Read Uncommited | 否 | 是 | 是 | 是 | 是 |
并发性和隔离级别成反比