在繁重的并发 INSERT 工作负载下,有什么方法可以减少 sysallocunits 争用?

Max*_*oke 6 sql-server sql-server-2012

我有一个被大量独立会话插入的数据库。每个会话写入不同的表,因此您会天真地期望它们都将并行进行。但是,通过检查sys.dm_os_wait_statssys.dm_os_latch_stats,我发现我实际上在DBCC PAGE报告为类型 11 的页面(即 PFS 页面)上遇到了大量的 PAGELATCH_* 等待。

所以基本上整个过程都变慢了,因为每个线程都在与其他线程竞争以分配新页面。

这种情况下的标准建议是:

我做了这两件事,它确实减少了 PFS 页面上的争用,但我现在看到PAGELATCH_EX页面上的一些争用DBCC PAGE告诉我是属于对象 ID 7 的数据页,即sysallocunits.

关于sysallocunits系统表用途的在线信息不多,我也找不到任何关于它的争用的信息。在改变这个表时,我似乎刚刚设法用瓶颈替换了我的分配瓶颈!

我想我可以通过将每个表放在自己的数据库中(然后不共享sysallocunits表)来减少争用,但这将是我宁愿避免的相当重要的架构更改。

有什么方法可以在sysallocunits不创建额外数据库的情况下减少争用?

编辑:根据要求,这是DBCC PAGE显示争用的页面之一的输出:

PAGE: (1:476)


BUFFER:


BUF @0x0000006143763A80

bpage = 0x0000004064D20000          bhash = 0x0000006301FCBAC0          bpageno = (1:476)
bdbid = 15                          breferences = 2047                  bcputicks = 48351
bsampleCount = 90                   bUse1 = 32392                       bstat = 0x10b
blog = 0x7a7a7a7a                   bnext = 0x0000000000000000          

PAGE HEADER:


Page @0x0000004064D20000

m_pageId = (1:476)                  m_headerVersion = 1                 m_type = 1
m_typeFlagBits = 0x0                m_level = 0                         m_flagBits = 0x0
m_objId (AllocUnitId.idObj) = 7     m_indexId (AllocUnitId.idInd) = 0   Metadata: AllocUnitId = 458752
Metadata: PartitionId = 458752      Metadata: IndexId = 1               Metadata: ObjectId = 7
m_prevPage = (1:475)                m_nextPage = (0:0)                  pminlen = 69
m_slotCnt = 34                      m_freeCnt = 5546                    m_freeData = 3089
m_reservedCnt = 0                   m_lsn = (18391:146685:11)           m_xactReserved = 0
m_xdesId = (0:588003648)            m_ghostRecCnt = 0                   m_tornBits = 208117691
DB Frag ID = 1                      

Allocation Status

GAM (1:2) = ALLOCATED               SGAM (1:3) = NOT ALLOCATED          PFS (1:1) = 0x40 ALLOCATED   0_PCT_FULL
DIFF (1:6) = CHANGED                ML (1:7) = NOT MIN_LOGGED           
Run Code Online (Sandbox Code Playgroud)

编辑 2:关于实际插入过程的更多细节。基本上:

  • 我最终想要插入大约 200 个表(这些表被称为 dbo.LastTradePrice、dbo.Volume 等)。这些表都有一个(SecurityID, Date, FromSourceID)三元组的聚集索引。这些表可以有数百万行。
  • 我有 200 个同时连接到 SQL 服务器,每个连接都批量插入不同的临时表(stage.LastTradePrice、stage.Volume 等)。这些表都在自动递增的主键上有聚集索引ID。每个临时表最多可容纳 300,000 行。
  • 然后我有多达 32 个连接,它们尝试同时将每个临时表合并到相应的普通表中。对于每个表,此合并过程涉及对临时表进行排序(我猜这会溢出到 tempdb)并将排序结果的子集存储到表变量中(另一个 tempdb 命中?)。然后使用表变量中的数据来更新主表,该过程涉及 INSERT 和 UPDATE 操作。

Tho*_*ser 5

根据您提供的工作负载描述,您给分配单元带来了很大的压力,特别是因为您试图维护排序的负载(使用 IDENTITY 列和簇)。跟踪发生的分配会给元数据带来很大的压力 - 当然,除非您处于批量模式。

以下是潜在的解决方案:

  • 加载到堆中。加载完成后重建索引(如果需要)。这还允许在单个表上并发 BULK INSERT。如果您使用的是 2012 年,则更喜欢带有缓存的 SEQUENCER 而不是 IDENTITY(IDENTITY 列有瓶颈)
  • 使用跟踪标志 610 在集群索引上启用最小日志记录负载(如果您必须使用集群)
  • 将每个表放在单独的文件组中(这会分散分配)。仅将此作为最后的手段
  • 如果打算将临时表合并到最终表中,为什么不直接合并而不经过表变量的中间步骤呢?
  • 如果您使用聚集索引,是否可以按需要进行合并的相同键进行排序(删除 tempdb 中稍后的排序)
  • 当加载到排序表(如上所述)时,如果输入已经排序,请使用 ORDER 提示。这减少了分配映射上的争用(并且还允许最少记录的负载)