使用mysql的Hibernate事务

uqzxnwby  于 2023-10-23  发布在  Mysql
关注(0)|答案(2)|浏览(170)

在高负载的服务上,我周期性地看到数据库中的死锁

------------------------
LATEST DETECTED DEADLOCK
------------------------
2023-03-06 22:48:18 0x7fa6a24af700
*** (1) TRANSACTION:
TRANSACTION 209985062, ACTIVE 2 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 1134110, OS thread handle 140353648719616, query id 13926656547 localhost 127.0.0.1 userdb updating
update element set tags_names_string='XXXXX', genres_names_string='XXXX', description='YYYY' where id=370443
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1313 page no 9937 n bits 120 index PRIMARY of table user.element trx id 209985062 lock_mode X locks rec but not gap waiting
*** (2) TRANSACTION:
TRANSACTION 209985053, ACTIVE 3 sec starting index read
mysql tables in use 1, locked 1
551 lock struct(s), heap size 90320, 985 row lock(s), undo log entries 545
MySQL thread id 1134109, OS thread handle 140353664120576, query id 13926657948 localhost 127.0.0.1 userdb updating
update element set last_update='2023-03-06 22:48:15.912' where id=370443
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 1313 page no 9937 n bits 120 index PRIMARY of table user.element trx id 209985053 lock mode S locks rec but not gap
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1313 page no 9937 n bits 120 index PRIMARY of table user.element trx id 209985053 lock_mode X locks rec but not gap waiting
*** WE ROLL BACK TRANSACTION (1)

如果两个连续的更新修改了同一行,就会发生这种情况。每次更新都是通过一个查询处理单个记录,并通过pk进行过滤
神奇的是,我在java代码中有一个表更新的同步。
我一直在努力解决一个问题很长一段时间。由于重试没有丢失的数据发生,但好奇心不允许我离开这个问题未解决。

SpringSefviceClass (singleton):

def synchronized updateElement(ElementMessage msg) {
  Element.withNewTransaction {
    def e = Element.get(e.id)
    e.xxx= msg.xxx
    ... 
    e.save(flush: true)
  }
  sleep(400)
}

添加sleep以确保完成所有DB事务活动。这减少了错误的数量,但无论如何都会发生。使用sleep(10000),我在日志中看不到更多的错误,但这对于服务来说太慢了。
在我的理解中,DB事务必须在结束Java代码中的事务之前完成。在 sleep 行执行之前的代码中。但我看到的错误,它没有完成,即使在睡眠!我敢肯定,一次只有一个线程执行方法(已经通过日志检查)
dynamicUpdate选项的休眠使用(在更新查询只改变了字段提到)。索引列上没有更改。

  • 表包含500 k条记录。
  • 5.7.35-38-log Percona服务器
  • Java 11
  • Spring5.3.25
  • Hibernate 5.6.11.Final
  • 大约40 Gb的内存由DB分配。使用了快速SSD。服务器通常有40%的忙碌,CPU时间为0.1-1%。负载平均值:8.00、9.70、9.36(12个内核)

有什么想法吗?

更新

主要的问题是,为什么在提交事务后,mysql仍然在做一些记录和死锁的事情

2jcobegt

2jcobegt1#

我建议研究以下可能的问题:

  • 事务完成后,您的代码不会关闭数据库连接
  • 不管你是因为什么原因而使用hitting the lock_wait_timeout--你可以降低它而不是引入睡眠超时

一般来说,为了防止死锁而在代码中使用sleep是糟糕的设计。
如果您需要在短时间内对同一条记录执行多个UPDATE,我建议缓存更改并一起执行。

mec1mxoz

mec1mxoz2#

您可以自己管理死锁检测并完全删除sleep(),而不是将死锁检测留给数据库来发现。这是线程安全的。如果可以确保ID是唯一的,则可以通过批量更新来进一步改进它。
大概是这样的:

// Define a concurrent set to store updating IDs
ConcurrentSet<Long> updatingIds = new ConcurrentHashSet<>()
ConcurrentLinkedQueue<ElementMessage> pendingUpdates = new ConcurrentLinkedQueue<>()

...

def updateElement(ElementMessage msg) {
    // Get the ID of the element being updated
    Long elementId = msg.elementId
    
    if (updatingIds.contains(elementId)) {
        // If the elementId is already being updated, enqueue the msg
        pendingUpdates.add(msg)
        return // Return without processing this update further
    }
    
    updatingIds.add(elementId)
    
    Element.withNewTransaction {
        try {
            def e = Element.get(elementId)
            e.xxx = msg.xxx
            // ...
            e.save(flush: true, failOnError: true)
        } catch (Exception e) {
            // Handle exceptions
        } finally {
            updatingIds.remove(elementId)
            
            // Process pending updates
            ElementMessage pendingMsg
            while ((pendingMsg = pendingUpdates.poll()) != null) {
                updateElement(pendingMsg)
            }
        }
    }
}

相关问题