【问题标题】:How to update different rows concurrently in Mysql如何在 Mysql 中同时更新不同的行
【发布时间】:2018-12-29 12:59:41
【问题描述】:

我有一个大表,我必须在其中更新几行。我尝试使用多个线程同时更新不同的行,但似乎 MySql 锁定了表中的所有行,而不是只锁定那些与 'where' 子句匹配的行。因此更新不是并发的,有时会导致锁超时错误。

有没有办法同时更新不同的行?

我在后端使用 Django 来执行更新操作。我可以切换到 Postgres,但它会有所帮助还是仍然存在锁定问题?

更新:添加代码片段:

def process_calculate_training(base_dept, exch_dept):
    # First initialize training to None, this is not happening concurrently.
    Emp.objects.filter(exch_dept=exch_dept, base_dept=base_dept).update(training=None)
    # Compute training and store result
    pass


def start_calculating_training():
    thread_func_args = [['MECH', 'COE'], ['MECH', 'ECE'], ['MECH', 'ICE'], ['MECH', 'IT']]
    with ThreadPool(4) as p:
        p.starmap(process_calculate_training, thread_func_args)

更新:执行'show engine innodb status;'

发现 Django 自动将隔离级别设置为“READ-COMMITTED”。因此 MySql 只锁定那些需要更新的行。但是更新并没有同时发生,因为 2 个线程似乎在等待锁定,即使它们必须更新不同的行。

我在并发更新操作期间做了show engine innodb status;。结果如下:

TRANSACTIONS
------------
Trx id counter 2446659
Purge done for trx's n:o < 2446655 undo n:o < 0 state: running but idle
History list length 32
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 421137001004688, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 421137001002848, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 421137001003768, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 2446658, ACTIVE 11 sec fetching rows
mysql tables in use 1, locked 1
LOCK WAIT 4601 lock struct(s), heap size 1024208, 7281 row lock(s), undo log entries 2165
MySQL thread id 427, OS thread handle 139661736408832, query id 651475 localhost root updating
UPDATE `sdk_emp` SET `training` = NULL WHERE (`sdk_emp`.`base_dept` = 'MECH' AND `sdk_emp`.`exch_dept` = 'COE')
------- TRX HAS BEEN WAITING 9 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 345 page no 5238 n bits 240 index PRIMARY of table `empdb`.`sdk_emp` trx id 2446658 lock_mode X locks rec but not gap waiting
Record lock, heap no 135
------------------
---TRANSACTION 2446657, ACTIVE 11 sec fetching rows
mysql tables in use 1, locked 1
13971 lock struct(s), heap size 2351312, 15525 row lock(s), undo log entries 6307
MySQL thread id 424, OS thread handle 139661737813760, query id 651473 localhost root updating
UPDATE `sdk_emp` SET `training` = NULL WHERE (`sdk_emp`.`base_dept` = 'MECH' AND `sdk_emp`.`exch_dept` = 'ECE')
---TRANSACTION 2446656, ACTIVE 11 sec fetching rows
mysql tables in use 1, locked 1
LOCK WAIT 879 lock struct(s), heap size 270544, 1985 row lock(s), undo log entries 411
MySQL thread id 426, OS thread handle 139661736810240, query id 651471 localhost root updating
UPDATE `sdk_emp` SET `training` = NULL WHERE (`sdk_emp`.`base_dept` = 'MECH' AND `sdk_emp`.`exch_dept` = 'ICE')
------- TRX HAS BEEN WAITING 10 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 345 page no 1078 n bits 240 index PRIMARY of table `empdb`.`sdk_emp` trx id 2446656 lock_mode X locks rec but not gap waiting
Record lock, heap no 125
------------------
---TRANSACTION 2446655, ACTIVE 11 sec fetching rows
mysql tables in use 1, locked 1
13984 lock struct(s), heap size 2318544, 15263 row lock(s), undo log entries 6308
MySQL thread id 425, OS thread handle 139661737010944, query id 651469 localhost root updating
UPDATE `sdk_emp` SET `training` = NULL WHERE (`sdk_emp`.`base_dept` = 'MECH' AND `sdk_emp`.`exch_dept` = 'IT')

一段时间后再次运行命令

TRANSACTIONS
------------
Trx id counter 2446659
Purge done for trx's n:o < 2446655 undo n:o < 0 state: running but idle
History list length 32
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 421137001004688, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 421137001002848, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 421137001003768, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 2446658, ACTIVE 31 sec fetching rows
mysql tables in use 1, locked 1
LOCK WAIT 4601 lock struct(s), heap size 1024208, 7281 row lock(s), undo log entries 2165
MySQL thread id 427, OS thread handle 139661736408832, query id 651475 localhost root updating
UPDATE `sdk_emp` SET `training` = NULL WHERE (`sdk_emp`.`base_dept` = 'MECH' AND `sdk_emp`.`exch_dept` = 'COE')
------- TRX HAS BEEN WAITING 29 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 345 page no 5238 n bits 240 index PRIMARY of table `empdb`.`sdk_emp` trx id 2446658 lock_mode X locks rec but not gap waiting
Record lock, heap no 135
------------------
---TRANSACTION 2446657, ACTIVE 31 sec updating or deleting
mysql tables in use 1, locked 1
27466 lock struct(s), heap size 4120784, 25388 row lock(s), undo log entries 12691
MySQL thread id 424, OS thread handle 139661737813760, query id 651473 localhost root updating
UPDATE `sdk_emp` SET `training` = NULL WHERE (`sdk_emp`.`base_dept` = 'MECH' AND `sdk_emp`.`exch_dept` = 'ECE')
---TRANSACTION 2446656, ACTIVE 31 sec fetching rows
mysql tables in use 1, locked 1
LOCK WAIT 879 lock struct(s), heap size 270544, 1985 row lock(s), undo log entries 411
MySQL thread id 426, OS thread handle 139661736810240, query id 651471 localhost root updating
UPDATE `sdk_emp` SET `training` = NULL WHERE (`sdk_emp`.`base_dept` = 'MECH' AND `sdk_emp`.`exch_dept` = 'ICE')
------- TRX HAS BEEN WAITING 30 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 345 page no 1078 n bits 240 index PRIMARY of table `empdb`.`sdk_emp` trx id 2446656 lock_mode X locks rec but not gap waiting
Record lock, heap no 125
------------------
---TRANSACTION 2446655, ACTIVE 31 sec fetching rows
mysql tables in use 1, locked 1
27489 lock struct(s), heap size 4169936, 25817 row lock(s), undo log entries 12692
MySQL thread id 425, OS thread handle 139661737010944, query id 651469 localhost root updating
UPDATE `sdk_emp` SET `training` = NULL WHERE (`sdk_emp`.`base_dept` = 'MECH' AND `sdk_emp`.`exch_dept` = 'IT')

我无法理解为什么 2 个线程无法获得锁,因为它们将更新不同的行?解决方法是什么?

【问题讨论】:

  • 如果您的意图是让所有更新成功或所有更新失败,请使用事务。此外,Django 和 Postgres 也不是替代品。 MySQL 和 Postgres 是备选方案,Django 可以与其中任何一个一起使用。
  • @rd_nielsen 我的意思是问使用 Postgres 代替 MySql 是否可以解决问题。
  • 您可以使用QuerySet.update() 使用来自 django 的单个查询更新多行您能告诉我们您的 python 代码吗?通过在 django 代码中以不同的方式构建查询,很可能可以显着提高性能。还是您使用原始 sql 来执行更新?
  • 为什么要在这里使用多个线程?诸如 mysql 和 postgresql 之类的 RDBMS 已内置查询优化(和并行性),您应该利用它们。您的多线程方法实际上可能会使数据库查询优化器更难完成其工作。如果您可以使用单个数据库查询来做某事,那通常是最高效的解决方案。
  • 除了我的用例之外,我真的很想了解当需要更新的行不同时,为什么进程会卡在锁上?不同行的并发更新是一个非常基本的用例(例如:不同的人同时更新他们在 Fb 中的个人资料)。应该有一些方法可以优雅地做到这一点。

标签: mysql sql django postgresql database-locking


【解决方案1】:
  1. 确保您的表索引良好,并且您的更新查询没有进行全表扫描或全索引扫描。通过这种方式,您的语句可能需要更少的锁。如果可能,通过主键或唯一键进行更新。

对语句执行 EXPLAIN 将帮助您了解查询的效率。

  1. MySQL默认隔离级别是REPEATABLE-READ,如果你使用事务,它会保持它接触到的行的行锁直到事务结束,即使行不匹配。

将隔离级别改为READ-COMMITTED可以解决行锁不匹配会立即释放的问题。

【讨论】:

  • 感谢您的提示。添加索引(在我的情况下无法通过主键更新)并发现 Django 默认将隔离级别设置为 READ-COMMITTED。但问题仍然存在。查看我对问题的更新。
  • 看到这一行:mysql tables in use 1, locked 1. 你已经锁定了表。它通常应该是 0。
  • 你的表的主键是什么?
  • 主键是 django 默认创建的 'id' 字段。如果表被每个进程锁定,那么为什么2个进程能够获得锁定并顺利进行?其中只有 1 个应该能够继续。
  • @AnshulGupta 这就是原因。这两列是否有任何索引:base_dept 和 exch_dept?
猜你喜欢
  • 1970-01-01
  • 2012-05-29
  • 2018-04-08
  • 1970-01-01
  • 2015-10-04
  • 1970-01-01
  • 2022-11-22
  • 2011-10-15
相关资源
最近更新 更多