问题分析

突然间被运营滴滴说某个活动的报名人数超过了限制人数,问怎么回事,我一下子还挺蒙的,我明明有在报名的操作之前设置了检查如果超过报名人数代码逻辑会抛错继续报名的呀。

然后我又打开数据库看了一下,出现了以下的情况:

于是情况就很明了了,这明显就是并发控制没有做好。为了叙述清楚这个情况,下面讲述一下业务逻辑:首先是从meeting表查是否报名已满,如果未满,则开始事务,将signed字段自增1,然后把参会记录插入到meeting_member表,提交事务。这里实际上是出现了丢失更新,举例如下。

T1

T2

数据库中signed的值BEGIN;

0

SELECT signed FROM meeting WHERE meeting_id=xx; (读出来的值为0)

BEGIN;

UPDATE meeting SET signed=signed+1 WHERE meeting_id=xx;

SELECT signed FROM meeting WHERE meeting_id=xx; (读出来的值也为0)

COMMIT;

UPDATE meeting SET signed=signed+1 WHERE meeting_id=xx;

COMMIT;

1

可以看到,如果T1没有提交,T2会读取到原来的值,最终出现T1的更新丢失的问题。对应到具体场景就是,如果有两个事务,第一个读取、然后更新,但还没有提交,这时候开始了第二个事务,他读取到的就是第一个事务更新前的数据,同样进行自增,这是T1提交,T2也提交,但是signed只增加了1。

(中间有进行实验,但是由于填这个坑花的时间太长,就不把实验过程放上来了,结果跟上表中罗列的一致)

PS

一开始我马上联想到的是事务的隔离级别以及脏读、不可重复读、幻读之类的问题,实际上这里出现的是第二类丢失更新

这里蛮坑的,我往事务隔离等级这个方向想了很久,才发现方向根本不对,可重复读已经解决了脏读和不可重复读的问题

解决思路

解决方法实际上比较简单(简单个屁),只需要加上悲观锁或者乐观锁就可以了。值得一提的是,其实把事务隔离等级调成未提交读或者可串行化也能解决问题,但如果使用未提交读那会是一个倒退,可串行化会造成并发性能的严重下降,所以不采用。

如果对比乐观锁和悲观锁,乐观锁需要代码实现(增加一个版本字段),而悲观锁可以用数据库原生的方式实现。悲观锁实现较为简单,但悲观锁的并发性能不如乐观锁。

悲观锁介绍

悲观的意思是,每次获取数据的时候,都会担心数据被修改,所以每次获取数据的时候都会进行加锁。在当前场景下,悲观锁就是在读取目前的signed时,给这个数据加上行级的排他锁,然后再进行更新。如果在T1还没有提交时,T2(一个同样步骤的事务)想要读取这个数据,因为他想要获得的是一个排它锁,由于T1还未提交,T1持有的排它锁会阻塞T2,T2只能等待T1提交之后才能读这一个数据。

排他锁

在mysql中,排他锁可以这样使用:

SELECT ... FOR UPDATE;

mysql会对查询结果集中每行都添加排它锁,在事务操作中,更新和删除操作会自动加上排他锁

如果数据被加上了排他锁,那么如果查询中无论是请求共享锁或是排他锁,都会因为之前的排他锁而被阻塞,直到之前的排他锁因为事务提交或回滚释放。如果不带任何锁的SELECT语句,无论查询的数据是否有被加锁(包括共享锁和排他锁),都能够进行查询而不被阻塞。具体的表现可以见

行锁和表锁

mysql对表的锁有两种不同的粒度,分别是表锁和行锁。行锁是粒度最小的锁,InnoDB支持行锁和表锁,而MyISAM只支持表锁。这里特意提出来,是因为并不是SELECT ... FOR UPDATE;就是行级锁,只有查询到数据、根据主键和/或对非主键含索引进行查询时才能使用行级锁,其他情况使用的还是表锁。具体原因是,InnoDB行锁是通过对索引的索引项加锁来实现的,这点值得注意。例如这里要实现行锁,就要对这个字段建索引。

实验

讲了那么多,做一个实验验证一下。

首先看一下mysql8.0默认的事务隔离级别。(注意8.0与5.x有分别)

select @@global.transaction_isolation,@@transaction_isolation;

可以看到默认的事务隔离级别是Repeatable Read(可重复读)。

然后新开两个查询连接,这里使用university数据库为例(上课用惯了),事务里进行一个查询和更新操作,如下所示。

BEGIN;

SELECT * FROM instructor WHERE name='Srinivasan' FOR UPDATE;

UPDATE instructor SET salary=65001 WHERE ID=10101;

-- ROLLBACK;

COMMIT;

先在第一个连接里执行前两行,开始一个事务T1,然后进行查询并加锁。注意这里对name这一列进行了索引,所以可以实现行级的锁。可以看到T1能够正常进行查询。

然后用另一个连接也开始一个事务,对这一个数据进行查询。可以看到查询被阻塞了(没有结果返回)。(实际上,等待一段时间(默认50s)后,就会出现当前会话锁等待超时)

回到第一个窗口继续第一个事务的更新操作,这时候第二个事务中的查询和更新操作继续被阻塞。如果第二个事务查询另一行的数据,则不会被阻塞。

当第一个事务提交或者回滚时,锁被释放,第二个事务马上可以进行查询和更新操作。

thinkphp5.0实现

讲到这么久,终于步入正题。这里也是有一点感慨,实现就两行代码,实际考虑的东西、涉及到的内容远远不止这么点。

在tp5的用法中比较简单,只需要在链式查询中加入lock(true)就可以。具体代码如:

Db::name('user')->where('id',1)->lock(true)->find();

//指定使用共享锁

Db::name('user')->where('id',1)->lock('lock in share mode')->find();

在模型中也可用,用法同数据库方法。

坑点

文档中有提到

就会自动在生成的SQL语句最后加上 FOR UPDATE或者FOR UPDATE NOWAIT(Oracle数据库)。

说明实现的方法就是加上FOR UPDATE,但没说明是行锁还是表锁,也没有说明要先开启事务才能使用,如果对数据库不够熟悉(例如一年前的我)看到这里就会一脸懵逼。另外,据闻tp6已经实现了乐观锁(?),同时,tp5也可以通过traits来实现乐观锁。

参考

https://blog.csdn.net/paopaopotter/article/details/79259686 “数据库第一类第二类丢失更新” ↩︎

https://blog.csdn.net/yishizuofei/article/details/79453588 “脏读、丢失更新、不可重复读、幻读” ↩︎

https://zhuanlan.zhihu.com/p/67210493 “Mysql RR级别依然可能丢失更新数据” ↩︎

https://www.cnblogs.com/zhoujinyi/p/3437475.HTML “MySQL 四种事务隔离级的说明” ↩︎

https://blog.csdn.net/Scrat_Kong/article/details/84454519 “为什么有了事务还需要乐观锁和悲观锁?” ↩︎

https://blog.csdn.net/tigernorth/article/details/7948539 “MySQL锁的用法之行级锁” ↩︎

https://blog.csdn.net/She_lock/article/details/82022431 “mysql读锁(共享锁)与写锁(排他锁)” ↩︎

https://www.kancloud.cn/manual/thinkphp5/118086 “ThinkPHP5.0完全开发手册-lock” ↩︎

来源:oschina

链接:https://my.oschina.net/u/4355947/blog/4335664

tp5 mysql悲观锁_thinkphp悲观锁机制处理高并发相关推荐

  1. Redis锁机制处理高并发

    文章正文 这里我们主要利用Redis的setnx的命令来处理高并发. setnx 有两个参数.第一个参数表示键.第二个参数表示值.如果当前键不存在,那么会插入当前键,将第二个参数做为值.返回 1.如果 ...

  2. 分布式锁?我一手synchronized 什么高并发,什么秒杀通通拿下(狗头)

    分布式锁 1.分布式锁 2.传统锁 2.1.经典问题--卖票 2.2.并发导致超卖现象 2.3.JVM锁 2.4.事务与JVM锁 2.5.MySql锁 2.5.1.一个SQL 2.5.2.悲观锁 2. ...

  3. 《Java高并发核心编程.卷2,多线程、锁、JMM、JUC、高并发设计模式》

    <Java高并发核心编程.卷2,多线程.锁.JMM.JUC.高并发设计模式> 目录 第1章 多线程原理与实战 1.2 无处不在的进程和线程 1.2.1 进程的基本原理 1.2.2 线程的基 ...

  4. MySQL锁机制:高并发场景下该如何保证数据读写的安全性?

    锁!这个词汇在编程中出现的次数尤为频繁,几乎主流的编程语言都会具备完善的锁机制,在数据库中也并不例外,为什么呢?这里牵扯到一个关键词:高并发,由于现在的计算机领域几乎都是多核机器,因此再编写单线程的应 ...

  5. 推测的删除锁(Speculative Lock Elision):实现高并发多线程执行

    背景 SLE全称Speculative Lock Elision,我称之为推测的删除锁.这是一篇关于SLE的论文翻译,但是因为本人英语功底很差,所以翻译的不通顺而且会有很多错误的地方.之所以把它发出来 ...

  6. Mysql更新计数器_MySQL实现计数器如何在高并发场景下更新并保持数据正确性

    一张表 两个字段 一个id 一个useCount 表里存了100个id 每个id对应自己的useCount 业务场景是:当id每使用一次 useCount要加1. 当useCount大于1000时 这 ...

  7. thinkphp5 mysql锁机制_thinkphp悲观锁机制处理高并发

    问题分析 突然间被运营滴滴说某个活动的报名人数超过了限制人数,问怎么回事,我一下子还挺蒙的,我明明有在报名的操作之前设置了检查如果超过报名人数代码逻辑会抛错继续报名的呀. 然后我又打开数据库看了一下, ...

  8. 使用Redis和对象锁实现限流。(高并发场景下订购)

    1.订购调用下游的Dubbo请求,为了防止击溃下游的服务,需要在上游设置订购限流.防止下游服务崩溃. 直接上代码: private void sendFluxLimitingCtrl() {Integ ...

  9. GitHub:基于epoll机制的高并发聊天室,c语言实现

    https://github.com/jwzh222/epoll 后期填坑.

  10. Java并发编程(05):悲观锁和乐观锁机制

    本文源码:GitHub·点这里 || GitEE·点这里 一.资源和加锁 1.场景描述 多线程并发访问同一个资源问题,假如线程A获取变量之后修改变量值,线程C在此时也获取变量值并且修改,两个线程同时并 ...

最新文章

  1. php jq实现抽奖,jquery实现抽奖系统
  2. javascript调用父窗口(父页面)的方法
  3. 在WPF中使用WinForm控件方法
  4. python random.seed()函数 (生成固定随机数)random.seed(None)(取消固定随机数种子)
  5. 舒尔特注意力训练表格_星孩注意力总是不集中?这些方法别错过
  6. c#抓取別的網頁的內容
  7. case when条件表达式
  8. vue 限制渲染条数_深入理解Vue 的条件渲染和列表渲染
  9. python制作u盘病毒_十行代码--用Python写一个USB病毒!
  10. 斜线 背景_腊梅花开 摄影 | 斜线加中心构图
  11. Android开发学习笔记---搭建Android开发环境
  12. [原]浅谈几种服务器端模型——多进程并发式
  13. 你掌握垃圾分类大法了吗?图像分类1分钟轻松解决
  14. 知识点收录01---关于Tomcat的一些知识点
  15. pads layout 无法将dxf文件导入进来的2D Line转换成Board Outline
  16. 一分六钱用计算机怎么算,交行信用卡分期付款计算器:5000元分6期手续费
  17. 网络安全未来发展怎么样?
  18. 我说CMMI 2.0 之 配置管理
  19. SSM框架整合环境搭建
  20. 安徽新华学院计算机学院官网,安徽新华学院计算机协会第十八届换届大会

热门文章

  1. 机器学习的应用——关于正确应用机器学习
  2. nssl 1336.膜拜神牛 {LIS}
  3. pr导出视频的每一帧
  4. 【Java】QuickHit游戏
  5. Python实现借助聚合数据API接口生成某一年的节假日对照表
  6. re2正则表达式引擎学习(四)
  7. 分享5个国外较好的图片网站
  8. GAMIT新版本10.71发布了
  9. html 长度太长截断,HTML CSS 表格换行禁止 超出指定长度自动截断
  10. 【Python乘方运算和开平方运算】