Mysql Innodb事务或表锁?
我有一个简单的表,它是一个电子邮件队列Mysql Innodb事务或表锁?,mysql,database-design,transactions,innodb,table-locking,Mysql,Database Design,Transactions,Innodb,Table Locking,我有一个简单的表,它是一个电子邮件队列 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 ut
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
}
另一个线程同时写入表。
由于这个原因,我不明白这个配置在读写时都被锁定了
我的问题是:
这是一种正确的锁定解决方案,可以确保每一行只读取一次并删除它
或者,作为一项交易来处理是否更好?如果是,是哪种交易?我对事务没有经验。使用事务可能更好,尤其是在死锁的情况下 首先,尝试将批处理大小从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操作不返回任何行。然后睡一会儿,再试一次
更好的做法是在表中添加一个名为processed的时间戳,默认值为空。然后,您可以更新行的时间戳,而不是删除行。这会给你一种解决问题的方法
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:
当事务处理项目所需时间过长时,此选项非常有用。我说2秒钟可能太长了
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;
不要在InnoDB中使用表锁。可能有一些用例,但不是这样。正确的解决方案是使用真正的消息队列示例:RabbitMQ或ActiveMQ。使用事务性数据库作为事实上的队列总是会导致锁争用和死锁,除非您将自己限制为仅从数据库队列读取一个线程。谢谢,我正在处理它。消息队列的一个问题是,如果出现问题,例如需要取消电子邮件批处理,则无法有选择地从队列中删除。在我过去的一次作业中,我们将任务保留在数据库中,但调度程序进程在任务准备运行时将其拉入,并将其发布到MQ中。只要dispatcher是单线程的,它就可以避免您询问的问题。然后,MQ可以被许多工作线程读取。这就平衡了您可以删除或修改数据库中的任务,直到它们发布到MQ。