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
Features
V2EX  ›  MySQL

关于 mysql 并发的问题又来了

  •  
  •   Features · 2022-04-03 18:54:19 +08:00 · 3407 次点击
    这是一个创建于 725 天前的主题,其中的信息可能已经有所发展或是发生改变。
    要扣除用户余额 100 ,以前大学学过一点 java EE,正常就是
    1.SELECT `余额` FROM user;
    2.余额 >= 100 则 UPDATE
    3.否则提示用户余额不足

    现在请求并发太高,查询的余额还没来得及更新,导致超扣了

    我目前的解决办法:
    1.把扣除余额请求放到自己实现的 redis 队列里
    2.使用一个 thread 定时轮询队列,多 thread 我怕又出现并发问题,0.5s 轮询一次,处理过程中停止轮询
    保证只有一个线程在处理数据
    3.客户端开个 socket 连接,做一些加载动画,等待跳转的动作,等 socket 更新数据

    我感觉这样就很不优雅
    有什么能在 mysql 层面解决这个问题的吗?
    28 条回复    2022-04-05 01:55:56 +08:00
    Aoang
        1
    Aoang  
       2022-04-03 19:00:17 +08:00 via iPhone
    事务呢?有事务不用,自己造个三角形的轮子
    Jooooooooo
        2
    Jooooooooo  
       2022-04-03 19:01:20 +08:00
    先用 redis 拦一遍是可以的

    最终在 update 里加个 where 余额>100 的条件啊
    Features
        3
    Features  
    OP
       2022-04-03 19:06:15 +08:00
    @Jooooooooo 哦哦,对,这样应该是可以的
    Features
        4
    Features  
    OP
       2022-04-03 19:18:51 +08:00
    实测是我太弱智了
    应该先 update where 余额 >= 100 ,这个语句不成功就 trhow Exception 就好了
    成功则添加记录等动作
    javapythongo
        5
    javapythongo  
       2022-04-03 19:21:45 +08:00
    行锁、乐观锁吧
    broadliyn
        6
    broadliyn  
       2022-04-03 19:41:28 +08:00
    update user set balance=balance-100 where id=1 and balance>=100;
    xuanbg
        7
    xuanbg  
       2022-04-03 19:57:11 +08:00
    6 楼正解
    kingjpa
        8
    kingjpa  
       2022-04-03 20:42:42 +08:00
    我一般是 ,先锁,开启事务 然后更新, 后提交事务, 测过并发,没问题的。
    其实 redis 也可以,但多一个服务就多一个不确定性。
    sunnyandpenta
        9
    sunnyandpenta  
       2022-04-03 20:49:07 +08:00
    start transaction
    1.SELECT balance FROM user where user_id = #{userId} for update;
    2. int affectRows = update user set balance=balance-100 where user_id = #{userId} and balance>=100;
    3.
    if(affectRows > 0){
    commit;
    }else{
    rollback;
    }
    stonewu
        10
    stonewu  
       2022-04-03 21:21:02 +08:00
    update user set balance = balance - 你要扣除的金额 where balance - 你要扣除的金额 >= 0 and id=用户 ID;
    jasonkayzk
        11
    jasonkayzk  
       2022-04-03 21:25:43 +08:00
    这么经典的问题,用乐观锁不就解决了:

    update user set balance=balance-? where id=? and balance>=?;
    documentzhangx66
        12
    documentzhangx66  
       2022-04-03 21:33:35 +08:00
    当你还不懂使用事务的情况下,这种涉及到金钱的操作,还是建议使用 Oracle 。

    一来 Oracle 稳,第二是 Oracle 这种扣钱的案例,能搜到很多例子...
    iseki
        13
    iseki  
       2022-04-03 21:43:41 +08:00
    如果你对事务不太了解,建议切换至 Serializable 隔离等级
    kekxv
        14
    kekxv  
       2022-04-03 21:44:16 +08:00 via iPhone
    时间有要求?没有的话有个最简单的,直接用 redis 锁这个 ID 就行
    iseki
        15
    iseki  
       2022-04-03 21:44:31 +08:00
    哦,如果完全不会用事务,那啥隔离等级也没用···这倒是忘了
    liangkang1436
        16
    liangkang1436  
       2022-04-03 21:49:35 +08:00 via Android
    你应该用事务和锁来实现,而不是用尽办法保证只有一个线程在更新数据库,因为数据库不是你一个人在用,别人也在可能操作这个表
    512357301
        17
    512357301  
       2022-04-03 21:50:53 +08:00 via Android
    6 楼正解。
    还有就是楼主很明显在用代码的栈或者数组思维搞数据库,却忘了数据库最擅长增删改查了,尤其是 MySQL
    liangkang1436
        18
    liangkang1436  
       2022-04-03 21:51:25 +08:00 via Android
    我记得 MySQL 默认的事务级别就是可重复读
    Features
        19
    Features  
    OP
       2022-04-03 22:02:02 +08:00
    @liangkang1436 确实,是我刚学这个,遇到的问题太初级了
    Features
        20
    Features  
    OP
       2022-04-03 22:05:12 +08:00
    @Aoang
    @iseki
    @liangkang1436

    请教下,这种高并发的情况下,和事务有关系吗?
    前一个请求中有未执行完成的事务,后面的 N 个请求又启用了 N 个事务
    还是说 Mysql 的机制中只允许存在一个事务?
    liangkang1436
        21
    liangkang1436  
       2022-04-03 22:09:24 +08:00 via Android
    @Features 你先去谷歌一下事务的基础知识,比如什么是 acid ,然后再去查一下 MySQL 的事务相关的配置和 SQL 写法,然后你就知道怎么走了
    CEBBCAT
        22
    CEBBCAT  
       2022-04-04 01:39:34 +08:00
    你的学习方式是不是有问题?上次的问题 /t/844048 就已经在 MySQL 工作原理上大错特错了,这个帖子又是一样地,只讲问题,只求解决,不讲原理

    强烈建议你找一本正儿八经的,偏实战方向的书学习一下。等下我可以帮你找一些

    很抱歉,最近精力不足,不能更 friendly 地回复你的帖子。

    如果你有问题,请先去 Google 过再来问!请不要把 BBS 当成可以随便发言的 IM 聊天软件。请阅读《提问的智慧》。

    根据我的视角,你总是拿一些初学者教程就会教授的东西来问,这不必要
    CEBBCAT
        23
    CEBBCAT  
       2022-04-04 01:56:56 +08:00
    @CEBBCAT 你可以找找看《疯狂 Java 讲义》,我没有读过它,但我看到他的目录里面涉及了 MySQL 以及对应的事务部分。另外对于初学者来说通过培训班的视频学习也是一种方式,而且也是更为直观的方式。

    假如你能耐得下心,以及有时间,去按照传统的计算机原理、操作系统、数据结构去学习,那再好不过了
    Evilk
        24
    Evilk  
       2022-04-04 15:17:24 +08:00 via iPhone
    如果是 mysql
    先开启事务
    根据主键 ID 锁住这行数据
    再进行相关操作
    最后再提交事务

    我最近正好在做这种需求
    测试过并发
    没有出现超扣的情况

    我之前也像你一样,从其他方面来做
    却忘了数据库本身就很擅长这种业务
    gjquoiai
        25
    gjquoiai  
       2022-04-04 17:22:22 +08:00
    @CEBBCAT #22 感谢指路,看了看这两个帖子,低血压已经治好了
    huangzhe8263
        26
    huangzhe8263  
       2022-04-04 18:42:06 +08:00
    这种入门级的问题,还是好好补一下基础好。
    现在还是能发现 bug 的,万一往里头埋了啥大漏洞进去
    那就不是,来论坛问,能解决的了
    iseki
        27
    iseki  
       2022-04-04 23:08:25 +08:00   ❤️ 1
    @Features 事务虽然会并发执行,但会按照设定的隔离等级,利用锁和多版本记录等机制保证正确处理你面对的这种问题,最严格的 Serializable (可序列化)隔离等级理论上会保证执行结果和看起来一个一个非并发的执行完全一致(当然只是理论上,实际中不同的数据库在一些细节上还是会有些不同的偏差,需要查阅数据库文档,不过感觉你现在不需要关心这些)。
    具体比如如下这个例子:

    ```sql
    select 账户 ID, 余额 from table_name where 余额 >= 100;
    update table_name set 余额 = xxxxx where 账户 ID = xxx;
    ```

    在( PostgreSQL 数据库中)隔离等级为 RR 或 SI 时,慢一步的会话可能会在 update 时报错,事务 rollback 。MySQL 在 RR 下不会报错失败(Lost update) 但在 SI 下也会不能提交。

    (所以 PostgreSQL 好!:)
    veightz
        28
    veightz  
       2022-04-05 01:55:56 +08:00 via iPhone
    一般来说,改余额还要配合流水表的,直接一张余额表该值,后面会很痛苦….
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1001 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 22:12 · PVG 06:12 · LAX 15:12 · JFK 18:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.