高并发下使用锁处理数据不一致

204阅读-0评论-作者:码农 高并发 数据一致性 锁机制 文件锁 数据库锁
高并发下操作数据库会发生争抢资源的情况,导致数据出现问题,这个时候我们就需要用锁机制来解决这个问题,常用的有文件锁和数据库锁。当然我们不考虑性能和效率,也不考虑这俩种锁的实际应用场景较少,只是用来说明这个机制。并发到一定量后数据库锁和文件锁的性能下降的相当厉害,后面我们会继续讲使用redis的消息队列实现高并发的处理。

    这篇文章我们就简单讲解一下文件锁和数据库锁的用法。


在用代码实现锁的前提下,我们先来简单描述一下数据库锁,数据库锁我们一般分为共享锁和排它锁,共享锁就是我读数据的时候,其他人也可以读,但是不能操作数据;排它锁就是我对数据操作的时候,其它人不能对数据进行操作。再根据锁的粒度来区分,分为表级锁和行级锁,还有其它的什么页级锁。我们下面讲的就是表级锁。下面我们说的对数据进行操作指的是添加、修改、删除操作。

共享锁

共享锁我们又叫做读锁,我们启动数据库,打开俩个dos窗口,全都链接上数据库,效果如下:

blob.png 

我们分别在俩个客户端里查询数据,和更新数据,理所当然的,都是可以进行操作的,下面我们就给一个客户端加个读锁,命令:lock table demo read; 效果如下

blob.png

然后我们在俩个客户端分别查询数据,结果如下:

blob.png

我们发现他们都是可以读取数据的,这个时候我们再在俩个客户端里面执行更新操作,把num修改为2试试:结果如下:

blob.png

这个时候我们发现,加锁的那个客户端会报--表demo被加了一个读锁,不能进行更新,另外一个客户端会处于等待状态,也是不执行更新的,这个时候我们就知道了读锁是不能对数据进行操作的。

接着我们释放锁,会发现那个等待的客户端的更新语句被执行了,释放锁的命令:unlock tables; 

    总结一下读锁的功能,加了读锁,所有人都可以读数据,所有人都不能对数据进行操作。下面我们来说排它锁,也就是写锁。


排它锁

我们重新把num修改为1,然后在一个客户端里面加一个写锁,命令:lock table demo write; 

blob.png

然后在加写锁的这个客户端里面查询数据,命令:select * from demo; 结果如下:

blob.png

然后在加写锁的客户端里面执行更新,把num修改为3,命令:update demo set num = 3 where i = 1; ,结果如下:

blob.png

我们发现加了写锁的客户端是可以对数据进行任何操作的,接着我们用另外一个客户端,对数据进行查询,命令:select * from demo; 这个时候我们发现客户端处于等待状态,并没有执行读取,我们强制关闭等待,然后执行更新操作,把num修改为4,命令:update demo set num = 4 where id = 1; 同样发现也是处于等待状态,这个时候我们去另外一个客户端释放锁,命令:unlock tables; 这时候我们发现处于等待状态的更新操作执行了。


写锁总结起来就是,对表加写锁的用户可以对数据进行任何读取和操作,其他人是不可以对数据进行读取和操作的。这种功能就可以实现高并发下解决数据不一致的问题。下面我们就用代码来实现。


数据库锁

我们模拟真实场景的商品售卖,比如我们现在库存有10件商品,也就是我们设计demo表中id为1的商品数量num为10,我们购买的时候查询这个num,如果这个num是0了,那么我们就给用户提示商品售罄,如果是并发不高一个一个用户购买,那肯定是没有问题的,但是如果很多用户一起下单,就会出现超卖的情况,也就是这个num会变成负数,换句话说就是成功购买的用户将会超过10个,我们来看代码:

image.png

代码比较简单,相信大家也是可以看懂的,并发数少的时候这个代码是不会出现问题的,我们直接用apache的ab测试工具,来模拟一百个用户抢购这10个商品,看看会发生什么情况:

image.png

我们在看看数据库中的num

image.png

这个时候我们发现num竟然变成负数了,也就意味着超卖了3个。


我们继续上带锁的代码

image.png

然后我们继续使用apache的ab工具测试,发现数据库中的num就一直是0了,那怕并发再高也不会发生超卖的情况,大家自行测试即可,我们只是将锁机制,消息队列实现高并发超卖的情况不在我们讨论范围之内。


文件锁

文件锁和数据库锁一样,分为读锁和写锁,也就是共享锁和排它锁,还有就是可以有非租塞模式,如果是数据库锁的话就是有请求对数据库加锁了,那么其它请求需要等待别人完成请求才能执行数据库操作,文件锁的非租塞模式就可以不用等待直接返回操作不了数据库,例如我们都去抢购一个产品,如果别人加锁了,那么我们就可以得到系统繁忙的提示,相信大家也可以理解这个功能,下面我们直接来实现文件锁的写锁:

image.png

同样的我们使用ab测试工具进行测试,结果一样不会发生超卖的情况。


对于锁机制的讲解还是比较通俗易懂的,相信大家都有一个基本概念了。


我们再简单说一下数据库按照粒度区分的三种锁:表级锁、行级锁、页级锁

表级锁:写锁会锁定整个表,会让其他人无法访问整张表的数据,也就是冲突多,但是速度快,效率高,消耗内存低,总结起来就是:开销小、加锁快、锁的粒度大、不会死锁、冲突高、并发度低;

行级锁:只会锁定要操作的数据,冲突少,但是速度慢,效率低,总结起来就是:开销大、加锁慢、粒度最小、会死锁、冲突最低、并发度高;

页级锁:折中表级锁和行级锁,总结起来就是介于表级锁和行级锁之间,会死锁;

特别注意:行级锁是一种类似建议锁的机制,使用的时候需要先检查锁,也就是每个事务都要在查询语句里面写for update,官方的解释过于简单,普通select语句默认不加锁,CUD语句默认写锁,想了解更多,自行搜索建议锁和强制锁。不同数据库实现行级锁的机制不太一样,mysql的行级锁是用索引条件加锁,也就是查询条件用到了索引就是行锁,其它的全部表级锁。


QQ:1007027975

0.058245s