【问题标题】:Forcing SQL Server to pre-cache entire database into memory强制 SQL Server 将整个数据库预缓存到内存中
【发布时间】:2014-05-22 20:06:08
【问题描述】:

我们有一个客户端站点,在具有 100+ Gb RAM 的服务器上具有 50Gb SQL 2012 数据库。

在使用应用程序时,SQL Server 在将数据库缓存到内存方面做得很好,但是缓存带来的性能提升发生在第二次运行查询时,而不是第一次。

为了尝试在第一次运行查询时最大化缓存命中,我们编写了一个遍历整个数据库中每个表的每个索引的 proc,运行如下:

SELECT * INTO #Cache 
FROM ' + @tablename + ' WITH (INDEX (' + @indexname + '))'

试图强制读取尽可能多的数据。 我们将它安排为每 15 分钟运行一次,总体而言它做得很好。

在不讨论其他瓶颈、硬件规格、查询计划或查询优化的情况下,是否有人对如何完成同样的任务有更好的想法?

更新
感谢您的建议。删除了“INTO #Cache”。经过测试,它对填充缓冲区没有任何影响。
补充:而不是选择 *,我只选择索引中的键。这(显然)更切中要害,而且速度更快。
补充:读取和缓存约束索引也。

这是当前代码:(希望对其他人有用)

CREATE VIEW _IndexView
as
-- Easy way to access sysobject and sysindex data
SELECT 
so.name as tablename,
si.name as indexname,
CASE si.indid WHEN 1 THEN 1 ELSE 0 END as isClustered,
CASE WHEN (si.status & 2)<>0 then 1 else 0 end as isUnique,
dbo._GetIndexKeys(so.name, si.indid) as Keys,
    CONVERT(bit,CASE WHEN EXISTS (SELECT * FROM sysconstraints sc WHERE object_name(sc.constid) = si.name) THEN 1 ELSE 0 END) as IsConstraintIndex
FROM    sysobjects so
INNER JOIN sysindexes si ON so.id = si.id
WHERE   (so.xtype = 'U')--User Table
AND     ((si.status & 64) = 0) --Not statistics index
AND (   (si.indid = 0) AND (so.name <> si.name) --not a default clustered index
        OR
        (si.indid > 0)
    )
AND si.indid <> 255 --is not a system index placeholder

UNION
SELECT 
so.name as tablename,
si.name as indexname,
CASE si.indid WHEN 1 THEN 1 ELSE 0 END as isClustered,
CASE WHEN (si.status & 2)<>0 then 1 else 0 end as isUnique,
dbo._GetIndexKeys(so.name, si.indid) as Keys,
CONVERT(bit,0) as IsConstraintIndex
FROM    sysobjects so
INNER JOIN sysindexes si ON so.id = si.id
WHERE   (so.xtype = 'V')--View
AND     ((si.status & 64) = 0) --Not statistics index
GO


CREATE PROCEDURE _CacheTableToSQLMemory
@tablename varchar(100)
AS
BEGIN
DECLARE @indexname varchar(100)
DECLARE @xtype varchar(10)
DECLARE @SQL varchar(MAX)
DECLARE @keys varchar(1000)

DECLARE @cur CURSOR
SET @cur = CURSOR FOR
SELECT  v.IndexName, so.xtype, v.keys
FROM    _IndexView v
INNER JOIN sysobjects so ON so.name = v.tablename
WHERE   tablename = @tablename

PRINT 'Caching Table ' + @Tablename
OPEN @cur
FETCH NEXT FROM @cur INTO @indexname, @xtype, @keys
WHILE (@@FETCH_STATUS = 0)
BEGIN
        PRINT '    Index ' + @indexname
        --BEGIN TRAN
            IF @xtype = 'V'
                SET @SQL = 'SELECT ' + @keys + ' FROM ' + @tablename + ' WITH (noexpand, INDEX (' + @indexname + '))' --
            ELSE
                SET @SQL = 'SELECT ' + @keys + ' FROM ' + @tablename + ' WITH (INDEX (' + @indexname + '))' --

            EXEC(@SQL)
        --ROLLBACK TRAN
        FETCH NEXT FROM @cur INTO @indexname, @xtype, @keys
END
CLOSE @cur
DEALLOCATE @cur

END
GO

【问题讨论】:

  • 每 15 分钟一次?如果您的数据库是 50 GB,并且您已经为 SQL Server 提供了 100+ GB 的内存,那么您应该只需要在启动时执行一次。
  • 为什么要插入临时表?
  • @MartinSmith 我怀疑编写它的开发人员发现将所有数据选择到 #temp 表中比在 SSMS 中将输出呈现到一堆网格中更快(可能在非常慢网络连接)。
  • sys.dm_db_index_physical_statsnull 用于大多数参数和“详细”,因为最后一个将读取所有页面。虽然不确定它是否会将所有内容都保存在缓存中。有一些机制不喜欢这样做,以避免淹没缓冲池。
  • _GetIndexKeys?你忘了定义吗?我们和你面临同样的问题。

标签: sql-server caching memory


【解决方案1】:

首先,有一个名为“最小服务器内存”的设置,看起来很诱人。忽略它。 From MSDN:

数据库引擎获取的内存量完全取决于实例上的工作负载。未处理许多请求的 SQL Server 实例可能永远不会达到最小服务器内存。

这告诉我们,设置更大的最小内存不会强制或鼓励任何预缓存。你可能有other reasons to set this,但预填充缓冲池不是其中之一。

那么你可以做些什么来预加载数据呢?这简单。只需设置一个代理作业以从每个表中执行select *。您可以将其安排为“Sql Agent 启动时自动启动”。换句话说,您已经在做的事情非常接近处理此问题的标准方法。

但是,我确实需要提出三个更改:

  1. 不要尝试使用临时表。只需从表中选择。你不需要对结果做任何事情来让 Sql Server 加载你的缓冲池:你需要做的就是选择。临时表可能会强制 sql server 在加载后从缓冲池中复制数据……您最终会(短暂地)存储东西两次
  2. 不要每 15 分钟运行一次。只需在启动时运行一次,然后不理会它。一旦分配,要让Sql Server释放内存需要很多时间。只是不需要一遍又一遍地重新运行。
  3. 不要尝试提示索引。提示就是:提示。 Sql Server 可以随意忽略这些提示,对于没有明确使用索引的查询也会这样做。确保预加载索引的最佳方法是构造一个明显使用该索引的查询。这里的一个具体建议是以与索引相同的顺序对结果进行排序。这通常会帮助 Sql Server 使用该索引,因为它可以“遍历索引”来生成结果。

【讨论】:

  • 或者创建一个系统程序并标记为启动程序;如果您在没有重新启动引擎的情况下重新启动 SQL Server 代理,这将防止不必要的流失。
  • SQL Server 有时会忽略的只是锁定提示。如果无法生成计划,这些索引提示会出错。
【解决方案2】:

这不是一个答案,而是为了补充 Joel Coehoorn 的答案,您可以使用此语句查看缓存中的表数据。使用它来确定所有页面是否都按您的预期保留在缓存中:

USE DBMaint
GO
SELECT COUNT(1) AS cached_pages_count, SUM(s.used_page_count)/COUNT(1) AS total_page_count,
name AS BaseTableName, IndexName,
IndexTypeDesc
FROM sys.dm_os_buffer_descriptors AS bd
INNER JOIN
(
SELECT s_obj.name, s_obj.index_id,
s_obj.allocation_unit_id, s_obj.OBJECT_ID,
i.name IndexName, i.type_desc IndexTypeDesc
FROM
(
SELECT OBJECT_NAME(OBJECT_ID) AS name,
index_id ,allocation_unit_id, OBJECT_ID
FROM sys.allocation_units AS au
INNER JOIN sys.partitions AS p
ON au.container_id = p.hobt_id
AND (au.type = 1 OR au.type = 3)
UNION ALL
SELECT OBJECT_NAME(OBJECT_ID) AS name,
index_id, allocation_unit_id, OBJECT_ID
FROM sys.allocation_units AS au
INNER JOIN sys.partitions AS p
ON au.container_id = p.partition_id
AND au.type = 2
) AS s_obj
LEFT JOIN sys.indexes i ON i.index_id = s_obj.index_id
AND i.OBJECT_ID = s_obj.OBJECT_ID ) AS obj
ON bd.allocation_unit_id = obj.allocation_unit_id
INNER JOIN sys.dm_db_partition_stats s ON s.index_id = obj.index_id AND s.object_id = obj.object_ID
WHERE database_id = DB_ID()
GROUP BY name, obj.index_id, IndexName, IndexTypeDesc
ORDER BY obj.name;
GO

【讨论】:

  • 请注意,如果在运行时正在构建/重建索引,则在索引构建完成之前查询可能不会完成。
【解决方案3】:

用它来替换函数 dbo._GetIndexKeys

(SELECT STRING_AGG(COL_NAME(ic.object_id,ic.column_id), ',') FROM sys.index_columns ic WHERE ic.object_id = so.id AND ic.index_id = si.indid) 作为键,

--dbo._GetIndexKeys(so.name, si.indid) as Keys,

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多