是否可以执行通常的原子 INSERT 操作但异步更新索引?

Muh*_*mer 7 sql postgresql database-indexes

索引使读取速度更快,但写入速度更慢。但是为什么不能进行单次写入并让数据库随时间异步添加索引,并在 INSERT 中缓存直到建立索引?

有这样的数据库吗?

Dai*_*Dai 4

将我的评论转换为答案:

\n
\n

索引使读取速度更快,但写入速度更慢

\n
\n

这种说法过于简单化,而且具有误导性。

\n

索引使数据查找速度更快,因为 DBMS 不需要进行表扫描来查找与谓词(WHERE查询的一部分)匹配的行。索引不会使“读取”速度更快(这完全取决于磁盘 IO 的特征),并且如果使用不当,它们有时甚至会使查询变慢(出于我不会详细介绍的原因)。

\n

我想强调的是,在执行 DML 语句 () 时,写入单个索引甚至多个索引的额外成本实际上INSERT/UPDATE/DELETE/MERGE/etc可以忽略不计!(实际上:外键约束是一个更大的罪魁祸首 - 我注意到您实际上可以通过添加额外的索引来消除外键约束检查的成本!)。索引主要使用 B 树实现(B 树本质上类似于二叉树,不同之处在于每个节点不是只有 2 个子节点,而是可以有许多子节点,因为每个树节点都为所有这些子节点指针提供了未使用的空间,因此插入 B 树的中间不需要将数据在磁盘上移动,这与其他类型的树(如堆树)不同。

\n

考虑一下这个 QA,其中 Postgres 用户(比如你自己)报告在表中插入了 10,000 行。如果没有索引,则花费了 78ms,有索引则花费了 84ms,仅增加了 7.5%,在该规模(6ms!)下,它是如此之小,很可能是舍入错误或由 IO 调度引起。这应该足以证明,如果没有实际的硬数据表明它对您和您的应用程序来说是一个问题,那么您不应该担心它。

\n

我假设您在阅读了像这样的文章后对索引有这种负面印象,这肯定会给人“索引很糟糕”的印象 - 但是虽然该文章中提到的观点没有错,但存在很多问题文章,所以你不应该教条地对待它。(我将在页脚中列出我对该文章的担忧)。

\n
\n

但是为什么不能进行单次写入并让数据库随时间异步添加索引

\n
\n

我假设您的意思是您希望 DMBSINSERT通过简单地将新记录附加到表末尾然后立即返回来执行单行操作,然后在稍后的任意时间点 DBMS 的内务管理系统将更新之后的索引。

\n

这样做的问题是它破坏了ACID 模型的 A、C 和 I 部分。

\n

索引不仅仅用于避免表扫描:它们还用于存储表数据的副本,以方便使用索引且还需要(例如)表的一小部分子集的查询。数据,这显着减少了磁盘读取。因此,RDBMS(和 ISO SQL)允许索引使用INCLUDES子句包含非索引数据。

\n

考虑这种情况:

\n
CREATE INDEX IX_Owners ON cars ( ownerId ) INCLUDE ( colour );\nCREATE INDEX IX_Names  ON people ( name ) INCLUDE ( personId, hairColour );\n\nGO;\n\nSELECT\n    people.name,\n    people.hairColour,\n    cars.colour\nFROM\n    cars\n    INNER JOIN people ON people.personId = cars.ownerId\nWHERE\n    people.name LIKE \'Steve%\'\n
Run Code Online (Sandbox Code Playgroud)\n

上述查询不需要读取磁盘上的cars或表。peopleDBMS 将能够仅使用索引中的数据来完全回答查询 - 这很好,因为索引往往存在于磁盘上的少量页面中,这些页面往往位于邻近位置,这对性能有好处,因为这意味着它将使用顺序 IO ,其扩展性比随机 IO好得多。

\n

RDBMS 将执行索引的字符串前缀索引扫描people.IX_Names以获取所有personId( 和) 值,然后它将在索引中hairColour查找这些值并能够从索引内数据的副本中获取无需直接读取表格。personIdcars.IX_Ownerscar.colourIX_Owners

\n
\n

现在,假设另一个数据库客户端刚刚完成将大量记录插入到cars和/或people表中COMMIT TRANSACTION,并且 RDMBS 使用您的想法,即稍后在需要时才更新索引,那么如果同一个数据库客户端从上面重新运行查询,它将返回陈旧的数据(即错误的数据),因为查询使用索引,但索引是旧的。

\n
\n

除了使用索引树节点来存储表数据的副本以避免非近端磁盘 IO 之外,许多 RDBMS 还使用索引树来存储整个副本- 甚至是表数据的多个副本,以实现其他场景,例如列式数据存储索引视图- 这两个功能绝对需要使用表数据自动更新索引。

\n
\n

有这样的数据库吗?

\n
\n

是的,它们存在 - 但它们没有被广泛使用(或者它们是小众),因为对于绝大多数应用程序来说,由于上述原因,这完全是不受欢迎的行为。

\n

有一些分布式数据库是围绕最终一致性设计的,但是客户端(和整个应用程序代码)需要在设计时考虑到这一点,并且必须重新设计以数据为中心的应用程序以支持最终一致性,这是一个巨大的 PITA这就是为什么你只看到它们被用在真正的大型系统(如 Facebook、Google 等)中,在这些系统中,可用性(正常运行时间)比用户看到几分钟的陈旧数据更重要。

\n
\n

脚注:

\n

关于本文: https: //use-the-index-luke.com/sql/dml/insert

\n
\n

表上的索引数量是影响插入性能的最主要因素。表的索引越多,执行速度就越慢。insert 语句是唯一不能直接从索引中受益的操作,因为它没有 where 子句。

\n
\n

我不同意。我认为外键约束(和触发器)更有可能对 DML 操作产生更大的有害影响。

\n
\n

向表中添加新行涉及几个步骤。首先,数据库必须找到存储行的位置。对于没有特定行顺序的常规堆表\xe2\x80\x94,数据库可以采用任何具有足够可用空间的表块。这是一个非常简单和快速的过程,大部分在主内存中执行。之后数据库要做的就是将新条目添加到相应的数据块中。

\n
\n

我同意这一点。

\n
\n

如果表上有索引,数据库必须确保也可以通过这些索引找到新条目。因此,它必须将新条目添加到该表上的每个索引中。因此,索引的数量是插入语句成本的乘数。

\n
\n

这是事实,但我不知道我是否同意它是插入成本的“乘数”。

\n

例如,考虑一个包含数百nvarchar(1000)列和几int列的表 - 并且每列都有单独的索引int(没有INCLUDE列)。如果您一次插入 100 兆字节大小的行(使用语句INSERT INTO ... SELECT FROM),更新这些索引的成本int很可能比表数据需要的 IO 少得多。

\n
\n

此外,向索引添加一项比向堆结构中插入一项要昂贵得多,因为数据库必须保持索引顺序和树平衡。这意味着新条目不能写入任何属于特定叶节点的块\xe2\x80\x94。虽然数据库使用索引树本身来找到正确的叶节点,但它仍然需要读取一些索引块来进行树遍​​历。

\n
\n

我强烈不同意这一点,尤其是第一句话:“向索引添加条目比向堆结构插入条目要昂贵得多”。

\n

如今 RDBMS 中的索引始终基于 B 树,而不是二叉树或堆树。B 树本质上类似于二叉树,只不过每个节点都有可容纳数十个子节点指针的内置空间,并且 B 树仅在节点填满其内部子节点指针列表时才会重新平衡,因此 B 树节点插入将相当大比文章中所说的便宜,因为每个节点都有足够的空闲空间用于新的插入,而无需重新平衡自身或任何其他相对昂贵的操作(此外,DBMS可以单独且独立于任何 DML 语句进行索引维护)。

\n

关于 DBMS 如何需要遍历 B 树来查找要插入的节点,该文章是正确的,但索引节点在磁盘上有效排列,例如将相关节点保留在同一磁盘页面中,从而最大限度地减少索引 IO读取假设它们尚未首先加载到内存中)。如果索引树太大而无法存储在内存中,RDBMS 始终可以在内存中保留“元索引”,这样它就可以立即找到正确的 B 树索引,而无需从根遍历 B 树。

\n
\n

一旦识别出正确的叶节点,数据库就会确认该节点中有足够的可用空间。如果不是,数据库将分割叶节点并在旧节点和新节点之间分配条目。此过程还会影响相应分支节点中的引用,因为也必须复制该引用。不用说,分支节点也可能会耗尽空间,因此可能也必须进行拆分。在最坏的情况下,数据库必须将所有节点分裂到根节点。这是树获得额外层并深度增长的唯一情况。

\n
\n

实际上这不是问题,因为 RDBMS 的索引维护将确保每个索引节点中有足够的可用空间。

\n
\n

毕竟,索引维护是插入操作中最昂贵的部分。这在图 8.1\xe2\x80\x9c按索引数量插入性能\xe2\x80\x9d 中也可见:如果表没有任何索引,则执行时间几乎不可见。尽管如此,添加单个索引足以将执行时间增加一百倍。每个额外的索引都会进一步减慢执行速度。

\n
\n

我认为这篇文章暗示(暗示?陈述?)每个 DML 都会进行索引维护,这是不诚实的。这不是真的。一些早期 dBase 时代的数据库可能就是这种情况,但现代 RDBMS(如Postgres、MS SQL Server、Oracle 等)肯定不是这种情况。

\n
\n

仅考虑插入语句,最好完全避免索引\xe2\x80\x94,这会产生迄今为止最好的插入性能。

\n
\n

再说一次,文章中的这一说法并没有错,但它基本上是在说,如果你想要一个干净整洁的房子,你应该扔掉你所有的财产。索引是生活中的一个事实。

\n
\n

然而,没有索引的表在现实世界的应用程序中是相当不现实的。您通常希望再次检索存储的数据,因此需要索引来提高查询速度。即使只写日志表通常也有主键和相应的索引。

\n
\n

的确。

\n
\n

尽管如此,没有索引的性能非常好,因此在加载大量数据时暂时删除所有索引是有意义的\xe2\x80\x94,前提是同时任何其他 SQL 语句不需要索引。这可以显着提高速度,从图表中可以看出,事实上,这也是数据仓库中的常见做法。

\n
\n

同样,对于现代 RDBMS,这是没有必要的。如果您进行批量插入,那么在表数据修改完成之前, RDBMS 不会更新索引,因为批量索引更新比许多单独更新便宜。同样,如果事务中没有后续查询依赖于更新的索引,我预计显式内部的多个 DML 语句和查询BEGIN TRANSACTION 可能会导致索引更新延迟。

\n

但我对那篇文章最大的问题是,作者对有害的 IO 性能做出了这些大胆的主张,但没有提供任何引用,甚至没有提供他们自己运行的基准测试。更令人难堪的是,他们再次发布了带有任意数字的条形图,没有任何引用或原始基准数据以及如何重现其结果的说明。始终要求您阅读的任何声明中都有引用和证据:因为任何人在没有证据的情况下都应该接受的唯一声明是逻辑公理 - 而关于数据库索引 IO 成本的定量声明不是逻辑公理:)

\n