多进程写入的事务封装

Posted

技术标签:

【中文标题】多进程写入的事务封装【英文标题】:Transaction encapsulation of multi-process writes 【发布时间】:2008-09-30 08:33:02 【问题描述】:

我有一个数据库场景(我正在使用 Oracle),其中几个进程插入到一个表中,一个进程从中选择。该表主要用作中间存储,多个进程(以下称为 Writers)向其写入日志事件,而单个进程(以下称为 Reader)则从中读取事件以进行进一步处理。 Reader 必须读取插入到表中的所有事件。

目前,这是通过为每个插入的记录分配一个升序的 id 来完成的。读取器周期性地从表中选择一个条目块,其中 id 大于先前读取块的最大 id。例如。类似:

SELECT
  *
FROM
  TRANSACTION_LOG
WHERE
  id > (
    SELECT
      last_id
    FROM
      READER_STATUS
   );

这种方法的问题在于,由于写入器是并发操作的,因此行并不总是根据其分配的 id 按顺序插入,即使这些行是按顺序升序分配的。也就是说,有时会在 id=110 的记录之后写入 id=100 的行,因为写入 id=110 的行的过程在写入记录 id=100 的进程之后开始,但首先提交。如果 Reader 已经读取了 id=110 的行,这可能会导致 Reader 丢失 id=100 的行。

强制写入器对表进行排他锁将解决问题,因为这将强制它们按顺序插入,并且读取器也等待任何未完成的提交。但是,这可能不会很快。

我的想法是,Reader 在阅读之前等待任何未完成的 Writer 提交就足够了。也就是说,只要 Reader 进行读取,Writer 就可以继续并发操作,直到所有 writer 完成。

我的问题是: 如何指示我的读者进程等待我的作家进程的任何未完成的提交?也欢迎对上述问题提出任何替代建议。

【问题讨论】:

【参考方案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 ) 

【讨论】:

谢谢。当然值得考虑。在接下来的几天里,我将尝试各种方法。 酷。请更新您的问题,让我们知道您的进展情况(或者更好的是,发布答案)。这是偶尔出现的问题之一,一个好的解决方案是金粉!祝你好运。【参考方案2】:

您可能希望在读取器进程中对表设置排他锁。这将等到所有写入器完成并释放他们的行锁,因此您可以确定没有未完成的写入器事务。

【讨论】:

是的,我同意这将导致阅读器等待。问题是,它会造成什么性能损失。 取决于阅读器进程运行的频率和时间。该解决方案至少允许多个写入器并行运行,因此如果写入发生的频率明显更高,那么读取性能损失应该是可以忍受的。【参考方案3】:

我不会做任何会影响并发和吞吐量的锁定。

如果您逐行跟踪已处理的日志行,您也不需要 Reader_Status 表。

这就是我要做的:在您的日志表中添加一个新列。例如,将其称为“已处理”。使其成为布尔值,默认为 false(或小整数,默认为 0 或其他)。 Writer 插入时使用默认值。

当 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 不应与 Writer 执行的 INSERT 操作冲突。

如果任何行被其他 Writers 提交的顺序不正确,Reader 将在下次查询时看到它们,如果他总是按照 id 列从最低值到最高值的顺序处理它们。

【讨论】:

【参考方案4】:

既然你知道last_id已经被Reader处理了,你可以这样请求下一个工作项:

select * from Transaction_log where id = (
  select last_id + 1 /* or whatever increment your sequencer has */
    from Reader_status)

【讨论】:

【参考方案5】:

我同意 AJ 的解决方案 (link)。此外,以下建议可能有助于减少孔的数量。

1) 使用Oracle Sequence创建id,使用auto-increment如下

INSERT INTO transaction_table VALUES(id__seq.nextval, <other columns>);

2) 使用autoCommit(true) 以便插入会立即提交。

这两个步骤将大大减少孔的数量。仍然有可能一些插入先开始但稍后提交,并且在这两者之间发生了读取操作。

【讨论】:

以上是关于多进程写入的事务封装的主要内容,如果未能解决你的问题,请参考以下文章

多进程生产者消费者框架设计

多进程生产者消费者框架设计

多进程生产者消费者框架设计

python多进程使用函数封装

php多进程模拟并发事务产生的问题

PHP实现多进程模拟并发事务时遇到的问题