文章目录
事务
什么是事务?
事务是代表单个工作单元的一组SQL语句,这些语句要么全做要么全不做
如果其中一个失败就不称为事务
如果某一个失败,我们就要还原之前操作进行的变动
事务的ACID特性:
- 原子性。事务是数据库的逻辑工作单位,是不可分割的工作单元(即要么全做要么全不做)
- 一致性。事务执行结果必须是使数据库从一个一致性的状态变到另一个一致性状态。一致性状态是指数据库中只包含成功事务提交的结果;不一致状态是指数据库中包含失败事务提交的部分结果
- 隔离性。一个事务不能被其他事务干扰。即一个事务内部的操作以及使用的数据对其他并发事务是隔离的,并发执行的各个事务之间不能互相干扰
- 持续性。一个事务一旦提交,对数据库的改变就是永久的
创建事务
use sql_store;
start transaction;
insert into orders (customer_id, order_id, status)
values (1, '2020-1-1', 1);
insert into order_items values (last_insert_id(), 1, 1, 1);
commit/rollback;
并发
在多个用户共同使用数据库的时候,多个用户一起操作,数据库进行处理就成为并发。
并发要保证事务数据的四大特性就要对出现的并发问题进行解决
并发问题
丢失更新
What?
当两个事务尝试更新相同的数据并且没有上锁时,就会发生丢失更新
较晚提交的事务会覆盖较早事务做的更改,即使两个事务更新的是一行的不同数据,由于时间的关系,较早修改的数据的那一列会被较晚的发生的事务所保存的那一列的信息所重新修改成原来的数据,这样就造成了丢失更新
Solution
使用锁
使用锁防止两个事务同时更新同样的数据
读脏数据
What?
当一个事务读取了尚未被提交的数据就是读取了脏数据
例如:
甲售票点运行事务T1,
读出车票余额A为16张;
甲售票点卖掉6张票,
修改车票余额A为10张,并写入数据库;
乙售票点运行事务T2,
读出车票余额A为10张;
甲售票点撤销了之前的操作,车票余额A恢复到16
Solution
需要为事务建立隔离级别;这样事务修改的数据不会立马被其他事务读取
不可重复读
What?
在某些事务中,读取了某个数据两次,得到了不同的结果
火车票售票系统操作序列3:
甲售票点运行事务T1,
读出车票余额A为16张;
乙售票点运行事务T2,
读出车票余额A也为16张;
乙售票点卖掉6张票,
修改车票余额A为10张,并写入数据库;
甲售票点运行事务T1,再次读出车票余额A为10张
封锁
向系统发出请求,对其枷锁。加锁后事务T对数据对象有了一定的控制。其他的事务不能更新此数据对象
基本锁
最基本的有两种:排它锁(X锁)、共享锁(S锁)
排它锁
又称写锁,事务对数据对象加锁,该事务既可以对对象进行读也可以进行写。其他任何事务都不能再对其加任何类型的所
共享锁
若事务T对数据对象A加S锁,则事务T可以对A进行读操作,但不能写A。其它事务只能对A再加S锁,而不能再加X锁,直到事务T释放A上的S锁。
锁的相容矩阵
T1****请求锁 相容性 T2****持有锁 | S | X | - |
---|---|---|---|
S | Y | N | Y |
X | N | N | Y |
- | Y | Y | Y |
三级锁协议
事务T在修改数据对象之前必须对其加X锁,直到事务结束才能释放X锁。(一级锁协议)
在一级封锁协议的基础上,再加上进一步的规定:事务T在读取数据对象之前必须对其加S锁,读完后并不立即释放S锁,直到事务T结束才释放(对二三级协议的概括)
三级锁协议就可以完成对上面的三种类型的并发错误做出处理
事务隔离级别
展示默认级别
show variables like 'transaction_isolation'
修改事务隔离级别
set (session) transaction isolation level serializable;
-- 如果其中加入了session在未来的所有事务都会是这个隔离级别
默认为第三种:可重复读取
使用最后一种能解决所有的问题
活锁
活锁(Live Lock)是指系统中的某个事务永远处于等待状态,得不到封锁的机会
解决方法
- “先来先服务”
- “升级”(长时间没有获得解锁,升高其优先级)
死锁
系统中有两个或两个以上的事务都处于等待状态,并且每个事务都在等待其中另一个事务解除封锁,它才能继续执行下去,但是哪个事务也不释放自己获得的锁,所以只好互相等待下去,造成任何一个事务都无法继续执行,这种现象称为“死锁”
最常见的就是在相同的两条语句下,两个语句的顺序问题造成了死锁
T1 | T2 |
---|---|
Lock A | . |
. | Lock B |
. | |
Lock B | . |
等待 | |
等待 | Lock A |
等待 | 等待 |
等待 | 等待 |
解决的两种方法
预防、诊断与解除
预防
一次封锁法
要求每个事务必须一次将所有要使用的数据全部加锁,否则就不能继续执行
问题
- 降低并发度
- 难于事先精确确定封锁对象
顺序封锁法
顺序封锁法是预先对数据对象规定一个封锁顺序,所有事务都按这个顺序实行封锁。
问题
- 维护成本高
- 难于实现
诊断
超时法
缺点
- 有可能误判死锁,由于某种原因导致事务的执行时间超过了时限,系统会误认为发生了死锁。
- 若时限设置得太长,死锁发生后不能及时被发现。
事务等待图法
并发控制子系统周期性地(比如每隔1 min)检测事务等待图,如果发现图中存在回路,则表示系统中出现了死锁。
解除
选择一个处理死锁代价最小的事务,将其撤销,释放该事务持有的所有锁,使其他事务得以继续运行下去。
并发调度的可串行性
可串行化调度
多个事务的并发执行是正确的,当且仅当其结果与按某一次川行地执行他们时的结果相同,这种并行调度策略就成为可串行化的调度
它是并行事务正确性的唯一准则
冲突可串行化调度
冲突的操作
不同的事务在时间上相邻的两个操作在不影响最终结果的前提下是可以交换执行的;如果不可交换就是冲突的操作
冲突的操作是指不同的事务对同一个数据的读写和写写操作
不冲突的操作是指不同的事务对同一个数据的读读操作和不同事务对不同的数据的任何操作
冲突可串行化调度
一个调度Sc在保证冲突操作的次序不变的情况下,通过交换两个事务不冲突操作的次序得到另一个调度SC’,如果SC’是串行的,那么就称调度SC是冲突的可串行化的调度。
如果一个调度是冲突可串行化的调度,那么它一定是可串行化的调度。
冲突可串行化调度是可串行化调度的充分条件,不是必要条件。
通过两段锁协议来产生可串行化调度。
两段锁协议
保证调度的正确性
所有的事务在封锁时应遵守以下的两条规则:
- 在对任何数据进行读、写操作之前,事务首先要获得对该数据的封锁
- 在释放一个封锁之后,事务不再申请和获得任何其他的封锁
事务遵守两段锁协议是可串行化调度的充分条件,而不是必要条件
两段锁协议不要求事务必须一次将所有要使用的数据全部加锁,因此遵守协议的事务也可能会发生死锁
冲突可串行化调度是可串行化调度的充分条件,不是必要条件。
通过两段锁协议来产生可串行化调度。
两段锁协议
保证调度的正确性
所有的事务在封锁时应遵守以下的两条规则:
- 在对任何数据进行读、写操作之前,事务首先要获得对该数据的封锁
- 在释放一个封锁之后,事务不再申请和获得任何其他的封锁
事务遵守两段锁协议是可串行化调度的充分条件,而不是必要条件
两段锁协议不要求事务必须一次将所有要使用的数据全部加锁,因此遵守协议的事务也可能会发生死锁