MySQL排他锁(FOR UPDATE)正在锁定整个表[重复]
Posted
技术标签:
【中文标题】MySQL排他锁(FOR UPDATE)正在锁定整个表[重复]【英文标题】:MySQL exclusive lock (FOR UPDATE) is locking the whole table [duplicate] 【发布时间】:2018-03-06 14:10:27 【问题描述】:我有一个 mysql 表(使用 InnoDB 作为存储引擎)来存储用户事务。
CREATE TABLE `transactions` (
`id` int(11) NOT NULL,
`correlation_id` char(36) NOT NULL,
`user_id` char(36) NOT NULL,
`currency` char(3) NOT NULL,
`time_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`transaction_amount` double NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `transactions`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `correlation_id_unique` (`correlation_id`),
ADD INDEX (`user_id`);
我在多线程环境中工作并希望确保:
没有两个线程可以同时为同一个用户插入事务 如果线程正在为用户插入事务,则没有其他线程可以读取该用户的事务我想出了以下解决方案:
当线程要为用户插入事务时,获取该用户对应行的排他锁
BEGIN;
-- Acquire an exclusive lock on the rows with user_id=1
SELECT * FROM transactions WHERE user_id = 1 FOR UPDATE;
-- Insert transactions
...
COMMIT;
当一个线程想要读取一个用户余额时(通常是对一个用户的所有事务求和),它首先为这个用户对应的行获取一个共享锁
SELECT SUM(transaction_amount)
FROM transactions
WHERE user_id=1
LOCK IN SHARE MODE;
但是,排他锁似乎锁定了整个表,而不仅仅是 SELECT...FOR UPDATE 语句返回的行。这是一个例子。
线程 1:
mysql> select user_id, transaction_amount from transactions;
+---------+--------------------+
| user_id | transaction_amount |
+---------+--------------------+
| 1 | 10 |
| 1 | -2 |
| 2 | 5 |
| 2 | 10 |
+---------+--------------------+
4 rows in set (0.00 sec)
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM transactions WHERE user_id = 1 FOR UPDATE;
+----+----------------+---------+----------+---------------------+--------------------+
| id | correlation_id | user_id | currency | time_created | transaction_amount |
+----+----------------+---------+----------+---------------------+--------------------+
| 1 | 1 | 1 | CHF | 2018-03-06 09:54:28 | 10 |
| 2 | 2 | 1 | CHF | 2018-03-06 09:54:28 | -2 |
+----+----------------+---------+----------+---------------------+--------------------+
2 rows in set (0.01 sec)
线程 2:
-- Retrieve transactions of user 2
mysql> SELECT * FROM transactions WHERE user_id = 2 LOCK IN SHARE MODE;
[[Hangs]]
在阅读MySQL's documentation 之后,我预计这会起作用:
选择...锁定共享模式
在读取的任何行上设置共享模式锁。 其他会话可以读取行,但在您的事务提交之前不能修改它们
选择 ... 更新
对于搜索遇到的索引记录,锁定行和任何关联的索引条目,就像您为这些行发出 UPDATE 语句一样。其他事务被阻止更新那些行、执行 SELECT ... LOCK IN SHARE MODE 或读取某些事务隔离级别的数据。
现在,我找到了this topic,说明在我的情况下,user_id
字段应该有一个索引 - 确实如此。
我感觉问题可能是由于请求SELECT * FROM transactions WHERE user_id=1
没有使用索引引起的:
EXPLAIN SELECT * FROM transactions WHERE user_id=1 FOR UPDATE;
+----+-------------+--------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | transactions | NULL | ALL | user_id | NULL | NULL | NULL | 2 | 50.00 | Using where |
+----+-------------+--------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 3 warnings (0.00 sec)
有什么想法吗?
【问题讨论】:
【参考方案1】:我使用 MySQL 5.6.31 测试了您的表,并在其中填充了 50 万行介于 1 和 1000 之间的随机值。
即使强制索引也无济于事:
mysql> EXPLAIN SELECT * FROM `transactions` force index (user_id) where user_id=1;
+----+-------------+--------------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+------+---------------+------+---------+------+--------+-------------+
| 1 | SIMPLE | transactions | ALL | user_id | NULL | NULL | NULL | 520674 | Using where |
+----+-------------+--------------+------+---------------+------+---------+------+--------+-------------+
但即使没有索引提示,也可以搜索整数字符串:
mysql> EXPLAIN SELECT * FROM `transactions` where user_id='1';
+----+-------------+--------------+------+---------------+---------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------+------+---------------+---------+---------+-------+------+-----------------------+
| 1 | SIMPLE | transactions | ref | user_id | user_id | 36 | const | 1 | Using index condition |
+----+-------------+--------------+------+---------------+---------+---------+-------+------+-----------------------+
将 varchar 列与二进制整数进行比较似乎破坏了可索引性。
【讨论】:
太棒了,非常感谢!以上是关于MySQL排他锁(FOR UPDATE)正在锁定整个表[重复]的主要内容,如果未能解决你的问题,请参考以下文章
数据库:Mysql中“select ... for update”排他锁分析