mysql insert 并发问题
怪我咯
怪我咯 2017-04-17 13:15:49
[MySQL讨论组]

问题:我有两张表,一张是优惠券活动表,另一个是用户领取优惠券的明细表。如下

CREATE TABLE `coupon_activity` (
  `act_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `act_code` char(6) NOT NULL DEFAULT '' COMMENT '活动编码',
  `coup_issue_num` int(11) NOT NULL DEFAULT '0' COMMENT '优惠券发行量',
  `coup_per_num` int(11) NOT NULL DEFAULT '0' COMMENT '单个用户可领取数',
  PRIMARY KEY (`act_id`),
  UNIQUE KEY `act_code_idx` (`act_code`) COMMENT '活动编码唯一索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='优惠券活动表';
CREATE TABLE `coupon_detail` (
  `coup_id` int(11) NOT NULL AUTO_INCREMENT,
  `act_code` char(6) NOT NULL DEFAULT '' COMMENT '活动编号',
  `coup_code` char(6) NOT NULL DEFAULT '' COMMENT '优惠券编码',
  `coup_user_id` int(11) NOT NULL DEFAULT '0' COMMENT '领取券用户id',
  PRIMARY KEY (`coup_id`),
  UNIQUE KEY `coup_code_idx` (`coup_code`) USING BTREE COMMENT '优惠券编码唯一索引',
  KEY `coup_user_idx` (`coup_user_id`) USING BTREE COMMENT '用户id普通索引',
  KEY `act_code_idx` (`act_code`) USING BTREE COMMENT '活动编码普通索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='优惠券明细表';

假如现在有一个编码为act_code = '000000'的优惠券活动,限定的没每个人只能领一张券,现在用户id=10的用户来领取该活动的券,在不考虑并发时的代码如下(#{}里面的字段表示前面的sql语句查出来的值):

begin;
select * from coupon_activity where act_code = '000000';
//当前10号用户领取该活动优惠券的数量
select count(coup_id) as count_per from coupon_detail where coup_user_id = 10 and act_code = #{act_code};
//插入明细表 当前用户领取量是否小于每个用户可领取数
if(#{count_per} < #{coup_per_num}){
    insert into coupon_detail values(1,act_code,'000000',10);
}
commit;

那么如果有两个事务进来的话,就会出现这种情况,两个事务读取的数据都是0,那么if语句就能通过,然后两个事务都成功insert,这样这个用户就多领取了一张优惠券。

备注:

  1. 备注用悲观锁的话,会发生死锁。

  2. 乐观锁的话,因为是insert语句,不是单单一条update语句,无法做到操作的时候进行校验。

  3. 只能领取一张券的话,可以使用联合唯一索引解决,但是业务上还会有可领取2张的,甚至更多

  4. 望各位mysql大大帮忙解决下,或者说还有其他更好的解决并发的方法,这个问题纠结好久了。

怪我咯
怪我咯

走同样的路,发现不同的人生

全部回复(3)
巴扎黑

可以参考我之前的这个提问。【投票大量请求处理问题】原文链接:http://segmentfault.com/q/1010000002629272
可以使用redis记录用户的领取数量,每次从redis中判断领取数,这样就不用锁表了,会提升很大的性能。

伊谢尔伦

提供一个思路供参考, 写一条复杂的insert语句如下:

insert into coupon_detail 
  select 1, act_code, '000000',10 from
    (select count(coup_id) as count_per 
      from coupon_detail 
      where coup_user_id = 10 and act_code = #{act_code}
    ) TMP
  where TMP.count_per < #{coup_per_num};
  

这样每次insert前 会去检查 先决条件. 没有具体去试, 但原则上我觉得应该可以解决你的问题.

怪我咯

基于楼主备注的第三条

只能领取一张券的话,可以使用联合唯一索引解决,但是业务上还会有可领取2张的,甚至更多

有一个建议:
还是使用联合唯一索引,如果楼主要考虑其他活动可能存在领取多张的情况,增加一个字段,该活动该用户领取的序号,假设此字段为 num int unsigned。
对于此类只能领取一次的活动,业务代码上是num总是为1;
对于其他可以领取多次的活动,也是在业务层代码上对num做分配与限制。

热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号