我有一个表,只有事务附加(不更新或删除)行(我将解释为什么这很重要),我需要获取此表的新的,以前未获取的行,每个分钟与cron。
我该怎么做?在任何编程语言中(我使用Perl,但那是无关紧要的。)
我列出了我如何解决这个问题的方法,并请你告诉我正确的方法(有一个......)
弹出我头脑的第一种方法是保存(在文件中)所提取行的最大auto_incrementing id,因此在下一分钟我可以使用:WHERE id > $last_id
获取。但这可能会错过行。因为在事务中插入了新行,所以保存id = 5的行的事务可能会在保存id = 4的行的事务之前提交。因此,cron脚本可能会检索第5行而不是第4行,并且当第4行在一个分秒后提交时,它将永远不会被提取(因为4不是>而不是5,这是$ last_id)。
然后我想我可以让cron作业获取最后两分钟内有一个日期字段的所有行,检查在上一次运行cron作业时是否再次检索了哪些行(为此我需要保存检索哪些行ID的地方,比较和处理新的行ID。不幸的是,这很复杂,并且也没有解决如果某个插入事务需要两个和一个半分钟来提交某些奇怪的数据库原因而发生的问题,这将导致日期对于下一次迭代而言太旧了cron job to fetch。
然后我想到安装像RabbitMQ或任何其他的消息队列(MQ)。执行插入事务的相同进程将通知RabbitMQ新行,然后RabbitMQ将通知一个处理新行的始终运行的进程。因此,不是在最后一分钟插入一批行,而是在写入时,该进程将逐个获取新行。这听起来不错,但是有太多的失败点 - RabbitMQ可能会停机一秒钟(例如重启),在这种情况下,插入事务将在没有接收进程收到新行的情况下提交。所以新行将被遗漏。不好。
我只想到了另一个解决方案:接收进程(其中有30个,在完全相同的数据上执行完全相同的工作,因此相同的行被处理30次,每次接收进程一次)可以写入另一个在处理行X时它们处理了行X的表,然后当时间到来时,它们可以通过OUTER JOIN查询请求主表中所有不存在于“have_processed”表中的行。但我相信(如果我错了,请纠正我)这样的查询会在数据库服务器上消耗大量的CPU和HD,因为它必须比较两个表的整个id列表才能找到新的条目(和桌子很大,每分钟都变大了。如果接收过程只有一个那么本来会很快 - 那么我就可以在主表中添加一个名为“have_read”的索引字段,这样可以在数据库服务器上快速,轻松地查找新行。
这样做的正确方法是什么?你有什么建议?问题很简单,但找到解决方案似乎很难(对我来说)。
谢谢。
答案 0 :(得分:2)
我认为“最好”的方法是使用一个检查新行的进程并将它们委托给30个消费者进程。然后,从数据库的角度来看,您的问题变得更加简单,并且委派过程也难以编写。
如果您无法通过数据库与三十个消费者进程进行通信,我可以提出的最佳选择是在表上创建一个触发器,将每行复制到一个辅助表。将每一行复制到辅助表三十次(每个使用者进程一次)。向此辅助表添加一列,指示“目标”使用者进程(例如,1到30之间的数字)。每个使用者进程都会检查具有唯一编号的新行,然后删除这些行。如果您担心某些行在处理之前被删除(因为消费者在处理过程中崩溃),您可以逐个获取,处理和删除它们。
由于通过不断删除已处理的行来保持辅助表的较小,INSERT
s,SELECT
和DELETE
s会非常快。此辅助表上的所有操作也将由主键索引(如果将使用者ID作为主键的第一个字段放置)。
在MySQL语句中,这将如下所示:
CREATE TABLE `consumer`(
`id` INTEGER NOT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `consumer`(`id`) VALUES
(1),
(2),
(3)
-- all the way to 30
;
CREATE TABLE `secondaryTable` LIKE `primaryTable`;
ALTER TABLE `secondaryTable` ADD COLUMN `targetConsumerId` INTEGER NOT NULL FIRST;
-- alter the secondary table further to allow several rows with the same primary key (by adding targetConsumerId to the primary key)
DELIMTER //
CREATE TRIGGER `mark_to_process` AFTER INSERT ON `primaryTable`
FOR EACH ROW
BEGIN
-- by doing a cross join with the consumer table, this automatically inserts the correct amount of rows and adding or deleting consumers is just a matter of adding or deleting rows in the consumer table
INSERT INTO `secondaryTable`(`targetConsumerId`, `primaryTableId`, `primaryTableField1`, `primaryTableField2`) SELECT `consumer`.`id`, `primaryTable`.`id`, `primaryTable`.`field1`, `primaryTable`.`field2` FROM `consumer`, `primaryTable` WHERE `primaryTable`.`id` = NEW.`id`;
END//
DELIMITER ;
-- loop over the following statements in each consumer until the SELECT doesn't return any more rows
START TRANSACTION;
SELECT * FROM secondaryTable WHERE targetConsumerId = MY_UNIQUE_CONSUMER_ID LIMIT 1;
-- here, do the processing (so before the COMMIT so that crashes won't let you miss rows)
DELETE FROM secondaryTable WHERE targetConsumerId = MY_UNIQUE_CONSUMER_ID AND primaryTableId = PRIMARY_TABLE_ID_OF_ROW_JUST_SELECTED;
COMMIT;
答案 1 :(得分:1)
我一直在想这个问题。所以,让我看看我是否做对了。你有一个巨大的表,其中N,金额可能随时间变化,处理写(让我们称之为生产者)。现在,有这些M,我的时间变化的金额,其他过程需要至少处理一次添加的每一条记录(让我们称之为消费者)。
检测到的主要问题是:
为了解决这些问题,我想到了这一点。创建此表(PK以粗体显示):
修改使用者,以便每次他们将记录添加到HUGE_TABLE时,他们还会将M记录添加到PENDING_RECORDS表中,以便它具有HugeTableID以及当时存在的每个ConsumerID。每次消费者运行它时都会查询PENDING_RECORDS表,并为自己找到少量匹配。然后它将与HUGE_TABLE连接(注意它将是内连接,而不是左连接)并获取它需要处理的实际数据。处理完数据后,消费者将删除从PENDING_RECORDS表中提取的记录,使其保持相当小。
答案 2 :(得分:0)
有趣,我必须说:)
1)首先 - 是否可以在只添加了行的表中添加一个字段(让我们称之为'transactional_table')?我的意思是,它是一个设计范例,你有理由不在这个表上做任何类型的更新,或者它是“结构上”阻止的(即连接到db的用户没有权限在这个表上执行更新)?
因为那时最简单的方法是将“have_read”列添加到此表中,默认值为0,并使用1更新此读取行的列(即使30个进程同时执行此操作,您应该没问题,因为它会非常快,不会破坏你的数据)。即使30个进程标记了相同的1000行,也没有任何损坏。虽然如果你不操作InnoDB,就性能而言,这可能不是最好的方法(MyISAM锁定更新的整个表,InnoDB只更新行)。
2)如果这不是您可以使用的 - 我肯定会检查您作为最后一个解决方案,稍作修改。创建一个表(比方说:fetched_ids),并在该表中保存获取的行ID。然后你可以使用类似的东西:
SELECT tt.* from transactional_table tt
RIGHT JOIN fetched_ids fi ON tt.id = fi.row_id
WHERE fi.row_id IS NULL
这将从您的事务表中返回尚未保存为已提取的行。只要(tt.id)和(fi.row_id)都具有(理想上唯一的)索引,即使在大型数据集上也应该可以正常工作。 MySQL很好地处理索引字段上的JOINS。不要害怕尝试 - 创建新表,复制ID,删除其中一些并运行查询。你会看到结果,你会知道它们是否令人满意:)
P.S。当然,应该小心地在这个'fetched_ids'表中添加行,而不是创建不必要的重复项(30个同时进程可以写入你需要的数据的30倍 - 如果你需要性能,你应该注意这种情况)。
答案 3 :(得分:0)
具有这样结构的第二个表怎么样:
source_fk - 这将包含您要读取的数据行的ID。 process_id - 这将是30个进程之一的唯一ID。
然后执行LEFT JOIN并从源中排除具有与指定的process_id匹配的条目的项目。
获得结果后,只需返回并为每个结果添加source_fk和process_id。
关于此问题的一个优点是,您可以稍后添加更多进程,没有任何问题。
答案 4 :(得分:0)
我会尝试添加时间戳列,并在检索新行时将其用作参考。