V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
UnrealEngine
V2EX  ›  PHP

thinkphp6 加锁查询语句阻塞导致应用缓慢,除了看代码外还能从哪里入手排查和解决?

  •  
  •   UnrealEngine · 327 天前 · 1401 次点击
    这是一个创建于 327 天前的主题,其中的信息可能已经有所发展或是发生改变。

    //扣减余额前查用户余额 $fundModel = new MemberFund(); $fund = $fundModel->field("available_balance") ->where("memberid", $memberId) ->lock(true) ->find();

    看了 php 慢日志是这个加锁的 sql 卡住了

    除了看代码外还有哪些排查和解决手段呢?

    9 条回复    2024-01-10 14:12:29 +08:00
    akin520
        1
    akin520  
       327 天前
    把 SQL 输入,去数据库运行 explain 试试,看看是什么问题
    bixchen
        2
    bixchen  
       327 天前
    把所有的用户余额操作放入异步队列完成,或者用 redis 锁
    kestrelBright
        3
    kestrelBright  
       327 天前
    innodb 的表更新时如果作为条件的字段没有主键或索引,会引发表锁而不是行锁
    rekulas
        4
    rekulas  
       327 天前
    单纯这个查询不大可能导致问题,重点排查该逻辑执行完毕后是否及时释放事务,是否跟其他逻辑有死锁冲突等情况,从数据库日志和事务记录应该能看出来
    UnrealEngine
        5
    UnrealEngine  
    OP
       327 天前
    @kestrelBright #3 主键这块没问题,应该是其他事务执行太久导致应用阻塞了
    UnrealEngine
        6
    UnrealEngine  
    OP
       326 天前
    @bixchen #2 这么操作可以解决根源但是要把整个项目中的余额操作相关代码都要全部改一遍……

    我在想要么直接加个不加 lock 的查余额方法得了

    因为我全局搜索了下整个项目,有的代码查余额时加了 lock 有的没加 lock

    摆烂……
    coderzhangsan
        7
    coderzhangsan  
       326 天前   ❤️ 1
    1.你的这个 SQL 加锁属于悲观锁,即 select {fileds} from {table} where {where} for update; 该锁的事务性操作并发能力不高。

    2.你的 where 条件如果索引是唯一键,即是行锁,如果是普通索引,则是间隙锁(影响范围是个区间),没有锁的行则是表锁,并发能力从高到低:行锁>间隙锁>表锁。

    3.如果是行锁的话,说明你的业务并发值超出悲观锁事务的并发临界值,这个可以查看数据库日志,查询有无死锁或锁超时日志记录,解决方法,个人建议如下:
    a.事务不使用悲观锁处理余额,事务直接使用 update 来完成余额更新,即 update fund set available_balance=available_balance-{money} where memberid = {memberId} and available_balance > available_balance-{money},并发能力要高于悲观锁
    b.比较笨的方法,提升主库配置,例如提升 CPU 核心数,核数可以提高并发临界值。

    参考下 V 站这篇帖子: https://v2ex.com/t/997702
    UnrealEngine
        8
    UnrealEngine  
    OP
       326 天前
    @coderzhangsan #7 我看项目很多地方的余额查询代码即便没有用到事务也在 select 语句后边加上了 for update ,这样会有什么问题?
    coderzhangsan
        9
    coderzhangsan  
       326 天前   ❤️ 1
    @UnrealEngine #8 select for update 是悲观锁 SQL 语句,目的是为查询条件对应的 SQL 加锁,不声明开启事务,mysql 默认 sql 提交方式为隐式提交,对于非 DQL 的 SQL 语句来讲,例如 insert/update\delete 等 DML 或 DDLSQL 语句来讲,事务跟随 SQL 自动提交,也就是不手动声明开启事务,for update 语句提交结束后,锁就释放了,这样做其实对业务来讲没什么意义,for update 一般伴随业务数据更改,保证一致性,所以需要放在一个完整的事务中执行。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2702 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 09:07 · PVG 17:07 · LAX 01:07 · JFK 04:07
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.