美文网首页
数据库(六),锁

数据库(六),锁

作者: dy2903 | 来源:发表于2018-02-15 10:56 被阅读5次

数据库(五),事务里面我们讲了事务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

相关文章

  • 数据库(六),锁

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

  • mysql进阶-行级锁、表级锁、乐观锁、悲观锁

    从应用的角度来看数据库锁可分为悲观锁、乐观锁从数据库(InnoDB)的角度看,数据库锁可以分为行级锁和表级锁 1....

  • 数据库_锁

    六、数据库锁1.mysql都有什么锁,死锁判定原理和具体场景,死锁怎么解决?MySQL有三种锁的级别:页级、表级、...

  • 两端锁协议

    1、数据库锁 数据库锁粒度划分:行锁、页锁、表锁。共享锁:读锁、S锁。事务T可以对A进行读取,其他事务只能读取而不...

  • MySQL05

    数据库高级对象,锁,权限管理 视图 索引 触发器 存储过程 悲观锁与乐观锁 行级锁、表级锁、页锁 数据库权限管理 ...

  • 数据库如何加锁?锁是用来干嘛的?

    数据库中的共享锁与排它锁 共享锁(S锁),又称为读锁,如果数据对象加上共享锁之后,则该数据库对象可以被其他事务查看...

  • 秒杀系统技术方案演变过程

    前言:秒杀系统需要保证商品库存不能出现超卖现象。一、数据库锁机制(悲观锁、乐观锁)实现秒杀(1)悲观锁:数据库本身...

  • mysql全局锁和表锁

    MySQL中的锁可以分为三类:全局锁,表级锁和行锁。 全局锁 全局锁是对整个数据库加锁,可以让整个数据库处于只读状...

  • 多线程之锁

    其实常用也就那么几个锁,总感觉线程所用的锁机制和数据库的很相似,什么读写锁,就和数据库的共享锁,排他锁没什么区别....

  • 解决并发问题,数据库常用的两把锁!

    在写入数据库的时候需要有锁,比如同时写入数据库的时候会出现丢数据,那么就需要锁机制。 数据锁分为乐观锁和悲观锁 它...

网友评论

      本文标题:数据库(六),锁

      本文链接:https://www.haomeiwen.com/subject/fzoytftx.html