【问题标题】:Why mysql deadlock happened for select for update and insert?为什么选择更新和插入会发生mysql死锁?
【发布时间】:2022-01-18 02:26:32
【问题描述】:

我用peewee和mysql写了一个简单的锁类。获取方法有时会抛出死锁异常。

这里是innodb状态监控和代码的输出。

  1. 似乎第一个事务正在等待锁定并且什么都不持有。那么,如果它没有持有任何需要的事务二,为什么会发生死锁呢?

  2. 似乎 select for update 和 insert 语句会相互锁定。但我还是不明白为什么。你能帮忙说明一下吗?

------------------------
LATEST DETECTED DEADLOCK
------------------------
2022-01-18 13:40:37 0x7f565a5bc700
*** (1) TRANSACTION:
TRANSACTION 4137600947, ACTIVE 0 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
SELECT `t1`.`id`, `t1`.`business_type`, `t1`.`resource`, `t1`.`owner`, `t1`.`timeout`, `t1`.`comment`, `t1`.`gmt_created`, `t1`.`gmt_modified` FROM `resource_((`t1`.`resource` = 'r') AND (`t1`.`business_type` = 't')) AND ((`t1`.`owner` = '119909') OR ((`t1`.`timeout` != 0) AND ((to_seconds('2022-01-18 13:40:37.3448`.`gmt_modified`)) > `t1`.`timeout`)))) LIMIT 1 OFFSET 0 FOR UPDATE
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 255733 page no 5 n bits 72 index lock_business_type_resource of table `database_name`.`resource_lock` trx id 4137600947 lock_mode X lociting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 1; hex 74; asc t;;
 1: len 1; hex 72; asc r;;
 2: len 4; hex 800036f5; asc   6 ;;

*** (2) TRANSACTION:
TRANSACTION 4137600943, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1136, 3 row lock(s), undo log entries 1
INSERT INTO `resource_lock` (`business_type`, `resource`, `owner`, `timeout`) VALUES ('t', 'r', '120048', 1)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 255733 page no 5 n bits 72 index lock_business_type_resource of table `database_name`.`resource_lock` trx id 4137600943 lock_mode X loc
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 1; hex 74; asc t;;
 1: len 1; hex 72; asc r;;
 2: len 4; hex 800036f5; asc   6 ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 255733 page no 5 n bits 72 index lock_business_type_resource of table `database_name`.`resource_lock` trx id 4137600943 lock mode S wai
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 1; hex 74; asc t;;
 1: len 1; hex 72; asc r;;
 2: len 4; hex 800036f5; asc   6 ;;

*** WE ROLL BACK TRANSACTION (1)
# coding=utf-8
import datetime
import logging
import peewee
from peewee import fn
from peewee import Model, CharField, IntegerField, DateTimeField, SQL
from peewee import MySQLDatabase


db_info = {}  # mysql database address port user and password
database = MySQLDatabase(db_info)  # db connection


class Lock(Model):
    # below two row have unique index 

    business_type = CharField(null=False)
    resource = CharField(null=False)
    owner = CharField(null=False)
    timeout = IntegerField(null=False, default=0)
    comment = CharField(null=True)
    gmt_created = DateTimeField(constraints=[SQL("DEFAULT CURRENT_TIMESTAMP")])
    gmt_modified = DateTimeField(constraints=[SQL("DEFAULT CURRENT_TIMESTAMP")])

    class Meta:
        indexes = (
            (('business_type', 'resource'), True),
        )
        table_name = 'resource_lock'
        database = database



def acquire(owner, timeout):

    with database.atomic():

        now = datetime.datetime.now()
        try:
            # two case that the lock can be selected
            # 1. current process is the owner
            # 2. lock timeout
            # but the select statement here may hit deadlock occasionally.
            # db deadlock should meas two sessions holding a row/lock
            # and waiting for each other. but here we have
            # unique constraint on resource and business_type. Only one row should be involved
            lock = Lock.select().where(
                Lock.resource == 'r',
                Lock.business_type == 't',
                ((Lock.owner == owner) | (Lock.timeout != 0) & (fn.to_seconds(now) - fn.to_seconds(Lock.gmt_modified) > Lock.timeout))
            ).for_update().get()
            if str(lock.owner) == (owner):
                print 'select success {} {}'.format(owner, time.time())
            else:
                print 'select success timeout, lock_owner: {}, owner: {} {}'.format(lock.owner, owner, time.time())
            lock.timeout = timeout
            lock.gmt_created = now
            lock.gmt_modified = now
            lock.owner = owner
            lock.save()
            print 'select success {}'.format(owner)
            return True
        except Lock.DoesNotExist:
            pass

        # if lock not exist "Lock.DoesNotExist" multiple processes can create the lock at same time. Only one
        # can succeeded. Others hit peewee.IntegrityError and acquire fail because of unique index
        try:
            Lock.create(business_type='t', resource='r', owner=owner, timeout=timeout)
            print 'create success {} {}'.format(owner, time.time())
            return True
        except peewee.IntegrityError:
            print 'create fail {} {}'.format(owner, time.time())
            return False


if __name__ == '__main__':
    while True:
        time.sleep(random.choice([0.1, 0.2, 0.3, 0.4, 0.5]))
        acquire(owner=os.getpid(), timeout=1)

【问题讨论】:

  • 您需要检查innodb状态监视器的输出以了解导致死锁的原因
  • to_seconds('2022-01-18 13:40:37.3448.gmt_modified`)中有多个错误--请检查源。 (注意撇号与反引号;时间点列)。如果你能得到实际的查询,请这样做。

标签: python mysql peewee database-deadlocks


【解决方案1】:

如果该查询中确实有 OR,我建议您执行以下操作之一:

  • 摆脱OR 并使用UNION 构建等效查询,然后找出最佳索引。

  • 将查询分成两个SELECTs,然后为每个建立一个单独的事务。

【讨论】:

    猜你喜欢
    • 2017-10-05
    • 2019-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-19
    • 1970-01-01
    • 1970-01-01
    • 2015-10-28
    相关资源
    最近更新 更多