Innodb 事务或表锁?
Innodb Transaction or tables lock?
我有一个简单的 table,它是一个电子邮件队列。
CREATE TABLE `emails_queue_batch` (
`eq_log_id` int(11) NOT NULL DEFAULT '0',
`eq_to` varchar(120) CHARACTER SET utf8 DEFAULT NULL,
`eq_bcc` varchar(80) CHARACTER SET utf8 DEFAULT '',
`eq_from` varchar(80) CHARACTER SET utf8 DEFAULT NULL,
`eq_title` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
`eq_headers` varchar(80) CHARACTER SET utf8 DEFAULT NULL,
`eq_content` longtext CHARACTER SET utf8,
`eq_sid` int(11) DEFAULT '0',
`eq_type` int(11) DEFAULT '0' COMMENT 'email type',
`eq_esp` int(11) DEFAULT '0',
PRIMARY KEY (`eq_log_id`),
KEY `email` (`eq_to`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
多个线程一次重复读取 50 行并删除这些行。
为了避免重复读取同一行,我使用了:
$db->query(" LOCK TABLE $table WRITE ");
$query= "SELECT * FROM $table LIMIT ".CHUNK_SIZE. " " ;
$emails2send=$db->get_results ($query);
if (!empty ($emails2send)){
// DELETE EMAIL
$eq_log_ids = array();
foreach ($emails2send as $email) $eq_log_ids[]= $email->eq_log_id ;
$query= "DELETE FROM $table WHERE eq_log_id IN ( ".implode(',', $eq_log_ids)." ) ";
$db->query ($query);
$db->query (" UNLOCK TABLES "); // unlock the table so other sessions can read next rows
........ code processing the read rows here .............
} else { // if !empty emails2send
// $emails2send is empty
$db->query (" UNLOCK TABLES; ");
$stop_running=true; // stop running
}
另一个线程同时写入table。
由于我不明白这个配置在读取和写入时都被锁定 table 死锁了。
我的问题是:
这是锁定正确的解决方案以确保我读取每一行一次且仅一次(并删除它)。
或者这是否更好地作为交易来处理,如果是的话是哪一种?我没有交易经验。
使用事务可能更好,尤其是当你陷入僵局时。
首先,尝试将批量大小从 50 减少到 1,看看情况是否有所改善。他们可能。这很容易。如果您使用事务,这就是您想要做的。
其次,尝试这种查询顺序。
START TRANSACTION;
SELECT @id := table.eq_log_id, table.* FROM table LIMIT 1 FOR UPDATE;
/* handle the item here */
DELETE FROM table WHERE eq_log_id = @id;
COMMIT;
这仅在 eq_log_id
是唯一(或主)键时有效。 运行 这在您的 php 程序中循环,直到 SELECT
操作 returns 没有行。那就睡一会,再试试
更好的方法是将名为 processed
的时间戳添加到您的 table,默认值为空。然后,您可以更新它们的时间戳,而不是删除行。这将为您提供一种解决问题的方法。
START TRANSACTION;
SELECT @id:=eq_log_id, * FROM table WHERE processed IS NULL LIMIT 1 FOR UPDATE;
/* handle the item here */
UPDATE table SET processed=NOW() WHERE eq_log_id = @id;
COMMIT;
您可以 运行 隔夜批量清除所有像这样的陈旧记录
DELETE FROM table WHERE processed < CURDATE() - INTERVAL 1 DAY;
我建议这样做是因为在生产中,查看已发送消息的时间历史非常有帮助。
方案A:
假设您可以在不到 2 秒的时间内处理 N 行。您有 N=50 -- 这可能太大了。
BEGIN;
SELECT ... LIMIT 50 FOR UPDATE;
... process ...
... gather a list of ids to delete ...
DELETE ... WHERE id IN (...)
COMMIT;
抓得越多,速度越快,但也越容易出现死锁。发生死锁时,只需重新开始事务即可。还要跟踪死锁发生的频率,以便调整“50”。
B计划:
当一项交易的处理需要 "too long" 时,这很有用。我说2秒大概是"too long".
Grab a row to process:
with autocommit=ON ...
UPDATE ... SET who_is_processing = $me,
when_grabbed = NOW()
id = LAST_INSERT_ID(id),
WHERE when_grabbed IS NULL
AND any-other-criteria
LIMIT 1;
$id = SELECT LAST_INSERT_ID();
... process $id ... (This may or may not involve transactions)
Release the row (or, in your case, delete it):
again, autocommit=ON suffices...
DELETE ... WHERE id = $id;
"Never" 在 InnoDB 中使用 table 锁。 (可能有用例,但这不是一个。)
我有一个简单的 table,它是一个电子邮件队列。
CREATE TABLE `emails_queue_batch` (
`eq_log_id` int(11) NOT NULL DEFAULT '0',
`eq_to` varchar(120) CHARACTER SET utf8 DEFAULT NULL,
`eq_bcc` varchar(80) CHARACTER SET utf8 DEFAULT '',
`eq_from` varchar(80) CHARACTER SET utf8 DEFAULT NULL,
`eq_title` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
`eq_headers` varchar(80) CHARACTER SET utf8 DEFAULT NULL,
`eq_content` longtext CHARACTER SET utf8,
`eq_sid` int(11) DEFAULT '0',
`eq_type` int(11) DEFAULT '0' COMMENT 'email type',
`eq_esp` int(11) DEFAULT '0',
PRIMARY KEY (`eq_log_id`),
KEY `email` (`eq_to`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
多个线程一次重复读取 50 行并删除这些行。
为了避免重复读取同一行,我使用了:
$db->query(" LOCK TABLE $table WRITE ");
$query= "SELECT * FROM $table LIMIT ".CHUNK_SIZE. " " ;
$emails2send=$db->get_results ($query);
if (!empty ($emails2send)){
// DELETE EMAIL
$eq_log_ids = array();
foreach ($emails2send as $email) $eq_log_ids[]= $email->eq_log_id ;
$query= "DELETE FROM $table WHERE eq_log_id IN ( ".implode(',', $eq_log_ids)." ) ";
$db->query ($query);
$db->query (" UNLOCK TABLES "); // unlock the table so other sessions can read next rows
........ code processing the read rows here .............
} else { // if !empty emails2send
// $emails2send is empty
$db->query (" UNLOCK TABLES; ");
$stop_running=true; // stop running
}
另一个线程同时写入table。 由于我不明白这个配置在读取和写入时都被锁定 table 死锁了。
我的问题是: 这是锁定正确的解决方案以确保我读取每一行一次且仅一次(并删除它)。
或者这是否更好地作为交易来处理,如果是的话是哪一种?我没有交易经验。
使用事务可能更好,尤其是当你陷入僵局时。
首先,尝试将批量大小从 50 减少到 1,看看情况是否有所改善。他们可能。这很容易。如果您使用事务,这就是您想要做的。
其次,尝试这种查询顺序。
START TRANSACTION;
SELECT @id := table.eq_log_id, table.* FROM table LIMIT 1 FOR UPDATE;
/* handle the item here */
DELETE FROM table WHERE eq_log_id = @id;
COMMIT;
这仅在 eq_log_id
是唯一(或主)键时有效。 运行 这在您的 php 程序中循环,直到 SELECT
操作 returns 没有行。那就睡一会,再试试
更好的方法是将名为 processed
的时间戳添加到您的 table,默认值为空。然后,您可以更新它们的时间戳,而不是删除行。这将为您提供一种解决问题的方法。
START TRANSACTION;
SELECT @id:=eq_log_id, * FROM table WHERE processed IS NULL LIMIT 1 FOR UPDATE;
/* handle the item here */
UPDATE table SET processed=NOW() WHERE eq_log_id = @id;
COMMIT;
您可以 运行 隔夜批量清除所有像这样的陈旧记录
DELETE FROM table WHERE processed < CURDATE() - INTERVAL 1 DAY;
我建议这样做是因为在生产中,查看已发送消息的时间历史非常有帮助。
方案A:
假设您可以在不到 2 秒的时间内处理 N 行。您有 N=50 -- 这可能太大了。
BEGIN;
SELECT ... LIMIT 50 FOR UPDATE;
... process ...
... gather a list of ids to delete ...
DELETE ... WHERE id IN (...)
COMMIT;
抓得越多,速度越快,但也越容易出现死锁。发生死锁时,只需重新开始事务即可。还要跟踪死锁发生的频率,以便调整“50”。
B计划:
当一项交易的处理需要 "too long" 时,这很有用。我说2秒大概是"too long".
Grab a row to process:
with autocommit=ON ...
UPDATE ... SET who_is_processing = $me,
when_grabbed = NOW()
id = LAST_INSERT_ID(id),
WHERE when_grabbed IS NULL
AND any-other-criteria
LIMIT 1;
$id = SELECT LAST_INSERT_ID();
... process $id ... (This may or may not involve transactions)
Release the row (or, in your case, delete it):
again, autocommit=ON suffices...
DELETE ... WHERE id = $id;
"Never" 在 InnoDB 中使用 table 锁。 (可能有用例,但这不是一个。)