Подтвердить что ты не робот

Принуждение SQL Server к предварительному кэшированию всей базы данных в память

У нас есть клиентский сайт с базой данных SQL Server на 50 ГБ на сервере с ОЗУ более 100 ГБ.

Как приложение используется, SQL-сервер делает отличную работу по кэшированию db в память, но увеличение производительности от кеширования происходит в SECOND-времени, когда выполняется запрос, а не первый.

Чтобы попытаться максимизировать кеш-запросы при первом запуске запросов, мы написали proc, который выполняет итерацию по каждому индексу каждой таблицы во всей БД, выполняя это:

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

В попытке заставить большой, уродливый, изобретательный читать как можно больше данных. Мы планируем запускать каждые 15 минут, и он отлично работает в целом.

Не обсуждая другие узкие места, спецификации оборудования, планы запросов или оптимизацию запросов, есть ли у кого-нибудь лучшие идеи о том, как выполнить эту же задачу?

ОБНОВЛЕНИЕ
Спасибо за предложения. Удален "INTO #Cache". Протестировано, и это не повлияло на заполнение буфера.
Добавлено: вместо Select *, я выбираю ТОЛЬКО ключи из Индекса. Это (очевидно) является более точным и намного быстрее.
Добавлено: индексы чтения и кэша.

Здесь текущий код: (надеюсь, это полезно для кого-то еще)

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
4b9b3361

Ответ 1

Прежде всего, существует параметр "Minumum Server Memory", который выглядит заманчивым. Игнорируй это. От MSDN:

Объем памяти, приобретенный механизмом Database Engine, полностью зависит от рабочей нагрузки, помещенной в экземпляр. Экземпляр SQL Server, который не обрабатывает многие запросы, никогда не сможет достичь минимальной памяти сервера.

Это говорит нам о том, что установка большей минимальной памяти не будет усиливать или поощрять предварительное кэширование. Возможно, у вас есть другие причины, чтобы установить это, но предварительная заполнение пула буферов не является одним из них.

Итак, что вы можете сделать для предварительной загрузки данных? Это легко. Просто настройте задание агента, чтобы сделать select * из каждой таблицы. Вы можете запланировать его "Начать автоматически при запуске Sql Agent". Другими словами, то, что вы уже делаете, довольно близко к стандартному способу справиться с этим.

Однако мне нужно предложить три изменения:

  • Не пытайтесь использовать временную таблицу. Просто выберите из таблицы. Вам не нужно ничего делать, чтобы получить Sql Server для загрузки пула буферов: все, что вам нужно сделать, это выбрать. Временная таблица может заставить сервер sql копировать данные из пула буферов после загрузки... вы в конечном итоге (кратко) храните вещи дважды.
  • Не запускайте его каждые 15 минут. Просто запустите его один раз при запуске, а затем оставьте его в покое. После выделения требуется много времени, чтобы получить Sql Server для выпуска памяти. Это просто не нужно было повторно запускать это снова и снова.
  • Не пытайтесь подсказать индекс. Подсказки - вот что: подсказки. Sql Server может игнорировать эти подсказки, и он будет делать это для запросов, которые не имеют четкого использования для индекса. Лучший способ убедиться, что индекс предварительно загружен, - это построить запрос, который явно использует этот индекс. Одно конкретное предложение здесь - упорядочить результаты в том же порядке, что и индекс. Это часто помогает Sql Server использовать этот индекс, потому что тогда он может "ходить по индексу" для получения результатов.

Ответ 2

Это не ответ, но, чтобы дополнить ответ Джоэла Коэхорна, вы можете посмотреть данные таблицы в кеше, используя этот оператор. Используйте это, чтобы определить, находятся ли все страницы в кеше, как вы ожидали:

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