SQL Планы обслуживания 1С
backup1:
- Резервное копирование базы данных.
- Очистка после обслуживания.
- Очистка журнала
defrag1 (после 3:00 каждые 3 дня):
- Проверка целостности базы данных.
- Выполнение инструкции T-SQL (DBCC FREEPROCCACHE).
- Восстановление индекса > 30%. (Сохранять индекс = Не перестраивать)
- Реорганизация индекса > 15%.
- Обновить статистику.
- Сжатие логов журнала транзакции.
Восстановление по модели simple:
Ваша база данных находится в SIMPLE режиме восстановления. Что это означает? Это означает, что бэкапы бывают только полные, журналы транзакций бэкапировать не нужно, производительность в этом смысле максимальная, но восстановиться можно только на точку бэкапа. Восстановление базы “на указанный момент времени” невозможно. Следовательно, еженочно (или чаще, в зависимости от потребности) мы должны снимать свеженькую копию нашей базы данных и складывать ее в надежное место, и обязательно не в то, в котором лежит наша основная база данных. В целом, использование модели SIMPLE для реальных рабочих баз оправданно только в случаях исключительно высокой нагрузки и незначительности события потери данных с момента последнего бэкапа.
Кроме того, затрону сразу вопрос работы с журналом транзакций. Так как нам журнал тразакций в этом режиме восстановления не очень-то и помощник, нет необходимости в его резервном копировании – вся информация из журнала уже ушла в бэкап. Обычно журналы в такой модели восстановления почти не растут, но иногда полезно его усекать. Например, после массового изменения данных эта операция может быть крайне полезна с точки зрения места, занимаемого журналом транзакций. Если диск с журналом переполнится – проблемы функционирования базы данных вам обеспечены.
Усечение данных может быть проведено, как стандартным мастером настройки плана обслуживания, так и с помощью несложно скрипта на T-SQL:
1 2 |
DBCC SHRINKFILE (DatabaseName, 1); GO |
Этот скрипт уменьшит размер лог-файла базы данных до начального размера (по умолчанию, чаще всего это будет 1 Мб). Однако, не стоит выполнять эту операцию постоянно. В идеале, ваши файлы не должны изменяться в размере в ходе планомерной работы, но об этом мы поговорим как-нибудь в другой раз.
Восстановление по модели full
Давайте рассмотрим основные принципы настройки резервного копирования и управления размером журнала лога транзакций с точки зрения самого массового варианта – полной модели восстановления БД.
Полная модель восстановления отличается от простой тем, что в течение всей работы базы данных мы можем (а еще точнее – ДОЛЖНЫ!) делать бэкапы лога транзакций, тем самым обеспечивая возможность восстановления БД между точками основных бэкапов или откаты на конкретные промежутки времени функционирования базы, а также обеспечивая освобождение места в файле журнала (усечение). Если этого не делать, он будет расти постоянно до тех пор, пока однажды не заполнит все доступное ему место (либо на диске, либо до ограничения, заданного в СУБД). Последствия кажутся очевидными, и не самыми приятными.
С точки зрения наличия полных бэкапов – безусловно, минимальная граница – это как правило те же одни сутки. Разностные бэкапы базы данных – это возможность сохранить только изменения, произошедшие с момента последнего бэкапа. Это позволяет достаточно быстро и оперативно проводить резервное копирование базы данных, при этом использовать достаточно быстрое восстановление БД до нужного состояния.
Резервные копии журнала транзакций могут выполняться с нужной вам периодичностью в течение дня, подробнее чем разностное копирование БД. Мы рекомендуем, обычно, выбирать степень подробности копий около ¼ от времени создания разностных копий БД.
Как уже было сказано выше, при выполнении резервной копии журнала транзакций базы данных в полной модели он усечется автоматически (только не путайте усечение со сжатием!).
Пересчет статистики и работа с индексами
Достаточно ошибочной является сложившаяся практика работы с индексами и статистикой у наших клиентов. Очень часто мы сталкиваемся вообще с полным отсутствием этих процедур в планах обслуживания баз данных. Часто они выполняются в неправильном порядке. Часто просто неоптимально (например, одновременно!).
Правильная последовательность действий выглядит так:
-
Определяем степень фрагментированности индекса
-
Если индекс маленький или мало фрагментирован, запускаем процедуру реорганизации индекса и пересчета статистики.
-
В противном случае запускам процедуру перестроения индекса. Процедура перестроения индекса фактически приведет к обновлению статистики, поэтому пересчитывать статистику после полного перестроения индекса не нужно.
-
- Пересчитываем всю остальную статистику, где это требуется.
Если рассмотреть мини-скрипт для пересчета статистики и перестроения индексов (не претендуем на супер полноту и универсальность), то выглядеть он будет примерно так (с перебором индексов через курсор):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
DECLARE @SQL NVARCHAR(MAX) DECLARE @MIN_IND_SIZE integer = 128 DECLARE @MIN_FRAGMENTATION_LEVEL integer = 10 DECLARE @CRITICAL_FRAGMENTATION_LEVEL integer = 30 DECLARE currentIndex CURSOR LOCAL READ_ONLY FORWARD_ONLY FOR SELECT 'ALTER INDEX [' + ind.name + N'] ON [' + SCHEMA_NAME(obj.[schema_id]) + '].[' + obj.name + '] ' + CASE WHEN stat.avg_fragmentation_in_percent > @CRITICAL_FRAGMENTATION_LEVEL THEN 'REBUILD WITH (SORT_IN_TEMPDB = ON, ONLINE = ON)' ELSE 'REORGANIZE' END + ';' FROM ( SELECT stat.[object_id], stat.index_id, avg_fragmentation_in_percent = MAX(stat.avg_fragmentation_in_percent) FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'DETAILED') stat WHERE stat.page_count > @MIN_IND_SIZE AND stat.index_id > 0 AND stat.avg_fragmentation_in_percent > @MIN_FRAGMENTATION_LEVEL GROUP BY stat.[object_id], stat.index_id ) stat JOIN sys.indexes ind WITH(NOLOCK) ON stat.[object_id] = ind.[object_id] AND stat.index_id = ind.index_id JOIN sys.objects obj WITH(NOLOCK) ON obj.[object_id] = stat.[object_id] OPEN currentIndex FETCH NEXT FROM currentIndex INTO @SQL WHILE @@FETCH_STATUS = 0 BEGIN print @sql EXEC sys.sp_executesql @SQL FETCH NEXT FROM cur INTO @SQL END CLOSE currentIndex DEALLOCATE currentIndex |
Обратите внимание на использование tempdb, а также на сохранение индекса доступным во время перестроения – в зависимости от редакции вашей СУБД последняя функция может быть недоступна.
Обрезать логи rp-ldf-cute64 (every Fri 06:00):
Планы обслуживания > Мастер планов обслуживания > Очистка журнала > Выполнение инструкции T-SQL:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
--------------------------------------------- -- НАСТРАИВАЕМЫЕ ПАРАМЕТРЫ -- Условие для выборки, '%' - все базы данных DECLARE @namelike varchar(100) = '%' ------------------------------------------- -- СЛУЖЕБНЫЕ ПЕРЕМЕННЫЕ DECLARE @SQLString NVARCHAR(4000) DECLARE @DBName varchar(100) DECLARE @DBLogName varchar(100) DECLARE @subject as NVARCHAR(1000) = '' DECLARE @finalmassage as NVARCHAR(4000) = '' ------------------------------------------- -- ТЕЛО СКРИПТА -- Отберем базы для выполнения операций DECLARE DBcursor CURSOR FOR ( SELECT t_Name.name as DatabaseName, t_LogName.name as DatabaseLogName FROM sys.databases as t_Name Inner join sys.master_files as t_LogName on t_Name.database_id = t_LogName.database_id WHERE t_Name.database_id > 4 AND t_Name.state_desc = 'ONLINE' -- база должна быть в сети AND t_Name.name like @namelike -- база должна содержать указанное слово AND t_LogName.type = 1 ) -- Цикл по всем базам, попавшим в выборку OPEN DBcursor FETCH NEXT FROM DBcursor INTO @DBName, @DBLogName WHILE @@FETCH_STATUS = 0 BEGIN -- Переводим базы в простую модель восстановления SET @SQLString = 'ALTER DATABASE [' + @DBName + '] SET RECOVERY SIMPLE WITH NO_WAIT;' PRINT @SQLString BEGIN TRY EXEC sp_executesql @SQLString END TRY BEGIN CATCH -- Ошбика выполнения операции SET @finalmassage = @finalmassage + 'ОШИБКА перевода базы данных ' + @DBName + ' в простую модель восстановоления ' + CHAR(13) + CHAR(13) + 'Код ошибки: ' + CAST(ERROR_NUMBER() as nvarchar(10)) + CHAR(13) + CHAR(13) + 'Текст ошибки: ' + ERROR_MESSAGE() + CHAR(13) + CHAR(13) + 'Текст T-SQL:' + CHAR(13) + @SQLString + CHAR(13) + CHAR(13) END CATCH; -- Запускаем сжатие лога базы данных SET @SQLString = 'USE [' + @DBName + ']; DBCC SHRINKFILE([' + @DBLogName + '], 16, truncateonly); ALTER DATABASE [' + @DBName + '] MODIFY FILE (NAME = [' + @DBLogName + '], SIZE = 64MB);' PRINT @SQLString BEGIN TRY EXEC sp_executesql @SQLString END TRY BEGIN CATCH -- Ошбика выполнения операции SET @finalmassage = @finalmassage + 'ОШИБКА сжатия базы данных ' + @DBName + ' в простую модель восстановоления ' + CHAR(13) + CHAR(13) + 'Код ошибки: ' + CAST(ERROR_NUMBER() as nvarchar(10)) + CHAR(13) + CHAR(13) + 'Текст ошибки: ' + ERROR_MESSAGE() + CHAR(13) + CHAR(13) + 'Текст T-SQL:' + CHAR(13) + @SQLString + CHAR(13) + CHAR(13) END CATCH; -- Следующий элемент цикла FETCH NEXT FROM DBcursor INTO @DBName, @DBLogName END CLOSE DBcursor; DEALLOCATE DBcursor; GO |
Query to see Index Fragmentation on all databases without using SP or temp tables
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
--The first parameter is Database ID that you could found in sys.databases table. --SELECT * FROM sys.databases DECLARE @dbid int = 9 SELECT dbschemas.[name] as 'Schema', dbtables.[name] as 'Table', dbindexes.[name] as 'Index', indexstats.avg_fragmentation_in_percent, indexstats.page_count, CASE WHEN indexstats.page_count <= 100 OR indexstats.avg_fragmentation_in_percent < 10 THEN '--Do nothing. It''s good' WHEN indexstats.avg_fragmentation_in_percent BETWEEN 10 AND 30 THEN 'ALTER INDEX '+dbindexes.[name]+' ON '+dbschemas.[name]+'.'+dbtables.[name]+' REORGANIZE;' ELSE 'ALTER INDEX '+dbindexes.[name]+' ON '+dbschemas.[name]+'.'+dbtables.[name]+' REBUILD;' END FROM sys.dm_db_index_physical_stats (@dbid, NULL, NULL, NULL, NULL) AS indexstats JOIN sys.tables dbtables ON dbtables.[object_id] = indexstats.[object_id] JOIN sys.schemas dbschemas ON dbtables.[schema_id] = dbschemas.[schema_id] JOIN sys.indexes AS dbindexes ON dbindexes.[object_id] = indexstats.[object_id] AND indexstats.index_id = dbindexes.index_id WHERE indexstats.database_id = @dbid ORDER BY indexstats.avg_fragmentation_in_percent DESC |
Следить, чтобы статистика была максимально актуальной.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
select o.name AS [TableName], a.name AS [StatName], a.rowmodctr AS [RowsChanged], STATS_DATE(s.object_id, s.stats_id) AS [LastUpdate], o.is_ms_shipped, s.is_temporary, p.* from sys.sysindexes a inner join sys.objects o on a.id = o.object_id and o.type = 'U' and a.id > 100 and a.indid > 0 left join sys.stats s on a.name = s.name left join ( SELECT p.[object_id] , p.index_id , total_pages = SUM(a.total_pages) FROM sys.partitions p WITH(NOLOCK) JOIN sys.allocation_units a WITH(NOLOCK) ON p.[partition_id] = a.container_id GROUP BY p.[object_id] , p.index_id ) p ON o.[object_id] = p.[object_id] AND p.index_id = s.stats_id order by a.rowmodctr desc, STATS_DATE(s.object_id, s.stats_id) ASC |
Следить за фрагментацией индексов
1 2 3 4 5 6 7 8 9 10 11 |
SELECT OBJECT_NAME(ips.OBJECT_ID) ,i.NAME ,ips.index_id ,index_type_desc ,avg_fragmentation_in_percent ,avg_page_space_used_in_percent ,page_count FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'SAMPLED') ips INNER JOIN sys.indexes i ON (ips.object_id = i.object_id) AND (ips.index_id = i.index_id) ORDER BY avg_fragmentation_in_percent DESC |
Is there a script that I can use to check the index fragmentation?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
DECLARE @DatabaseID int SET @DatabaseID = DB_ID() SELECT DB_NAME(@DatabaseID) AS DatabaseName, schemas.[name] AS SchemaName, objects.[name] AS ObjectName, indexes.[name] AS IndexName, objects.type_desc AS ObjectType, indexes.type_desc AS IndexType, dm_db_index_physical_stats.partition_number AS PartitionNumber, dm_db_index_physical_stats.page_count AS [PageCount], dm_db_index_physical_stats.avg_fragmentation_in_percent AS AvgFragmentationInPercent FROM sys.dm_db_index_physical_stats (@DatabaseID, NULL, NULL, NULL, 'LIMITED') dm_db_index_physical_stats INNER JOIN sys.indexes indexes ON dm_db_index_physical_stats.[object_id] = indexes.[object_id] AND dm_db_index_physical_stats.index_id = indexes.index_id INNER JOIN sys.objects objects ON indexes.[object_id] = objects.[object_id] INNER JOIN sys.schemas schemas ON objects.[schema_id] = schemas.[schema_id] WHERE objects.[type] IN('U','V') AND objects.is_ms_shipped = 0 AND indexes.[type] IN(1,2,3,4) AND indexes.is_disabled = 0 AND indexes.is_hypothetical = 0 AND dm_db_index_physical_stats.alloc_unit_type_desc = 'IN_ROW_DATA' AND dm_db_index_physical_stats.index_level = 0 AND dm_db_index_physical_stats.page_count >= 1000 |