代码之家  ›  专栏  ›  技术社区  ›  Dr. Hans-Peter Störr

只读取数据库中类似日志的表中的新行

  •  8
  • Dr. Hans-Peter Störr  · 技术社区  · 15 年前

    我们认为,有几个服务器正在将行块插入关系数据库中的一个表中,而一个服务器偶尔会从该表中读取一次新数据。(表概念上是某种日志文件——数据只被插入,但从未被修改过,而读服务器显示了日志的尾部。)有没有一种方法让读服务器只读取新数据?我们可以随意安排桌子的结构。

    有些想法在我的脑海中闪过,但不起作用:

    • 将行标记为“已读”不适合我们的应用程序:读取服务器不应更改数据库。(写入数据库以显示内容不是一件好事,可能会有几个会话显示这些内容。)

    • 我们可以在每一行中插入一个时间戳,该时间戳由数据库系统时间填充。问题是这不是提交时间的时间戳,而是插入时间的时间戳。如果您询问数据库“在现在-5分钟到现在之间给我所有值”,您就不能依赖所有存在的值,因为可能有正在进行的事务。稍后您必须再次询问这个间隔中的值,这是我想要避免的。

    • 我们可以插入一个从序列填充的连续行计数。与使用时间戳时一样,运行事务也会出现同样的问题。

    有没有解决这个问题的方法,或者我必须应用一些启发式方法,比如假设事务时间最长,总是要求在“现在-事务时间最长”之后写入值,并读取一些数据两次?

    以防万一:我们使用Oracle来解决这个问题。但我假设答案只与其他数据库一起使用,也具有普遍的兴趣。

    6 回复  |  直到 15 年前
        1
  •  3
  •   charstar    15 年前

    未指定正在使用的数据库,因此不清楚是否必须将解决方案锤击到现有部署中。有一些队列引擎可以插入到MySQL中,它们可能会工作。其中一个是 Q4M .一些商业数据库(如Oracle)具有时间数据库功能,允许确定事务时间与有效时间与实时时间之比。

    使用Oracle时,伪列 ora_rowscn 或者有用的组合 scn_to_timestamp(ora_rowscn) 可以有效地提供提交行的时间戳(发生行的SCN)。或者,Oracle Workspace Manager提供了版本启用表,基本上是这样的:您可以使用 DBMS_WM.EnableVersioning(...) ,行插入一个附加的 WMSYS.WM_PERIOD(...) 字段指定有效的时间范围,在读卡器上设置工作区的有效范围 DBMS_WM.SetValidTime(...) .

    您还可以通过将时间戳概念与提交时间启发式方法相结合,在一定程度上伪造该功能。其思想是简单地将“有效时间”与数据一起存储为一列,而不是从现在开始使用任意增量()。换句话说,一个二级时间戳列,它将基于提交时间+一些可接受的延迟窗口(可能是平均提交时间+标准偏差的两倍)的启发式,指定未来的某个日期(“有效时间”)。或者,使用一些平均提交时间的ceil()ing(“至少是提交时间,但四舍五入为30秒间隔”)。后者将有效地量化(合并?)将读取时间日志记录。它看起来并没有太大的不同,但是这样可以避免您阅读多余的行。它还解决了读取应用程序在不编写更多代码的情况下无法准确地知道写入应用程序的提交时间的问题。

        2
  •  4
  •   Regent    15 年前

    MS SQL有其特定的解决方案:

    您可以添加一列 划线 表的数据类型。引擎将在任何更新/插入语句中自动更新相关行上的此列。

    如果作者使用 ReadCommitted 然后读卡器可以使用的隔离级别 ReadUncommitted 隔离级别(因此在返回任何结果之前不需要等待所有事务结束),但使用如下查询:

    SELECT * FROM [Log]
    WHERE Version > @LastKnownVersion
        AND Version < MIN_ACTIVE_ROWVERSION()
    

    在哪里? @LastKnownVersion 是读卡器处理的最大行版本,并且 MIN_ACTIVE_ROWVERSION() 是一个内置的MS SQL函数,它返回仍在事务中的最小行版本号。

    因此,对于这个解决方案,即使您提交了id=4但id=3还没有提交,它也只返回id=3之前更改过的行,因为它的版本 最小活动行版本() .

    这种方法的优点是,在得到任何结果之前,读者不需要等待事务被提交,如果有很多作者,那么什么是至关重要的。(读者可能永远被锁定。)

        3
  •  0
  •   blispr    15 年前

    这是一个可能的解决方案,取决于您的情况等。

    有一个名为“read_timestamp”的列,该列为空,一旦读取了一行,读取过程就会将其更新为非空时间戳。

    读卡器用“where read_timestamp is NULL”查询该表。

    一个简单的解决方案是使用一个估计(即此行 可能已经看过了 有点小题大做)。因此,您可以随时显示“最后50行”或“最后10分钟到达的行”(不准确的是,另一个日志查看器可能已经提取了这些行)。

    第三种解决方案是使用后端进程将这些行送入队列:每读取一行都会使该行从队列中消失(因为它是一个“pop”操作)。因此,一行只能查看一次(先到先得)。

        4
  •  0
  •   Ioan    15 年前

    我想说你关于时间戳的想法是有效的,但与其要求范围,不如要求所有的值 之后 一段时间。你应该得到所有 可获得的 数据库中您最近选择的时间段内的值。显然,对于任何仍在进行中但尚未记录的事务,它都不起作用…但是您只需要执行一个简单的查询。

    编辑:
    必须确保时间戳值每行都是唯一的。在这种情况下,您只需要跟踪从数据库中读取的最新时间戳值。数据库的下一个查询包含所有值 之后 那。您不会丢失任何数据,也不会读取重复数据。在查询过程中,任何正在进行的事务都不会存储在数据库中,这样您就可以在下次查询数据库时得到它。

        5
  •  0
  •   Aaron Digulla    15 年前

    创建另一个表 LOG_REVISION . 它包含一行 INTEGER )

    日志记录进程应该读取这个表,并将找到的数字添加到每个日志记录中。在提交事务之前锁定行。

    阅读过程应首先更新 对数修正 通过增加数字,然后读取所有具有旧值的行 对数修正 .

    [编辑]还有两种解决方法:

    • 在另一个表中记录已处理的行。
    • 编写器将数据放入中间表,读卡器将行复制到最终位置,并删除已处理的行。
        6
  •  0
  •   Arthur Thomas    15 年前

    为日志表创建一个ID序列,以便每个日志都有一个唯一的ID。然后当读卡器读取日志时,它将在某个位置记录读取的最高ID。下次运行时,它将在最后一个记录的ID之后获取所有ID。任何正在进行的事务都不会成为问题,因为您没有在结果集中获取它们。他们将在下一次跑步中集合。

    如果你有:

    id | log
    1  | blah
    2  | blah again
    3  | more blah
    * transaction to insert row '4' in progress
    

    然后您将获取所有这些日志,并将3记录为找到的最后一个ID。下一次跑步时:

    选择ID,从日志中记录,其中ID>上次记录的ID按ID排序为3

    4  | yet again some blah
    5  | does this blah never end
    6  | omg blah
    

    把6号记录为你最后一次记录的新身份证。我仍然认为最好保留记录的日期。

    编辑“确定”以捕获类似的每件事情,您必须在另一个位置保留一组读取的所有记录,然后获取读取集与活动日志表之间的差异。如果您不能接触到日志表,那么您只需要处理集合,并查找其中一个集合中不存在的内容。