V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
MySQL 5.5 Community Server
MySQL 5.6 Community Server
Percona Configuration Wizard
XtraBackup 搭建主从复制
Great Sites on MySQL
Percona
MySQL Performance Blog
Severalnines
推荐管理工具
Sequel Pro
phpMyAdmin
推荐书目
MySQL Cookbook
MySQL 相关项目
MariaDB
Drizzle
参考文档
http://mysql-python.sourceforge.net/MySQLdb.html
kran
V2EX  ›  MySQL

如何在发放优惠码时保证同一个码不会被重复发出多次?

  •  
  •   kran · 2015-03-16 12:15:05 +08:00 · 6478 次点击
    这是一个创建于 3546 天前的主题,其中的信息可能已经有所发展或是发生改变。

    表结构如下:

    `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '编号',
    `coupon` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '优惠券码',
    `type` tinyint(1) unsigned NOT NULL COMMENT '类型',
    `is_use` enum('N','Y') COLLATE utf8_unicode_ci NOT NULL DEFAULT 'N' COMMENT '是否使用',
    `use_date` int(10) unsigned NOT NULL COMMENT '使用时间',
    `is_del` enum('N','Y') COLLATE utf8_unicode_ci NOT NULL DEFAULT 'N' COMMENT '是否删除',
    `creation_date` int(10) unsigned NOT NULL COMMENT '创建时间',
    PRIMARY KEY (`id`),
    KEY `type` (`type`,`is_use`,`is_del`)

    现在使用这样的查询,会锁表:

    begin;
    select * from coupon_list where is_use='N' and type=5 and is_del='N' limit 1 for update;
    

    如何才能触发行锁而不是表锁?

    37 条回复    2015-03-24 23:23:44 +08:00
    abelyao
        1
    abelyao  
       2015-03-16 13:13:22 +08:00
    这是把所有优惠码都先创建好、然后去表里找没有使用的吗?
    换个思路、在用户领取的时候再创建优惠码呢?
    c742435
        2
    c742435  
       2015-03-16 13:14:48 +08:00   ❤️ 1
    不懂数据库。
    但是为什么要先生成优惠码。为什么不在发放的时候向数据库存入优惠码。
    zhicheng
        3
    zhicheng  
       2015-03-16 13:22:57 +08:00 via Android
    用派发终端数做取余。
    loading
        4
    loading  
       2015-03-16 13:26:28 +08:00
    发放时再取优惠码,根据 timestamp 计算 md5 就可以直接用,查找数据库,已有就重新取。
    kran
        5
    kran  
    OP
       2015-03-16 13:55:54 +08:00
    @abelyao
    @c742435

    这是因为客户在自己的系统里生成的优惠码,我们负责发放。自己生成的在客户那边用不了。


    @zhicheng
    不好意思啊,没有看明白,能细说说吗?

    @loading
    没理解错的话是用程序生成而不是在库里取现有的吧?要是这样不满足要求
    hackwjfz
        6
    hackwjfz  
       2015-03-16 14:01:11 +08:00
    加一个flag,发出去了从1变0.不知道这样可否。
    zhicheng
        7
    zhicheng  
       2015-03-16 14:02:08 +08:00 via Android
    比如有两个终端,编号为 0和1。
    那么0号终端永远取,id % 2 == 0 的记录。1 号终端永远取 id % 2 == 1 的记录。依此类推。
    kran
        8
    kran  
    OP
       2015-03-16 14:03:19 +08:00
    @hackwjfz
    表里的`is_use`就相当于一个标志, 但是多个会话同时请求的话还是会得到同一条记录。
    kran
        9
    kran  
    OP
       2015-03-16 14:06:54 +08:00
    @zhicheng
    不太一样,这个是通过web发码,如果对客户端信息做一定的计算,应该是能降低取到同一条记录的概率,但还是会重复。
    zhicheng
        10
    zhicheng  
       2015-03-16 14:16:45 +08:00 via Android
    通过web发码也是要经过服务器的。
    kran
        11
    kran  
    OP
       2015-03-16 14:24:16 +08:00
    @zhicheng
    我的理解是:根据终端号分配优惠码的区段(也就是一个码只会对应到一个终端),条件是终端数目是有限的,web的情况是终端数是无限的。不知道我理解的对不对?
    justlikemaki
        12
    justlikemaki  
       2015-03-16 14:25:17 +08:00
    对于取兑换码的这个过程写个同步可以么?
    zhicheng
        13
    zhicheng  
       2015-03-16 14:28:07 +08:00 via Android
    你要分清楚终端和顾客的区别,顾客是无限的,终端是有限的。比如你准备10台服务器抢优惠码,那这10台服务器就是10个终端。
    kran
        14
    kran  
    OP
       2015-03-16 14:28:55 +08:00
    @justlikemaki
    我没太理解,能细说一下吗?
    同步指的是加队列还是什么?
    fityme
        15
    fityme  
       2015-03-16 14:31:42 +08:00
    我的做法是,给这批优惠码加一个索引存redis,依赖redis做锁,不锁数据库。
    这样做的问题就是多依赖了一个点,然后虽然不会一个优惠码多发,但是异常情况可能会造成活动结束有些码没有真的发出去,细节可以再考虑一下。
    laoyur
        16
    laoyur  
       2015-03-16 14:32:21 +08:00
    数据库那块不太精通
    不过要我来做的话,我会想办法在应用层来处理这个资源竞争的问题,比如用semaphore之类
    happywowwow
        17
    happywowwow  
       2015-03-16 14:32:23 +08:00
    http://haicang.blog.51cto.com/2590303/1085388
    http://www.cnblogs.com/funsion/p/4017779.html
    就上面行锁的需求来说,需要对条件字段加索引
    kran
        18
    kran  
    OP
       2015-03-16 14:33:07 +08:00
    @zhicheng
    嗯。。现在的情况是,一台服务器负责发码,每一个访问网站的人都可以*即时*得到一个优惠码。顾客是直连到服务器的。
    kran
        19
    kran  
    OP
       2015-03-16 14:36:19 +08:00
    @fityme
    这个可行,直接把码放到redis队列里。只是程序已经上线,如果能用现有的工具解决最好了。
    zhicheng
        20
    zhicheng  
       2015-03-16 14:43:25 +08:00 via Android
    那10个进程?
    原来你只有一台服务器,who the fucking care....
    如果你的QPS不到10K,请直接锁库。如果QPS不到100K,请使用队列。我设计的是100K以上的需求。。。
    kran
        21
    kran  
    OP
       2015-03-16 14:44:45 +08:00
    @laoyur
    也就是同步的方法吗? 如果是这样的话,直接表锁也和它差不多了吧?
    kran
        22
    kran  
    OP
       2015-03-16 14:49:55 +08:00
    @happywowwow 文章里说的是,使用相同索引那么就会对索引加锁,跟锁表差不多。

    @zhicheng 对哦!进程数是有个大概范围的。我真是笨死了 T_T
    看来这个场景下之需要所表就ok的。
    能问一下100k以上需要那些处理吗?
    kran
        23
    kran  
    OP
       2015-03-16 14:51:05 +08:00
    @zhicheng 啊。按终端分就是处理方法吗?
    zhicheng
        24
    zhicheng  
       2015-03-16 14:53:55 +08:00 via Android   ❤️ 1
    一个数据库肯定顶不住,在创建优惠码之后可以分布到多个数据库里,同时读写,等一批码发完最后同步一下。这种方式可以任凭你分布到多少服务器上都不会互相影响。
    kran
        25
    kran  
    OP
       2015-03-16 15:00:01 +08:00
    @zhicheng 非常好!感谢ing!
    zhicheng
        26
    zhicheng  
       2015-03-16 15:02:37 +08:00 via Android
    恭喜你掌握了每秒顶住10亿红包系统的秘密。。。
    kran
        27
    kran  
    OP
       2015-03-16 15:13:38 +08:00
    @zhicheng 别吓唬我,我最怕知道秘密了~哈哈
    ayv
        28
    ayv  
       2015-03-16 15:16:27 +08:00
    把优惠码放到一个持久化的队列 例如redis的list
    happywowwow
        29
    happywowwow  
       2015-03-16 15:28:55 +08:00   ❤️ 1
    ”InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!“
    lujjjh
        30
    lujjjh  
       2015-03-16 15:37:35 +08:00   ❤️ 1
    如果一定要用 MySQL,为什么要设计成记录“优惠码是否使用”而不是记录“优惠码拥有者”呢?
    先 SELECT 再 UPDATE 原子性难以得到保证的话,最简单的方案就是先 UPDATE 再 SELECT 嘛。
    julyclyde
        31
    julyclyde  
       2015-03-16 21:13:50 +08:00
    @abelyao 临时生成会比较慢,再加上判断重复、判断重复、判断重复、判断重复的耗时就可能超时了
    @c742435
    abelyao
        32
    abelyao  
       2015-03-16 21:28:23 +08:00
    @julyclyde 可没说临时生成就必须做重复判断哦,可以把 unique id 视作优惠码多一部分,算法有很多种,就看你怎么设计了。当然楼主说优惠码是已经有客户那边生成了,就不再讨论这个方案了。
    lujiajing1126
        33
    lujiajing1126  
       2015-03-16 23:04:21 +08:00
    @lujjjh select的时候加悲观锁
    或者用version_code做乐观锁
    lujiajing1126
        34
    lujiajing1126  
       2015-03-16 23:05:22 +08:00
    话说为啥不试试redis的list。。。
    很多抢票都是这么干的
    donglingyongadls
        35
    donglingyongadls  
       2015-03-17 10:01:58 +08:00
    借鉴bloom filter?没试过,仅供参考
    ly827
        36
    ly827  
       2015-03-17 11:55:56 +08:00
    貌似是新浪游戏的面试题啊~~可以尝试事务处理+for update~保证唯一 看情况决定用不用存储过程
    li24361
        37
    li24361  
       2015-03-24 23:23:44 +08:00
    @zhicheng 腾讯就是利用狂加数据库来顶住的压力,我觉得不只是堆数据库这么简单吧
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1155 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 18:39 · PVG 02:39 · LAX 10:39 · JFK 13:39
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.