内存优化表索引大小比数据大 10 倍

Yor*_*rik 5 sql-server memory-optimized-tables sql-server-2017

在我们的一台生产服务器上,我发现索引大小是数据大小的 10 倍。

这些服务器和模式与类似的工作负载相同。

CREATE TABLE [dbo].[process_aggregation_close]
(
    [organization_id] [bigint] NOT NULL,
    [computer_id] [uniqueidentifier] NOT NULL,
    [process_id] [int] NOT NULL,
    .
    .
INDEX [ix_process_aggregation_memory_computer_id_process_id] NONCLUSTERED 
(
    [organization_id] ASC,
    [computer_id] ASC,
    [process_id] ASC
)
)WITH ( MEMORY_OPTIMIZED = ON , DURABILITY = SCHEMA_ONLY )
GO
Run Code Online (Sandbox Code Playgroud)

表是临时表,它们涉及非常频繁的插入/删除操作。

sys.dm_db_xtp_table_memory_stats

Microsoft SQL Server 2017 CU9 标准版。

所有数据库都处于完全恢复模式。我认为它与checkpoint无关,因为这些表仅是模式持久的。(无论如何,我尝试了一个没有效果的手动检查点)。

这些内存表是暂存的,并且总是存在插入和删除活动。我监控了五个负载相似的 SQL Server。此行为仅发生在一个上。

Ran*_*gen -1

大小差异是由于索引中的行版本控制和内存表的垃圾收集器的工作方式造成的。

\n\n

查看用于内存优化表的垃圾收集过程:

\n\n
\n

如果(例如)删除大量行后\n 没有事务活动且不存在内存压力,则在事务活动恢复\n 或存在内存压力之前\n 不会对已删除的行进行垃圾回收\n

\n
\n\n

好吧,这对于索引来说是有意义的,但是为什么数据被清除而索引没有被清除呢?

\n\n

我有根据的猜测是,从索引中删除行版本控制与从表中删除覆盖的数据是不同的过程。更多内容请参见此处

\n\n

举个例子,其中一个查询插入 1M 行并对由问题中的三列组成的表执行完全删除。

\n\n
-- insert data into the tables  \nSET NOCOUNT ON;\nDECLARE @I int =0;\nWHILE @I < 1000000\nBEGIN\n    INSERT INTO dbo.MemOptimizedTable([organization_id], [computer_id], [process_id])\n    VALUES (cast(@I as bigint) + 2147483647, NEWID(),@I) ;\n    SET @I +=1;\nEND\n-- Delete everything\n DELETE FROM  dbo.MemOptimizedTable;\n\nSELECT object_id, memory_used_by_table_kb, memory_used_by_indexes_kb \nFROM sys.dm_db_xtp_table_memory_stats;\n
Run Code Online (Sandbox Code Playgroud)\n\n

第一次运行查询后

\n\n
object_id   memory_used_by_table_kb memory_used_by_indexes_kb\n1045578763          62501           58852\n
Run Code Online (Sandbox Code Playgroud)\n\n

运行查询 3 次给了我们这个起点,索引大小已超出表大小

\n\n
object_id   memory_used_by_table_kb memory_used_by_indexes_kb\n965578478           62501           78464\n
Run Code Online (Sandbox Code Playgroud)\n\n

当再次运行1M记录的插入时,表使用的mem增长

\n\n
--Insert running \nobject_id   memory_used_by_table_kb memory_used_by_indexes_kb\n965578478           86715            88471\n
Run Code Online (Sandbox Code Playgroud)\n\n

并成长

\n\n
--Insert running\nobject_id   memory_used_by_table_kb memory_used_by_indexes_kb\n965578478           123628           104662\n
Run Code Online (Sandbox Code Playgroud)\n\n

直到插入完成

\n\n
--Insert done\nobject_id   memory_used_by_table_kb memory_used_by_indexes_kb\n965578478           62500            79543\n
Run Code Online (Sandbox Code Playgroud)\n\n

索引增长,表使用的内存在插入后被删除并保持不变。插入完成后,会触发一个进程来删除表使用的多余内存。

\n\n

这绝不是完整的解释,但可以作为一个起点。

\n\n

我们可以做什么?

\n\n

根据这篇文章,\xe2\x80\x99s 也没有办法强制垃圾收集发生。

\n\n

你可以选择

\n\n
    \n
  • 什么也不做,让 SQL Server 处理
  • \n
  • 删除并创建索引以重置内存使用情况(如果表上有另一个索引)
  • \n
  • ...
  • \n
\n\n

删除和创建索引的命令示例

\n\n
ALTER TABLE  dbo.table\n       DROP INDEX [ix_process_aggregation_memory_computer_id_process_id];  \n\nALTER TABLE  dbo.table\n       ADD INDEX [ix_process_aggregation_memory_computer_id_process_id] ([organization_id] ASC,[computer_id] ASC, [process_id] ASC);\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

示例中使用的表的建表语句

\n\n
 CREATE TABLE dbo.MemOptimizedTable(   \n  [organization_id] [bigint] PRIMARY KEY NONCLUSTERED NOT NULL,\n    [computer_id] [uniqueidentifier] NOT NULL,\n    [process_id] [int] NOT NULL,\nINDEX [ix_process_aggregation_memory_computer_id_process_id] NONCLUSTERED \n(\n    [organization_id] ASC,\n    [computer_id] ASC,\n    [process_id] ASC\n)\n)WITH ( MEMORY_OPTIMIZED = ON , DURABILITY = SCHEMA_ONLY)\nGO\n
Run Code Online (Sandbox Code Playgroud)\n