早上好
我有多个客户试图在表中获取唯一的主键。
只有当它们与成功的范围扫描匹配时,由该 PK 标识的行才被视为“有效”。范围扫描是SELECT id FROM lookup WHERE allowed='Y' and updated<=NOW() LIMIT 1
------------+---------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| fullname | varchar(250) | NO | UNI | 0 | |
| allowed | enum('Y','N') | NO | MUL | N | |
| updated | timestamp | NO | | CURRENT_TIMESTAMP | |
| hits | smallint(6) | NO | MUL | 0 | |
| stop_allow | enum('Y','N') | NO | MUL | N | |
+------------+---------------+------+-----+-------------------+----------------+
完成第一个选择后,将执行另一个 SELECT 以检索内容。
问题是许多客户端同时在做同样的事情(或者他们随机找到一种方法来相互匹配 grrrr...)。
到目前为止,我已经尝试过:
1)
start transaction;
*range scan* LIMIT 1 FOR UPDATE;
SELECT * from lookup WHERE id=(result of the range scan);
*perform stuff*
commit;
这是一个性能 killer 。东西被永远锁定,一段时间后“Mysql 服务器升天”。
2)
start transaction;
*range scan*
SELECT * from lookup WHERE id=(result of the previous query) FOR UPDATE;
*perform stuff*
commit;
autocommit=0 失败得很惨,但是速度很快
3) 在这一点上,我开始认为事务是问题所在
no transaction;
//get a row that is not being processed
*range scan* LEFT OUTER JOIN temp_mem_table WHERE **temp_mem_table.id IS NULL**
$rid = (result of the range scan)
//check if another client is doing the same thing, if so then stop here
select 1 from temp_mem_table WHERE id=$rid
//if there is a result => return null; this is not enough to block stuff going through
//signal to other client that this ID is being processed
insert into temp_mem_table(id) values($rid)
//get the content
SELECT * from lookup WHERE id=($rid);
*perform time intensive operations*
编辑:temp_mem_table 实际上是一个内存表,它会在一段时间内刷新。它看起来像这样:
CREATE TABLE temp_mem_table(id int(11), primary key(id)) engine=memory
思路是:如果正在处理的内容存储在所有客户端都可以访问的内存表中,那么他们应该能够知道他们的 friend 在做什么。支票应该停止任何进一步的处理。但不知何故,他们找到了一种方法来通过:(
短时间后,似乎有近 50% 的主键至少被处理了两次。
我会想办法做到这一点,但也许你们中的一些人遇到过类似情况并且可以提供帮助。
谢谢
最佳答案
好的,对于那些遇到著名的“如何在 Mysql 中选择未锁定的行?”的人来说比如在这里看到http://bugs.mysql.com/bug.php?id=49763还有很多其他地方。这里有一个肮脏的 hack 来解决它。
这是在 READ REPEATABLE MODE 中完成的,它应该是 ACID 超过 9000 或至少不会破坏任何东西(也许)。
起点是有某种“范围”的行需要锁定以供读取,这样其他客户端无论如何都不会得到它。
SELECT pk FROM tbl LIMIT 0,10
SELECT pk FROM tbl where *large range scan*
我确实创建了一个内存表(因为它应该更快),例如:
CREATE TABLE `jobs` (
`pid` smallint(6) DEFAULT NULL,
`tid` int(11) DEFAULT NULL,
UNIQUE KEY `pid` (`pid`),
UNIQUE KEY `tid` (`tid`)
) ENGINE=MEMORY DEFAULT CHARSET=utf8 |
Pid是客户端的唯一标识。在我的例子中,它是实际的进程 ID。 Tid 是与我们执行范围王扫描的那个巨大表的主键匹配的任务 ID。
那么伪代码是这样的:
SELECT pk from tbl WHERE (range scan) or limit 0,100
delete from jobs where pid=$my_pid
foreach of those pk do
if(insert IGNORE into jobs(pid,tid) values(1234,pk)) break;
done;
select pk from jobs where pid=$my_pid
select * from big_tbl where id=pk
已使用 2、10、25、50 和 100 个并发客户端对此进行了测试,并在每个客户端上获得了 100% 唯一的任务分配。
现在这可能不是 super 复杂,或者看起来可能不优雅,但只要 CPU 保持凉爽,我不在乎。
关于mysql - 如何从并发表读取中的范围选择中获取唯一主键,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23129338/