这个问题在这里已有答案:
我有一个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之后,我本以为这会起作用:
选择...锁定共享模式
在读取的任何行上设置共享模式锁定。其他会话可以读取行,但在事务提交之前无法修改它们
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 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列与二进制整数的比较似乎打败了可索引性。