【问题标题】:Optimising retrieval of versioned records in SQL using a linked list approach使用链表方法优化 SQL 中版本化记录的检索
【发布时间】:2016-11-18 10:49:05
【问题描述】:

我有一个应用程序,它有一个大的版本记录表——也就是说,一条记录有一个在所有版本之间共享的 GUID(所以根本不是 GUID)和一个整数版本号。 GUID 和版本号一起是特定行的复合键。

业务逻辑表明,处理此表时最常见的操作是检索其中一条或多条记录的最新版本。现有代码以似乎效率最低的方式执行此操作 - 对于每条记录,它执行子查询以查找最大版本号,然后选择具有该版本号的记录。

伪代码:

currentRecord = record where record.ID == "{{guid}}"
    and record.versionNumber == 
        MAX(record.versionNumber where record.ID == "{guid}")

我真的很想对此进行优化,但我对 SQL 缺乏经验,我不知道该怎么做。比我更聪明的人已经尝试在这种设计的约束下进行优化,例如已经有我们可以创建的所有索引。是的,这种操作的低效率是一个重大问题,最终会影响我们的用户。

到目前为止,我有一个想法,我打算在有时间的时候尝试一下,类似于链表。除了版本号(仍然需要向用户显示)我想添加一个 true GUID versionID,然后每当我们创建记录的新版本时,我们都会指向一个previousVersion 列在上一个版本,并更新上一个版本的nextVersion 列指向新插入的行。这将允许将最新版本的检索简化为

currentRecord = record where record.ID == "{{guid}}"
    and record.NextVersion = NULL

这是个好主意吗?从我公认的有限理解来看,它应该将此操作从 O(N^2) 改进到 O(N),对吧?它不会以任何方式改变我们想要记录的所有版本的情况。检索比插入更常见得多,因此它需要插入和更新来添加记录而不是仅仅插入这一事实不应该有任何明显的影响。

注意:有a question already from someone with the same problem,以及其他几个类似的方法,但没有人建议这种链表样式的方法——但他们确实提出了一种最终允许相同的空检查找到最新版本的方法,但是它使用了开始和结束日期,这在我的特定问题空间中会令人困惑(记录已经有开始和结束日期,含义完全不同)。我怀疑如果这是一个好主意,有人会在回答另一个问题时提出它,但这个想法让我很烦,所以我仍然希望有人解释它为什么很糟糕。

如果相关,我正在使用 SQL Server。

【问题讨论】:

  • 您可以使用row_number() 合理有效地过滤到最新版本。或者您可以简单地在行中添加一个位标志 - IsLatestVersion,将其添加到您的索引中,并使其保持最新状态。
  • 表的完整 DDL,包括所有索引,肯定会有所帮助。至少我们可以删掉您已有的解决方案。
  • 我很感激,但是对于一个这个问题的一般版本已经被问过多次了,所以我想我应该专注于区分点(链表的想法),另外我不确定在这种情况下我可以发布任何看起来太像生产代码的东西(我是新手,比抱歉更安全)
  • 您基本上是在写入时将元数据写入行。最简单的形式是@Blorgbeard 建议的 - 只需标记一个“当前行”位。然后,您可以创建在此位中过滤的索引。不过,这里有相当多的写入开销。你能在一夜之间完成一项大工作吗?
  • 好的,所以链表构成了逻辑的一部分——您不只是在寻找最新的。一项改进性能的建议是不要使用 GUID,而是使用 IDENTITY (INT)。您可以在网上找到很多原因,为什么 INT 比 GUID 更适合键。 GUID 的唯一原因是该记录必须是真正全球唯一的,即您有多个要合并的断开连接的数据库。当然,元数据绝不会污染记录——元数据是个好主意。

标签: sql sql-server optimization linked-list


【解决方案1】:

你的方法应该没问题。你只需要(id, recordNumber) 的索引。您可以在数据库中执行此操作:

create index t_id_recordNumber on t(id, recordNumber);

您的代码应自动使用此索引。

【讨论】:

  • 对不起,如果我误解了,但您的意思是我们现有的方法只使用一个 guid 和一个版本号吗?如果是这样,我们已经在这些列上建立了索引,但它仍然很慢。
  • @Toadfish,你有 DBA 吗?这样的索引自然容易产生碎片,因此您可能需要不时重建它。
  • 这应该是一个完全不同的问题而不是评论,但是是否存在索引过多之类的东西?我很确定多年来人们一直在为每个查询瓶颈添加新索引......这可能会回答我们是否有 DBA 的问题
【解决方案2】:

我尝试了它的工作/执行方式,并认为我会尽我所能回答我自己的问题。

我的测试代码可以在http://rextester.com/GKGJX28620 查看,但是在这种情况下它有点没有意义,因为这个站点不显示执行计划/统计信息。如果您足够好奇,您可能必须在 SSMS 或其他任何方式中运行它

到目前为止,我已经创建了两个测试。首先,我尝试通过其记录 ID(在所有版本之间共享)检索特定记录的最新版本。虽然在大多数情况下,旧方法和新方法都会导致完全相同的查询(单个索引查找)并花费相同的时间返回,但其他时候旧方法(使用子查询)会执行两次索引查找和相反,流聚合,最终会明显变慢。

在第二个测试中,我尝试在非键列上检索多条记录。我观察到新方法始终明显更快。它总是一个单一的索引查找,而旧的方法总是两个(加上一个流聚合)。每次这两个查询在同一个批次中运行时,旧方法占总执行时间的 71%,新方法占 29%。

总的来说,这似乎表明这个想法有一些优点,但我完全没有资格从事 SQL 优化,所以我很高兴有人有资格参与进来,解释一下我在这里搞砸了。

【讨论】:

  • 感谢您回来提供您的发现。如果插入的额外处理不会影响您的插入/更新性能,那么我建议您忘记疑虑并继续前进。性能问题有一种相互放大的方式,并且随着时间的推移变得更糟,直到它们成为紧急情况。如果您现在以有序的方式解决性能问题,则可以降低将来在紧急情况下必须应用此功能的风险。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多