【问题标题】:Implement a ring buffer实现一个环形缓冲区
【发布时间】:2014-03-28 11:31:05
【问题描述】:

我们有一个记录数据的表。它以每秒 15K 行的速度记录。

问题:我们如何将表大小限制为 10 亿最新行?

即一旦达到 10 亿行,它就变成一个环形缓冲区,添加最新的时删除最旧的行。

触发器可能会过多地加载系统。 Here's SO 上的触发器示例。 我们已经在使用一系列调整来保持速度(例如存储过程、表参数等)。

编辑(8 年后):

我最近的问题/答案here 使用时间序列数据库解决了类似问题。

【问题讨论】:

  • 经过一番考虑,我们重新检查了客户的问题,得出的结论是,尽管要求使用环形缓冲区,但客户真正想要的是最后十亿行的滚动记录。这可以通过定期唤醒的 cron 作业来实现,进行计数然后删除最旧的计数 - 10 亿行。有一些缺点,主要是它可能会将服务器加载到在 cron 作业运行时无法捕获某些数据的程度。因此需要对执行频率进行一些实验。

标签: sql sql-server tsql ssms


【解决方案1】:

除非 10 亿有什么神奇之处,否则我认为您应该考虑其他方法。

首先想到的是对数据进行分区。比如说,将一小时的数据放入每个分区。这将导致一个分区中大约有 15,000*60*60 = 5400 万条记录。大约每 20 小时,您可以删除一个分区。

分区的一大优点是插入性能应该很好,您不必删除单个记录。根据查询负载、索引和其他因素,可能会有额外的开销。但是,由于没有额外的索引和主要是插入的查询负载,它应该比尝试在插入时每秒删除 15,000 条记录更好地解决您的问题。

【讨论】:

  • 感谢您的回答。我们之前考虑过分区的另一个原因,一个问题是您需要企业版 SQL Server (technet.microsoft.com/en-us/library/cc645993.aspx)。另外,配置是一件相当复杂的事情。但是,如果您的系统处于这种复杂程度,这是一个很好的建议。
  • @NickT。 . .如果您每秒执行 15k 次插入,那么您就处于这种复杂程度。
【解决方案2】:

我没有完整的答案,但希望有一些想法可以帮助您入门。

我会在表格中添加某种数字列。该值将递增 1,直到达到您想要保留的行数。此时,该过程将切换到update 语句,覆盖前一行而不是插入新行。您显然无法使用此列来确定行的顺序,因此,如果您还没有,我还将添加一个时间戳列,以便您稍后按时间顺序对它们进行排序。

为了在事务之间协调计数器值,您可以使用序列,然后执行模除以获取计数器值。

为了处理表格中的任何空白(例如,有人删除了某些行),您可能需要使用 merge 语句。如果行丢失,这应该执行插入,如果存在则更新。

希望这会有所帮助。

【讨论】:

    【解决方案3】:

    这是我的建议:

    • 用 1,000,000,000 行预填充表,包括作为主键的行号。
    • 不是inserting 新行,而是让记录器保留一个每次递增的计数器变量,并根据行号update 适当的行。

    这实际上是您在其他情况下对环形缓冲区所做的事情。你不会一直分配内存和删除;你只是一遍又一遍地覆盖同一个数组。

    更新:更新doesn't actually change the data in place,正如我想的那样。所以这可能效率不高。

    【讨论】:

    • 确实,事实上我们将来很可能将这方面转移到一个预填充的二进制文件中,并完全放弃 SQL Server。还可以节省一大笔许可费。
    【解决方案4】:

    只是一个在评论中写起来很复杂的想法。

    创建几个日志表,以3个为例,Log1、Log2、Log3

    CREATE TABLE Log1 (
        Id int NOT NULL
            CHECK (Id BETWEEN 0 AND 9)
       ,Message varchar(10) NOT NULL
       ,CONSTRAINT [PK_Log1] PRIMARY KEY CLUSTERED ([Id] ASC) ON [PRIMARY]
    )
    CREATE TABLE Log2 (
        Id int NOT NULL
            CHECK (Id BETWEEN 10 AND 19)
       ,Message varchar(10) NOT NULL
       ,CONSTRAINT [PK_Log2] PRIMARY KEY CLUSTERED ([Id] ASC) ON [PRIMARY]
    )
    CREATE TABLE Log3 (
        Id int NOT NULL
            CHECK (Id BETWEEN 20 AND 29)
       ,Message varchar(10) NOT NULL
       ,CONSTRAINT [PK_Log3] PRIMARY KEY CLUSTERED ([Id] ASC) ON [PRIMARY]
    )
    

    然后创建一个分区视图

    CREATE VIEW LogView AS (
        SELECT * FROM Log1
      UNION ALL
        SELECT * FROM Log2
      UNION ALL
        SELECT * FROM Log3
    )
    

    如果您使用的是 SQL2012,则可以使用序列

    CREATE SEQUENCE LogSequence AS int 
        START WITH 0
        INCREMENT BY 1
        MINVALUE 0
        MAXVALUE 29
        CYCLE
    ;
    

    然后开始插入值

    INSERT INTO LogView (Id, Message)
    SELECT NEXT VALUE FOR LogSequence
          ,'SomeMessage'
    

    现在您只需要按某种计划截断日志表

    如果您没有 sql2012,则需要以其他方式创建序列

    【讨论】:

      【解决方案5】:

      我自己也在寻找类似的东西(使用表作为循环缓冲区),但似乎更简单的方法(对我来说)只是定期删除旧条目(例如最低 ID 或最低创建/上次修改日期时间或超过一定年龄的条目)。它不是一个循环缓冲区,但对于某些人来说,它可能是一个足够接近的近似值。 ;)

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-12
      • 1970-01-01
      • 2012-04-04
      • 1970-01-01
      相关资源
      最近更新 更多