You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Dropwizard+JDBI3操作MySQL批量更新用户设置时的事务死锁问题求助

Dropwizard+JDBI3操作MySQL批量更新用户设置时的事务死锁问题求助

我现在在用Dropwizard搭配JDBI3来和MySQL数据库交互。为了减少流量、让API更好用,我做了一个接口,它接收一个设置Map,然后在事务里把数据库里的用户设置更新成对应的样子。但问题是,当这个接口被同时调用的时候,有时候第一个请求会先查询并锁定当前设置,然后开始更新,但会被第二个请求持有的等待锁给阻塞,最后就出现死锁了。

DAO代码

interface MysqlUserSettingsStore {

    @Transaction
    fun replace(id: Int, settings: Map<String, Any?>) {
        val existing = listLocked(id)

        // Update all settings
        for ((key, value) in settings) {
            if (existing[key] == value) {
                // Skip unchanged setting
                continue
            }

            replace(id, key, value)
        }

        // Remove existing settings not in the new settings
        for ((key, _) in existing) {
            if (settings[key] == null) {
                delete(id, key)
            }
        }
    }

    @SqlQuery("""
        SELECT *
        FROM user_settings
        WHERE id = :id
        FOR UPDATE
    """)
    @KeyColumn("key")
    @ValueColumn("value")
    @RegisterColumnMapper(SettingValueMapper::class)
    fun listLocked(
        @Bind("id")
        id: Int,
    ): Map<String, Any?>

    @SqlUpdate("""
        INSERT INTO user_settings
        VALUES (:id, :key, :value)
        ON DUPLICATE KEY UPDATE value = :value
    """)
    @RegisterArgumentFactory(ReplaceBooleanArgumentHandler::class)
    fun replace(
        @Bind("id")
        id: Int,
        @Bind("key")
        key: String,
        @Bind("value")
        value: Any?,
    )

    @SqlUpdate("""
        DELETE FROM user_settings
        WHERE id = :id 
          AND `key` = :key
    """)
    fun delete(
        @Bind("id")
        id: Int,
        @Bind("key")
        key: String,
    )

}

数据库配置

database:
  driverClass: 'com.mysql.cj.jdbc.Driver'
  user: 'root'
  password: ''
  url: 'jdbc:mysql://localhost:3306/<database>'
  maxSize: 80
  defaultTransactionIsolation: 'repeatable-read'

SHOW ENGINE INNODB STATUS 输出

------------------------
LATEST DETECTED DEADLOCK
------------------------
2024-11-05 15:59:36 140331471333120
*** (1) TRANSACTION:
TRANSACTION 41960, ACTIVE 0 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1128, 1 row lock(s)
MySQL thread id 2382, OS thread handle 140331686369024, query id 246406 172.17.0.1 root executing
/* MysqlUserSettingsStore.listLocked */ 
        SELECT *
        FROM user_settings
        WHERE id = 300
        FOR UPDATE

*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 160 page no 7 n bits 216 index PRIMARY of table `propertree`.`user_settings` trx id 41960 lock_mode X waiting
Record lock, heap no 145 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 4; hex 8000012c; asc    ,;;
 1: len 4; hex 6b657932; asc key2;;
 2: len 6; hex 00000000a3e3; asc       ;;
 3: len 7; hex 82000000c10110; asc        ;;
 4: len 6; hex 76616c756532; asc value2;;


*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 160 page no 7 n bits 216 index PRIMARY of table `propertree`.`user_settings` trx id 41960 lock_mode X waiting
Record lock, heap no 145 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 4; hex 8000012c; asc    ,;;
 1: len 4; hex 6b657932; asc key2;;
 2: len 6; hex 00000000a3e3; asc       ;;
 3: len 7; hex 82000000c10110; asc        ;;
 4: len 6; hex 76616c756532; asc value2;;


*** (2) TRANSACTION:
TRANSACTION 41959, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1128, 4 row lock(s)
MySQL thread id 2381, OS thread handle 140327490713344, query id 246414 172.17.0.1 root update
/* MysqlUserSettingsStore.replace */ 
        INSERT INTO user_settings
        VALUES (300, 'key1', 'value1')
        ON DUPLICATE KEY UPDATE value = 'value1'

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 160 page no 7 n bits 216 index PRIMARY of table `propertree`.`user_settings` trx id 41959 lock_mode X
Record lock, heap no 145 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 4; hex 8000012c; asc    ,;;
 1: len 4; hex 6b657932; asc key2;;
 2: len 6; hex 00000000a3e3; asc       ;;
 3: len 7; hex 82000000c10110; asc        ;;
 4: len 6; hex 76616c756532; asc value2;;

Record lock, heap no 148 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 4; hex 8000012c; asc    ,;;
 1: len 7; hex 746573744b6579; asc testKey;;
 2: len 6; hex 00000000a363; asc      c;;
 3: len 7; hex 81000001540122; asc     T ";;
 4: len 9; hex 7465737456616c7565; asc testValue;;


*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 160 page no 7 n bits 216 index PRIMARY of table `propertree`.`user_settings` trx id 41959 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 145 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 4; hex 8000012c; asc    ,;;
 1: len 4; hex 6b657932; asc key2;;
 2: len 6; hex 00000000a3e3; asc       ;;
 3: len 7; hex 82000000c10110; asc        ;;
 4: len 6; hex 76616c756532; asc value2;;

*** WE ROLL BACK TRANSACTION (1)

只要3个并发请求就可能触发死锁,如果用更宽松的锁机制,需要更多请求才会出现。我试过用无锁查询,把事务隔离级别改成read-committed,这样死锁概率低很多,但会出现不符合预期的行为。我也试过直接用控制台执行查询来复现,但没成功,估计是需要刚好的时机。还试过不用@Transaction注解,手动调用START TRANSACTION和COMMIT,结果和用注解的情况差不多。

也许这种接口本来就不该被同时调用同一个id,但如果能有办法避免死锁就更好了,求助各位大佬!

备注:内容来源于stack exchange,提问作者Terkel Laursen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.16 02:58:03