事务和锁
基础事务使用
# 事务
begin; # 开始事务
insert into runoob_transaction_test value(5);
insert into runoob_transaction_test value(6);
commit; # 提交事务
四个隔离级别
设置后在使用事务时会产生特殊的效果
- read uncommitted(未提交读),隔离级别最低
set session transaction isolation level read uncommitted;
A:启动事务,此时数据为初始状态
B:启动事务,更新数据,但不提交
A:再次读取数据,发现数据已经被修改了,这就是所谓的“脏读”
B:回滚事务
A:再次读数据,发现数据变回初始状态
- read committed(已提交读,默认)
set session transaction isolation level read committed;
A:启动事务,此时数据为初始状态
B:启动事务,更新数据,但不提交
A:再次读数据,发现数据未被修改
B:提交事务
A:再次读取数据,发现数据已发生变化,说明B提交的修改被事务中的A读到了,这就是所谓的“不可重复读”
- repeatable read(可重复读),这里涉及的知识点有事务版本,数据链
A:启动事务,此时数据为初始状态
B:启动事务,更新数据,但不提交
A:再次读取数据,发现数据未被修改
B:提交事务
A:再次读取数据,发现数据依然未发生变化,这说明这次可以重复读了
B:插入一条新的数据,并提交
A:再次读取数据,发现数据依然未发生变化,虽然可以重复读了,但是却发现读的不是最新数据,这就是所谓的“幻读”
A:提交本次事务,再次读取数据,发现读取正常了
- 将A的隔离级别设置为可串行化(Serializable)
A:启动事务,此时数据为初始状态
B:发现B此时进入了等待状态,原因是因为A的事务尚未提交,只能等待(此时,B可能会发生等待超时)
A:提交事务
B:发现插入成功
# serializable完全锁定字段,若一个事务来查询同一份数据就必须等待,直到前一个事务完成并解除锁定为止。是完整的隔离级别,会锁定对应的数据表格,因而会有效率的问题
死锁
- A更新lives表,请求lives的排他锁,成功。
- B更新earth表,请求earth的排他锁,成功。
- 5秒过后
- A更新earth,请求earth的排它锁,由于B占用着earth的排它锁,等待。
- B更新lives,请求lives的排它锁,由于A占用着lives的排它锁,等待。
这样相互等待对方释放资源,造成资源读写拥挤堵塞的情况,就被称为死锁现象,也叫做阻塞。而为什么会产生,上例就列举出来啦。
然而数据库并没有出现无限等待的情况,是因为数据库搜索引擎会定期检测这种状况,一旦发现有情况,立马选择一个事务作为牺牲品。牺牲的事务,将会回滚数据。有点像两个人在过独木桥,两个无脑的人都走在啦独木桥中间,如果不落水,必定要有一个人给退回来。这种相互等待的过程,是一种耗时耗资源的现象,所以能避则避。
哪个人会被退回来,作为牺牲品,这个我们是可以控制的。控制语法:
set deadlock_priority <级别>
死锁处理的优先级别为 low<normal<high,不指定的情况下默认为normal,牺牲品为随机。如果指定,牺牲品为级别低的。
还可以使用数字来处理标识级别:-10到-5为low,-5为normal,-5到10为high。
减少死锁,高并发带来的死锁是不可避免的,所以我们只能让其变的更少。
- 按照同一顺序访问数据库资源,上述例子就不会发生死锁啦
- 保持是事务的简短,尽量不要让一个事务处理过于复杂的读写操作。事务过于复杂,占用资源会增多,处理时间增长,容易与其它事务冲突,提升死锁概率。
- 尽量不要在事务中要求用户响应,比如修改新增数据之后在完成整个事务的提交,这样延长事务占用资源的时间,也会提升死锁概率。
- 尽量减少数据库的并发量。
- 尽可能使用分区表,分区视图,把数据放置在不同的磁盘和文件组中,分散访问保存在不同分区的数据,减少因为表中放置锁而造成的其它事务长时间等待。
- 避免占用时间很长并且关系表复杂的数据操作。
- 使用较低的隔离级别,使用较低的隔离级别比使用较高的隔离级别持有共享锁的时间更短。这样就减少了锁争用。
建议把mysql的锁级别设置在默认就好,使用代码去实现乐观锁,避免mysql的死锁