Загрузка...
Перед выполнение всего того, что здесь описано сделайте backup баз данных и проверьте работоспособность бэкапов!
Изначально было неоходимо просто уменьшить транзакционные лог-файлы (ldf) баз данных, которые сильно разраслись, но впоследствии могут оставаться процентов на 90 пустыми занимая почти всё место на дисках.
Если на сервере 1 - 2 базы, то можно сильно не заморачиваться и сделать всё это вручную. Запускаем ssms (Microsoft SQL Server Management Studio), подключаемся к субд, открываем список баз данных и правой кнопкой мыши кликаем на нужную базу данных затем переходим по диалоговым окнам: Задачи->Сжать->Файлы и кликаем на Файлы или в английском варианте: Tasks->Shrink->Files - кликаем на Files:

и в английском варианте:

В окне Сжатие файла (Shrink File) в раскладке Тип Файла (File type) выбираем, либо Журнал (Log), либо Данные (Data), выберем Журнал (Log) для эксперимента:

английский вариант

Если всё устраивает и нет неоходимости заморачиваться, то можно просто нажать OK и где это там, под капотом SQL Server'а запустится код DBCC SHRINKFILE, но мы пойдём другим путём.
Жмём на кнопку Скрипт (Script):

англ. вариант:

После нажатия на кнопку Скрипт (Script) сгенерируется код и откроется в новом окне SSMS:

англ. версия

Чтобы запустить код, что в руссифицированной версии, что в английской версии достаточно нажать F5. Но опять же есть нюанс, он заключается в том, что у базы может быть более одного лог файла (ldf) и тем более может более одно файла самой базы данных (mdf, ndf).
В этом случае с моей точки зрения нужно переходить к динамическому SQL.
Для этого понадобится список баз данных, к которым нужно применить DBCC SHRINKFILE, особенность заключается в том, что эту команду неоходимо запускать в контексте конкретной базы данных.
Список баз получаем из системной таблицы sys.databases:
SELECT name
, database_id
FROM sys.databases;

Получается что первые 4 базы системые, трогать их в текущей задаче думаю смысла нет. Отфильровываем системные: WHERE database_id > 4
SELECT name
, database_id
FROM sys.databases
WHERE database_id > 4;
Выполняем код - F5

в результате получаем список пользовательских баз, к которым будет применяться DBCC SHRINKFILE.
Далее нужно найти логические имена файлов с помощью системной таблицы sys.database_files, но её нужно запускать, либо в контексте базы данных, либо писать полный ну или почти полный квалификатор:
SELECT *
FROM sys.database_files

SELECT выполнился в контексте базы master.
Дополним клаузулу FROM из предыдущего SELECT'а именем тестовой базы данных (FROM [TSQLV6].sys.database_files) и выполним его:

Теперь используем две системные таблицы и динамический SQL составляем скрипт:
-- Источник https://itdiary.ru/subd/adm-sql-server/shrinkfile/
-- Данный скрипт подходит для небольшого количества баз,
-- если его запускать из SSMS
-- т.к. в ssms @SQL NVARCHAR(MAX) ограничена 4000 символами в юникоде
-- либо данный код завернуть в процедуру
SET NOCOUNT ON;
DECLARE @SQL NVARCHAR(MAX) = '';
DECLARE @File NVARCHAR(64)= 'LOG'; -- 'LOG' - лог файлы базы, 'ROWS' - файлы базы, 'NULL' - все файлы базы
IF OBJECT_ID('tempdb..#logfilefordb','u') IS NOT NULL
DROP TABLE #logfilefordb;
CREATE TABLE #logfilefordb
(
dbname NVARCHAR(255) NOT NULL
, fname NVARCHAR(255) NOT NULL
, CONSTRAINT UX_db_logfile UNIQUE (dbname, fname)
)
SELECT @SQL+='SELECT '''+[name]+''' AS db_name'+
', name AS fname '+
'FROM ['+[name]+'].sys.database_files ' +
'WHERE type_desc = COALESCE(NULLIF('''+@File+''',''NULL''), type_desc);' + CHAR(13) + CHAR(10)
FROM sys.databases
WHERE database_id > 4;
PRINT @SQL;
INSERT INTO #logfilefordb (dbname, fname)
EXEC(@SQL);
SET @SQL = '';
SELECT
@SQL += 'USE ['+dbname+'];' + CHAR(13) + CHAR(10) +
'DBCC SHRINKFILE (N'''+fname+''' , 0, TRUNCATEONLY);' + CHAR(13) + CHAR(10)
FROM #logfilefordb;
PRINT @SQL;
EXEC(@sql);
Смотрим результат:

Конструкция WHERE type_desc = COALESCE(NULLIF('LOG','NULL'), type_desc) - это аналог IF ... ELSE:
NULLIF('LOG','NULL') - данная функция возвращает NULL тогда и только тогда, когда оба парамера одинаковые, в противном случае NULLIF возвращает первый параметр, в данном случае это 'LOG'
После того, как отработала функция NULLIF она вернула значение, либо NULL, либо 'LOG', либо 'ROWS':
Если передаётся параметр 'NULL' то клаузула WHERE будет интерпретироваться как WHERE type_desc = type_desc, т.е всегда будет ИСТИНА(TRUE) и все логические файлы будут обработаны: и лог файлы (.ldf), и файлы баз данных(.mdf, .ndf).
Если передаётся параметр 'LOG' то клаузула WHERE будет интерпретироваться как WHERE type_desc = 'LOG', только транзакционные лог-файлы (.ldf) баз будут обработаны.
Если передаётся параметр 'ROWS' то клаузула WHERE будет интерпретироваться как WHERE type_desc = 'ROWS', основные файлы (.mdf, .ndf) баз будут обработаны.
Теперь вместо 'LOG' установим 'NULL' и запустим:

Если переменная @File равна 'NULL', то сокращаются все файлы баз: и лог-файлы, и все остальные.
Теперь установим 'ROWS' и запустим:

В результате обработаны только основные файлы баз, а лог-файлы не были затронуты.
Теперь 2-й скрипт, тоже с динамическим SQL, но с использование курсора (CURSOR). Сразу скажу, что CURSOR не приветствуется у разработчиков, но покажу его в качестве альтернативы:
-- Источник https://itdiary.ru/subd/adm-sql-server/shrinkfile/
SET NOCOUNT ON;
DECLARE @SQL NVARCHAR(MAX) = '';
DECLARE @File NVARCHAR(64)= 'LOG'; -- 'LOG' - лог файлы базы, 'ROWS' - файлы базы, 'NULL' - все файлы базы
DECLARE @DBname NVARCHAR(MAX);
DECLARE @LogicalFileName NVARCHAR(MAX);
IF OBJECT_ID('tempdb..#logfilefordb','u') IS NOT NULL
DROP TABLE #logfilefordb;
CREATE TABLE #logfilefordb
(
dbname NVARCHAR(225) NOT NULL
, fname NVARCHAR(225) NOT NULL
, CONSTRAINT UX_db_logfile UNIQUE (dbname, fname)
)
DECLARE database_name_u CURSOR READ_ONLY FORWARD_ONLY FOR
SELECT DISTINCT [name] AS dbname
FROM sys.databases
WHERE database_id > 4
ORDER BY dbname ASC;
OPEN database_name_u
FETCH NEXT FROM database_name_u
INTO @DBname
WHILE @@FETCH_STATUS = 0
BEGIN
SET @SQL = 'SELECT ''' + @dbname + ''' AS db_name'+
', name AS fname '+
'FROM [' + @dbname + '].sys.database_files ' +
'WHERE type_desc = COALESCE(NULLIF(''' + @File + ''',''NULL''), type_desc);' + CHAR(13) + CHAR(10)
PRINT @SQL;
INSERT INTO #logfilefordb (dbname, fname)
EXEC(@SQL);
FETCH NEXT FROM database_name_u
INTO @DBname
END
CLOSE database_name_u;
DEALLOCATE database_name_u;
DECLARE shrink_files CURSOR READ_ONLY FORWARD_ONLY FOR
SELECT dbname, fname
FROM #logfilefordb ORDER BY dbname, fname;
OPEN shrink_files
FETCH NEXT FROM shrink_files
INTO @DBname, @LogicalFileName
WHILE @@FETCH_STATUS = 0
BEGIN
SET @SQL = 'USE [' + @DBname + '];' + CHAR(13) + CHAR(10) +
'DBCC SHRINKFILE (N''' + @LogicalFileName + ''' , 0, TRUNCATEONLY);' + CHAR(13) + CHAR(10)
PRINT @SQL;
EXEC(@SQL);
FETCH NEXT FROM shrink_files
INTO @DBname, @LogicalFileName
END
CLOSE shrink_files;
DEALLOCATE shrink_files;
Результат выполнения:

Обратите внимание, что в первом скрипте без курсора динамический SQL сначала формируется весь , затем исполняется.
В случае с курсором динамический SQL тоже формируется(без него здесь тоже никак), но выполняется построчно, что с одной стороны медленнее, с другой - уменьшается риск переполнения переменной.
Если вы желаете выполнить скрипт без финального запуска команд, для того чтобы просто посмотреть сформировавшиеся SQL запросы, тогда в каждом из скриптов удалите или закомментируйте вторую команду EXEC(@SQL). После выполнения перейдите на вкладку Сообщения и там увидите сформированные запросы.
PS:Перед использование всего, что описано выше на рабочем сервере, потренируйтесь на тренировочном стэнде/виртуалке, обычном хосте. Если вы решили использовать вышепреведённый код, вы это делаете на свой страх и риск.