数据库(六),锁

数据库(五),事务里面我们讲了事务ACID属性,事务最重要的能在异常情况的修复以及并发连接的处理上。

异常情况的修复主要通过日志来完成,那么并发连接的处理主要通过。本章主要整理的是的相关知识。

为什么需要锁?

现在Bob的账户里面有1000块钱,此时程序突然同时来了两个要求,一个要把Bob的钱转给Smith 20块,一个要把Bob的钱转Joe 30块。这两个要求一查Bob的账户,都发现现在Bob有1000块,所以要求A算出现在Bob应该有980块,要求B算出来Bob应有970。要求A的数据被要求B的数据覆盖了。

这样就出问题了,明明应该扣50块钱,现在却只是扣了30块。

就是用来解决这样的并发访问的问题。当每次访问Bob账户之前,都加一个锁,禁止别人再次访问,只有等待持有锁的人来释放

image.png

悲观锁和乐观锁

悲观锁

如果事务A把Bob账户锁住了,事务B自然不能操作Bob账户,也就是说其他线程只能在外面等待。

这种加锁的方式就是悲观锁。它每次取读写数据时总认为数据会被别人修改,所以将数据加锁,置于锁定状态,不让别人访问。

缺点是如果持有锁的时间太长,其他用户需要等待很长的时间。

悲观锁主要适用于并发争抢比较严重的场景。

乐观锁

悲观锁的问题显而易见,如果将数据加锁了以后,其他的线程是无法访问的,只能等待。如果持有锁的时间太长,需要等待大量的时间。

所以我们引入了乐观锁,所谓乐观锁是认为一般情况下不会有太多的人修改余额,所有没有加锁,只有在最后更新的时候才去看是否有冲突。

具体怎么做呢?

可以在日志中加上一个version(版本)字段,

  • 每次的时候,不仅需要读出余额,还需要读出版本号。

  • 等修改了余额以后,往回写之前需要检查一下版本号,看看与读的时候版本号是否一样。

    • 如果不一样,说明数据已经被改变了,所以需要放弃写操作,重新读取余额和版本号

    • 如果一样,则将新余额写回去,把版本号加1 。

比如

事务1把Bob的余额减去30,此时它读到了(Bob余额=1000,版本=1)

事务2也需要将Bob的余额减去50,他也读到了(Bob余额=1000,版本=1)

然后事务1率先完成计算,把新的余额值970写回了,版本 加 1 ,变成了版本2。

事务2写回去的时候,发现最新的版本号变为2,表示之前读的数据已经改变,所以需要重新读一遍

这就是乐观锁,这种方式适合于冲突不多的场景,如果冲突很多,数据争用激烈,会导致不断的尝试,反而降低了性能。
image.png

死锁

死锁产生的条件

如果出现如下这种情况

  • 有两个线程同时参与

  • 这两个线程在不同方向给同一个资源加锁

  • 争抢相同的资源

那么很可能出现死锁

比如事务1是Bob给Smith转账,事务2是Smith给Bob转账。

当这两个事务单元同时发生的时候,就有问题呢。

事务单元1会先锁定Bob,然后锁定Smith,而事务单元2会先锁定Smith,然后锁定Bob

事务1会等待事务2把Bob给释放了,而事务2在等待事务1把Smith释放了。

image.png

如何解决

那么如何解决死锁呢?最好的方法是尽可能不出现死锁,当然很难。或者说如果锁定时间超时了,则强行释放,不过这种方法效率比较低,因为如果有用户的事务本来时间就很长,则每个死锁的检测时间将会很长。

所以最优的方案在于预测死锁,可以把事务单元等待的锁记录下来

比如下图中,事务单元1持有"Lock Bob"的锁,现在又在申请一把"Lock Smith"的锁,在申请之前,可以查看同样申请了"Lock Smith"的有哪些事务单元。明显事务单元2也申请过这把锁。好了,下一步是看事务单元2在申请什么锁呢,发现它居然在申请"Lock Bob"这把锁,而这把锁目前由事务单元1持有。所以现在已经发现有死锁的可能了,也就是发生了碰撞。所以可以提前补救。

image.png

U锁

下面来讨论一种死锁的情况。如下图
image.png

事务1 Trx1

开始事务1
读A(读锁)
A - 100(读锁需要升级为写锁)
提交事务1

事务2 Trx2

开始事务2
读A(读锁)
A - 100(读锁需要升级为写锁)
提交事务2(解锁)

事务1和事务2的读锁是可以并行的,所以读锁可以同时进入临界区,但是写锁不能,会被挡在外面。此时事务2又发起了写锁。那么尴尬的局面就产生了。

事务1的写锁需要等事务2的读锁释放资源。

事务2的写锁需要等待事务1的读锁释放资源。

所以形成了死锁。其实这种死锁的形成条件非常的简单,只需要针对同一个数据进行读写。比如说update set A=A-1 where id = 100如果运行多次,就会出现死锁

解决的办法是引入U锁,可以将读锁直接升级为写锁。

对于事务1,读以后马上就是写,所以直接就使用写锁,而不是读锁呢。
image.png

同理事务2也是如此。

image.png

image.png

原文地址:https://www.cnblogs.com/dy2903/p/8449316.html