100 Gb以上のRAMを搭載したサーバー上に50 Gb SQL 2012データベースを持つクライアントサイトがあります。
アプリケーションを使用すると、SQLサーバーはdbをメモリにキャッシュするという素晴らしい仕事をしますが、キャッシュによるパフォーマンスの向上は、最初ではなく、2番目のクエリの実行時に発生します。
クエリの初回実行時にキャッシュヒットを最大化するために、DB全体のすべてのテーブルのすべてのインデックスを反復処理するプロシージャを作成し、これを実行します。
SELECT * INTO #Cache
FROM ' + @tablename + ' WITH (INDEX (' + @indexname + '))'
可能な限り多くのデータに対して、大きくてugい、不自然な読み取りを強制する試み。 15分ごとに実行するようにスケジュールされており、一般的に素晴らしい仕事をしています。
他のボトルネック、ハードウェアの仕様、クエリプラン、クエリの最適化について議論することなく、誰もこの同じタスクを達成する方法についてより良いアイデアを持っていますか?
更新
提案をありがとう。 「INTO #Cache」を削除しました。テスト済みであり、バッファの充填に関して違いはありませんでした。
追加:Select *の代わりに、Indexからキーのみを選択しています。これは(明らかに)より正確であり、はるかに高速です。
追加:制約インデックスの読み取りとキャッシュも行います。
現在のコードは次のとおりです:(他の人に役立つことを願っています)
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
まず第一に、魅力的なように見える「最小サーバーメモリ」と呼ばれる設定があります。それを無視します。 MSDNから
データベースエンジンによって取得されるメモリの量は、インスタンスにかかるワークロードに完全に依存します。多くの要求を処理していないSQL Serverインスタンスが、最小サーバーメモリに到達することはありません。
これにより、最小メモリを大きく設定しても、事前キャッシュが強制されたり、推奨されたりすることはありません。 これを設定する他の理由 がありますが、バッファプールを事前に埋めることはそれらの1つではありません。
それでは、データをプリロードするために何ができますか?それは簡単です。エージェントジョブをセットアップしてselect *
すべてのテーブルから。 「SQLエージェントの起動時に自動的に起動する」ようにスケジュールできます。言い換えれば、あなたがすでにやっていることは、これを処理する標準的な方法にかなり近いということです。
ただし、3つの変更を提案する必要があります。
これは答えではありませんが、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