› 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 · Mar 16, 2015 · 7717 views
    This topic created in 4218 days ago, the information mentioned may be changed or developed.

    表结构如下:

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

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


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

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

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