我有一个数据库场景(我正在使用Oracle),其中有几个进程插入表中,一个进程从中进行选择。该表基本上用作中间存储,多个进程(在下文中称为Writers)写入日志事件,并且单个进程(以下称为Reader)从中读取事件以进行进一步处理。 Reader必须读取插入表中的所有事件。
目前,这是通过从升序中为每个插入的记录分配id来完成的。读者定期从表中选择一个条目块,其中id大于先前读取的块的最大id。例如。类似的东西:
SELECT
*
FROM
TRANSACTION_LOG
WHERE
id > (
SELECT
last_id
FROM
READER_STATUS
);
这种方法的问题在于,由于编写器同时操作,因此行并不总是根据其分配的ID按顺序插入,即使这些按顺序按升序分配。也就是说,id = 100的行有时会在id = 110的记录之后写入,因为在写入记录id = 100的进程之后开始写入id = 110的行的过程开始,但是先提交。如果读取器已经读取了id = 110的行,则可能导致Reader缺少id = 100的行。
强制写入者对表的独占锁定可以解决问题,因为这会强制它们按顺序插入,也可以让Reader等待任何未完成的提交。然而,这可能不会很快。
我的想法是,读者在阅读之前等待任何优秀的作家提交就足够了。也就是说,在读者读取所有作者完成之前,作家可以继续同时操作。
我的问题是:
如何指示我的读者进程等待我的编写器进程的任何未完成的提交?对于上述问题的任何其他建议也是受欢迎的。
答案 0 :(得分:1)
有趣的问题。听起来你正在建立一个很好的解决方案 我希望我能帮忙。
一些建议......
你可以创建一个表WRITER_STATUS,它有一个last_id字段:每个编写器在写入要写入日志的ID之前更新该表,但前提是它的ID大于last_id的当前值。
读者也会检查此表,现在知道是否有任何作者尚未写入。
这可能更有效 读者完成阅读后,会检查所检索记录中的任何漏洞 然后它将任何缺少的ID记录到MISSING_IDS表中,并且对于下一次读取它会执行类似
的操作SELECT *
FROM TRANSACTION_LOG
WHERE id > (SELECT last_id
FROM READER_STATUS)
OR id IN ( SELECT id from MISSING_IDS )
答案 1 :(得分:1)
您可能希望在阅读器进程中对表进行独占锁定。这将等到所有作者完成并释放他们的行锁,这样你就可以确定没有出色的作家交易。
答案 2 :(得分:1)
我不会做任何可能会影响并发性和吞吐量的锁定。
如果您逐行跟踪已处理的日志行,则也不需要Reader_Status表。
这就是我要做的事情:在日志表中添加一个新列。例如,将其称为“已处理”。使它成为布尔值,默认为false(或小整数,默认为0或其他)。写入者在插入时使用默认值。
当Reader查询要处理的下一个记录块时,他会查询处理为false且id值低的行。
SELECT * FROM Transaction_Log
WHERE processed = 0
ORDER BY id
LIMIT 10;
当他处理它们时,Reader使用UPDATE将处理从false更改为true。因此,当Reader下次查询一个记录块时,他确信他不会获得他已经处理过的行。
UPDATE Transaction_Log
SET processed = 1
WHERE id = ?; -- do this for each row processed
此UPDATE不应与Writers完成的INSERT操作冲突。
如果任何行被其他Writer按顺序提交,读者将在下次查询时看到它们,如果他总是按照id列从最低值到最高值的顺序处理它们。
答案 3 :(得分:0)
由于您知道Reader处理了last_id
,因此您可以通过以下方式申请下一个工作项:
select * from Transaction_log where id = (
select last_id + 1 /* or whatever increment your sequencer has */
from Reader_status)
答案 4 :(得分:0)
我同意AJ的解决方案(link)。此外,以下建议可能有助于减少漏洞数量。
1)使用Oracle Sequence
创建ID并使用auto-increment
,如下所示
INSERT INTO transaction_table VALUES(id__seq.nextval, <other columns>);
2)使用autoCommit(true)
以便插入将立即提交。
这两个步骤将大大减少孔数。仍然有可能某些插入首先启动但稍后提交并且之间发生了读取操作。