MySQL独占锁(FOR UPDATE)锁定整个表[重复]

问题描述 投票:2回答:1

这个问题在这里已有答案:

我有一个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`);

我在多线程环境中工作,并希望确保:

  • 没有两个线程可以同时为同一用户插入事务
  • 如果线程正在为用户插入事务,则没有其他线程可以从该用户读取事务

我提出了以下解决方案:

  1. 当线程想要为用户插入事务时,获取与该用户对应的行的独占锁 BEGIN; -- Acquire an exclusive lock on the rows with user_id=1 SELECT * FROM transactions WHERE user_id = 1 FOR UPDATE; -- Insert transactions ... COMMIT;
  2. 当线程想要读取用户余额时(通常通过对用户的所有事务求和),它首先获取对应于该用户的行的共享锁 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之后,我本以为这会起作用:

选择...锁定共享模式

在读取的任何行上设置共享模式锁定。其他会话可以读取行,但在事务提交之前无法修改它们

SELECT ... FOR UPDATE

对于搜索遇到的索引记录,锁定行和任何关联的索引条目,就像为这些行发出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)

任何的想法?

mysql multithreading concurrency locking innodb
1个回答
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列与二进制整数的比较似乎打败了可索引性。

© www.soinside.com 2019 - 2024. All rights reserved.