for update秒杀

2023-05-16

Mysql InnoDB 排他锁

用法: select … for update;

例如:select * from goods where id = 1 for update;

排他锁的申请前提:没有线程对该结果集中的任何行数据使用排他锁或共享锁,否则申请会阻塞。

for update仅适用于InnoDB,且必须在事务块(BEGIN/COMMIT)中才能生效。在进行事务操作时,通过“for update”语句,MySQL会对查询结果集中每行数据都添加排他锁,其他线程对该记录的更新与删除操作都会阻塞。排他锁包含行锁、表锁。

1、InnoDB行锁是通过给索引上的索引项加锁来实现的,只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁。

2、由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。应用设计的时候要注意这一点。 
3、当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。 
4、即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。 
5、检索值的数据类型与索引字段不同,虽然MySQL能够进行数据类型转换,但却不会使用索引,从而导致InnoDB使用表锁。通过用explain检查两条SQL的执行计划,我们可以清楚地看到了这一点。


最近发现很多人被类似秒杀这样的设计困扰,其实这类问题可以很方便地解决,先来说说这类问题的关键点是什么:

  1. 一定要高性能,不然还能叫秒杀吗?
  2. 要强一致性,库存只有100个,不能卖出去101个吧?但是库存10000实际只卖了9999是否允许呢?
  3. 既然这里说了是秒杀,那往往还会针对每个用户有购买数量的限制。

总结一下,还是那几个词:高性能强一致性!

下文的所有解决方案是在 Mysql InnoDB 下做的。因为用到了很多数据库特性。其他的数据库或其他的数据库引擎会有不同的表现,请注意。

 

完全不考虑一致性的方案

表结构

+-----------+------------------+------+-----+---------+----------------+
| Field     | Type             | Null | Key | Default | Extra          |
+-----------+------------------+------+-----+---------+----------------+
| id        | int(11) unsigned | NO | PRI | NULL | auto_increment | | user_id | int(11) | NO | | NULL | | | deal_id | int(11) | NO | | NULL | | | buy_count | int(11) | NO | | NULL | | +-----------+------------------+------+-----+---------+----------------+ 

 

方案

表结构很简单,其实就是一个userdeal的关联表。谁买了多少就插入数据呗。

首先,还要检查一下传过来的buy_count是否超过单人购买限制。

接下来,每次插入前执行以下以下操作检查一下是否超卖即可:

select sum(buy_count) from UserDeal where deal_id = ?

最后还要检查一下这个用户是否购买过:

select count(*) from UserDeal where user_id = ? and deal_id = ?

全都没问题了就插入数据:

insert into UserDeal (user_id, deal_id, buy_count) values (?, ?, ?)

 

存在的问题

大家别笑,这样的设计你一定做过,刚毕业的时候谁没设计过这样的系统啊?而且大部分系统对性能和一致性的要求并没有那么高,所以以上的设计方案还真是普遍存在的。

那就说说在什么情况下会出问题吧:

  1. 如果库存只剩一个,两个用户同时点购买,两个人检查全部成功,最后,就超卖了。
  2. 如果一个用户同时发起两次请求,检测部分同样可能会同时通过,最后,数据就异常了。

那就让我们一步步来解决里面存在的问题吧。

 

保证单用户不会重复购买

先来解决最简单的问题,保证单用户不会重复购买。

其实只要利用数据库特性即可,让我们来加一个索引:

alter table UserDeal add unique user_id_deal_id(user_id, deal_id)

加上唯一索引后,不仅查询性能提高了,插入的时候如果重复还会自动报错。

当然别忘了在业务代码中 catch 一下这个异常,并在页面上给用户友好的提醒。

 

解决超卖问题

方案

为了解决这个问题,第一个想到的就是把这几次操作在事务中操作。否则无论怎么改,也都不是原子性的了。

但是加完事务后就完了?

上面的select语句没有使用for update关键字,所以就算加入了事务也不会影响其他人读写。

所以我们只要改一下select语句即可:

select sum(buy_count) from UserDeal where deal_id = ? for update

 

优化

刚改完后发现,问题解决了!so easy!步步高点读机,哪里不会点哪里,so easy!

但是不对啊!为什么两个用户操作不同的deal也会相互影响呢?

原来我们的select语句中的查询条件是where deal_id = ?,你以为只会锁所有满足条件的数据对吧?

但实际上,如果你查询的条件不在索引中,那么 InnoDB 会启用表锁!

那就加一个索引呗:

alter table UserDeal add index ix_deal_id(deal_id)

 

提高性能了

好了,到目前为止,无论用户怎没点,无论多少个人买同一单,都不会出现一致性的问题的。

而且事务都是行锁,如果你的业务场景不是秒杀,操作是分散在各个单子上的。而且你的压力不大,那么优化到这就够了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

for update秒杀 的相关文章

  • 数据库字段的长度 作用

    数据库字段的长度 指的是字节 作用 xff1a 如果不指定长度 xff0c 数据库在存储的时候都必须给每个字段预留最大的存储空间 xff0c 这样极大的浪费了空间 xff0c 也加大了数据库本身管理的难度 字段类型长度应设置为保证正常使用需

随机推荐

  • 飞控常见问题汇总

    1 mavros不能正常通信 失败原因如下 xff1a 用万用表检查Tx xff0c Rx xff0c GND的线是否断了 xff0c 杜邦线很容易线芯断掉 USB转TTL的小板子是否工作正常 xff0c 使用ls l dev grep t
  • PHP面试篇之基础0

    个人气场 技术实力 职业规划及贡献 1 深入理解PHP到底是什么 跨平台 xff08 window Linux Unix xff09 服务器端脚本语言 和apache配合方便 xff0c 性能很高因为PHP可以编译成apache模块 无须编
  • IT人士必备网站

    搜索网站 www 3bsou com www baigoogledu com 1 xff0c 免费动态域名申请 http www 3322 org 希网 http comexe cn 科迈 http www comexe cn http w
  • Mysql数据库数据类型详解

    MySQL的数据类型非常多 xff0c 选择正确的数据类型对于获得高性能至关重要 三大原则 xff1a 1 xff0c 更小的通常更好 xff0c 应该尽量使用可以正确存储数据的最小数据类型 2 xff0c 简单就好 xff0c 简单数据类
  • 符姓大全

    符蓉珊 符海瑛 符海英 符海瑛 符何瑛 符小菲 符睿智 符语嫣 符天翔 符天翔 符芮嘉 符文 符程 符自然 符李颜 符海瑛 符虹清 符天恩 符斌 符运波 符浩 符立 符若兮 符司晨 符尔岚 符若萱 符诗嘉 符思嘉 符斯嘉 符颖欣 符诗嘉 符
  • 什么是socket

    什么是网络套接字 xff08 Socket xff09 xff1f Socket是网络上两个程序双向通讯连接的端点 对于一个Socket而言 xff0c 它至少需要3个参数来指定 xff1a 1 xff09 通信的目的地址 xff1b 2
  • Samba服务安装

    Samba服务 xff08 共享文件 xff09 一 xff0c 安装1 apt get install samba2 apt get install samba common3 xff09 apt get install cifs uti
  • Ubuntu14配置nginx虚拟主机

    一 建立项目目录 在 home share 目录下创建一个test项目目录 xff0c 并编辑一个子目录 index php 文件 sudo mkdir p home share test 新建一个主页 index php文件 sudo v
  • 记忆方法

    数字编码联系法 xff1a 就是把数字 翻译 成编程 xff0c 然后用联想的方式把这些编程串连起来 xff0c 这就叫数字编码联系法 这种方法适用于临时记忆一些多位数字或数量不太大 xff0c 又不需长期保持记忆的数字 另外 xff0c
  • PHP 使用 file_get_contents 接收 POST 的資料

    一般接收 POST 资料都是使用 POST 这个变量 xff0c 但 POST 只能取得 Content type 為 application x www form urlencoded 或 multipart form data 的資料
  • 浮点数转成整型intval

    n 61 34 19 99 34 var dump intval n 100 int 1998 var dump strval n 100 string 4 34 1999 34 var dump intval strval n 100 i
  • 《硬件合集》Nvidia NX & Pixracer接线图 &电调调参 BLheliSuite

    Nvidia NX 1 针脚定义 2 电源接口关系图 3 使用技巧 值得一提的是 NX的针脚中ttyTHS0可以用作mavros的通信端口 xff0c 正好的Tx Rx串口通信 只需要交叉接线即可 xff0c 并每次运行时为该端口赋予sud
  • self :: 和 this-> 的用法

    在访问PHP类中的成员变量或方法时 xff0c 如果被引用的变量或者方法被声明成const xff08 定义常量 xff09 或者static xff08 声明静态 xff09 那么就必须使用操作符 反之如果被引用的变量或者方法没有被声明成
  • 程序是怎样运行的

    一 CPU的内部结构解析 1 程序运行流程 程序员用C语言等高级语言编写程序 int a a 61 1 43 2 printf 34 d 34 a 将程序编译后转换成机器语言的EXE文件 01000101000010111 00110100
  • 人性的弱点 --卡耐基

    自序 成就此书的因缘 和人类所具备的潜能相比 xff0c 我们仍处于蒙昧之中 人类的身心只有极小部分得到了发挥 广义而言 xff0c 人类个体远未到达极限 人类囿于自身习惯 xff0c 从未将与生俱来的诸多能力发挥至极致 本书 xff0c
  • git-flow分支模型

    分支模型 xff1a 用 git flow 初始化工程目录完成后 xff0c 只能看到两个分支 xff08 长期分支 xff09 xff1a master 分支 xff1a 用于上线的分支 xff0c 保护性分支 xff0c 只包含经过测试
  • MySQL show关键字用法

    SHOW DATABASES 列出 MySQL Server 上的数据库 SHOW TABLES FROM db name 列出数据库中的表 SHOW TABLE STATUS FROM db name 列出数据库的表信息 xff0c 比较
  • windows10共享文件夹挂载到Ubuntu

    程序开发人员一般都会把开发目录放在windows系统下 xff0c 开发环境却是linux 以前我是linux下文件挂载到windows xff0c 有同事前车之鉴 xff0c 万一虚拟机linux挂壁了 xff0c 很难恢复 现在准备把w
  • 闭包函数中use使用

    匿名函数中的use xff0c 其作用就是从父作用域继承变量 下例是最常见的用法 xff0c 如果不使用use xff0c 函数中将找不到变量 msg 1 2 3 4 5 6 7 8 lt php msg 61 1 2 3 func
  • for update秒杀

    Mysql InnoDB 排他锁 用法 xff1a select for update 例如 xff1a select from goods where id 61 1 for update 排他锁的申请前提 xff1a 没有线程对该结果集