随着SQL Server在Linux平台的普及,很多运维人员需要在Linux服务器上实现数据库日志的自动收缩。本文将原SQL脚本改造为适配Linux环境的Shell脚本,兼容SQL Server 2017及以上Linux版本(2017/2019/2022),保留全版本适配、安全合规、易维护的核心特性,同时贴合Linux系统的操作习惯。
一、完整Shell脚本
bash
#!/bin/bash
# =========================================================================
# 脚本名称:sqlserver_shrink_log_job.sh
# 适用环境:Linux + SQL Server 2017/2019/2022(x64)
# 前置条件:
# 1. Linux服务器已安装SQL Server,且启动sqlserver服务
# 2. 已安装sqlcmd工具(/opt/mssql-tools/bin/sqlcmd)
# 3. master库已创建dbo.ShrinkUser_DATABASESLogFile存储过程
# 4. 执行脚本的Linux用户具备执行sqlcmd的权限
# 核心功能:在Linux的SQL Server中创建自动收缩日志的定时作业
# 作者:运维研发组
# 日期:2025-12-16
# =========================================================================
set -euo pipefail # Shell严格模式:未定义变量报错、管道失败则脚本退出
# ======================== 自定义配置参数(根据实际需求修改) ========================
export SQL_SERVER="localhost" # SQL Server实例地址(本地填localhost,远程填IP/主机名)
export SQL_USER="sa" # SQL登录账户(建议替换为低权限运维账户)
# 建议通过环境变量传入密码,避免硬编码(执行前执行:export SQL_PWD="你的密码")
# export SQL_PWD="YourStrongPassword" # 注释:生产环境请勿直接写入脚本!
export JOB_NAME="ShrinkFile_DB_Logfile" # 作业名称
export JOB_CATEGORY="Database Maintenance" # 作业分类
export SCHEDULE_NAME="Shrink_DB_Logfile_Schedule" # 调度名称
export DAILY_EXEC_TIME="050000" # 每日执行时间(HHMMSS,050000=凌晨5点)
export DELETE_EXIST_JOB="1" # 是否删除已存在的同名作业(1=删除,0=不删除)
export NOTIFY_LEVEL="2" # 作业失败时记录事件日志(0=不记录,2=失败记录)
# ======================== 依赖检查 ========================
# 检查sqlcmd是否安装
if ! command -v /opt/mssql-tools/bin/sqlcmd &> /dev/null; then
echo "【错误】未找到sqlcmd工具,请先安装:sudo apt-get install mssql-tools unixodbc-dev(Debian/Ubuntu)或 sudo yum install mssql-tools unixODBC-devel(RHEL/CentOS)"
exit 1
fi
# 检查密码是否传入
if [ -z "${SQL_PWD:-}" ]; then
echo "【错误】未设置SQL_PWD环境变量,请执行:export SQL_PWD='你的SQL账户密码' 后再运行脚本"
exit 1
fi
# 检查SQL Server服务是否运行(可选)
if ! systemctl is-active --quiet mssql-server; then
echo "【警告】SQL Server服务未运行,建议先启动:sudo systemctl start mssql-server"
read -p "是否继续执行脚本?(y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
# ======================== 构建TSQL脚本内容 ========================
TSQL_SCRIPT=$(cat << EOF
SET NOCOUNT ON;
SET XACT_ABORT ON;
-- 检查依赖的收缩存储过程是否存在
IF NOT EXISTS (
SELECT 1
FROM master.sys.objects
WHERE name = N'ShrinkUser_DATABASESLogFile'
AND type = N'P'
AND schema_id = SCHEMA_ID(N'dbo')
)
BEGIN
RAISERROR(N'前置条件不满足:master库中未找到dbo.ShrinkUser_DATABASESLogFile存储过程!',16,1);
RETURN;
END;
-- 检查并处理已存在的同名作业
IF EXISTS (
SELECT 1
FROM msdb.dbo.sysjobs
WHERE name = N'$JOB_NAME'
)
BEGIN
IF $DELETE_EXIST_JOB = 1
BEGIN
EXEC msdb.dbo.sp_delete_job @job_name = N'$JOB_NAME', @delete_unused_schedules = 1;
PRINT N'已删除同名作业:$JOB_NAME';
END
ELSE
BEGIN
RAISERROR(N'作业【%s】已存在,若需重新创建请将DELETE_EXIST_JOB设为1',16,1,N'$JOB_NAME');
RETURN;
END
END
BEGIN TRY
BEGIN TRANSACTION;
-- 创建作业分类(若不存在)
IF NOT EXISTS (
SELECT 1
FROM msdb.dbo.syscategories
WHERE name = N'$JOB_CATEGORY'
AND category_class = 1
)
BEGIN
DECLARE @ReturnCode INT = 0;
EXEC @ReturnCode = msdb.dbo.sp_add_category
@class = N'JOB',
@type = N'LOCAL',
@name = N'$JOB_CATEGORY';
IF @ReturnCode <> 0
BEGIN
RAISERROR(N'创建作业分类【%s】失败,返回码:%d',16,1,N'$JOB_CATEGORY',@ReturnCode);
END
PRINT N'已创建作业分类:$JOB_CATEGORY';
END
-- 创建核心作业
DECLARE @JobId BINARY(16), @ReturnCode INT = 0;
EXEC @ReturnCode = msdb.dbo.sp_add_job
@job_name = N'$JOB_NAME',
@enabled = 1,
@notify_level_eventlog = $NOTIFY_LEVEL,
@notify_level_email = 0,
@notify_level_netsend = 0,
@notify_level_page = 0,
@delete_level = 0,
@description = N'Linux环境自动收缩用户数据库日志文件(适配SQL Server Linux版),依赖master.dbo.ShrinkUser_DATABASESLogFile存储过程',
@category_name = N'$JOB_CATEGORY',
@owner_login_name = N'$SQL_USER',
@job_id = @JobId OUTPUT;
IF @ReturnCode <> 0
BEGIN
RAISERROR(N'创建作业【%s】失败,返回码:%d',16,1,N'$JOB_NAME',@ReturnCode);
END
PRINT N'已创建作业主体:$JOB_NAME';
-- 添加作业执行步骤
EXEC @ReturnCode = msdb.dbo.sp_add_jobstep
@job_id = @JobId,
@step_name = N'Execute_Shrink_Log_Procedure',
@step_id = 1,
@cmdexec_success_code = 0,
@on_success_action = 1,
@on_success_step_id = 0,
@on_fail_action = 2,
@on_fail_step_id = 0,
@retry_attempts = 0,
@retry_interval = 0,
@os_run_priority = 0,
@subsystem = N'TSQL',
@command = N'
BEGIN TRY
PRINT N''开始执行日志收缩存储过程:'' + CONVERT(NVARCHAR(30), GETDATE(), 120);
EXEC master.dbo.ShrinkUser_DATABASESLogFile;
PRINT N''日志收缩存储过程执行完成:'' + CONVERT(NVARCHAR(30), GETDATE(), 120);
END TRY
BEGIN CATCH
DECLARE @ErrMsg NVARCHAR(4000) = ERROR_MESSAGE();
RAISERROR(N''收缩日志失败:%s'',16,1,@ErrMsg);
END CATCH
',
@database_name = N'master',
@flags = 0;
IF @ReturnCode <> 0
BEGIN
RAISERROR(N'添加作业步骤失败,返回码:%d',16,1,@ReturnCode);
END
PRINT N'已添加作业执行步骤';
-- 配置作业调度
EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule
@job_id = @JobId,
@name = N'$SCHEDULE_NAME',
@enabled = 1,
@freq_type = 8,
@freq_interval = 127,
@freq_subday_type = 1,
@freq_subday_interval = 0,
@freq_relative_interval = 0,
@freq_recurrence_factor = 1,
@active_start_date = CONVERT(INT, REPLACE(CONVERT(VARCHAR(10), GETDATE(), 112), '-', '')),
@active_end_date = 99991231,
@active_start_time = $DAILY_EXEC_TIME,
@active_end_time = 235959;
IF @ReturnCode <> 0
BEGIN
RAISERROR(N'配置作业调度失败,返回码:%d',16,1,@ReturnCode);
END
PRINT N'已配置作业调度:每日$DAILY_EXEC_TIME执行';
-- 关联作业到本地服务器(Linux下SQL Server实例默认名为MSSQLSERVER)
EXEC @ReturnCode = msdb.dbo.sp_add_jobserver
@job_id = @JobId,
@server_name = N'(local)';
IF @ReturnCode <> 0
BEGIN
RAISERROR(N'关联作业到服务器失败,返回码:%d',16,1,@ReturnCode);
END
PRINT N'已将作业关联到本地服务器';
COMMIT TRANSACTION;
PRINT N'============================================';
PRINT N'作业【$JOB_NAME】创建成功!';
PRINT N'============================================';
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE(),
@ErrorSeverity INT = ERROR_SEVERITY(),
@ErrorState INT = ERROR_STATE();
RAISERROR(N'创建作业失败:%s',@ErrorSeverity,@ErrorState,@ErrorMessage);
END CATCH
SET NOCOUNT OFF;
EOF
)
# ======================== 执行TSQL脚本 ========================
echo "【信息】开始执行TSQL脚本,创建自动收缩日志作业..."
/opt/mssql-tools/bin/sqlcmd -S "$SQL_SERVER" -U "$SQL_USER" -P "$SQL_PWD" -Q "$TSQL_SCRIPT"
# 检查执行结果
if [ $? -eq 0 ]; then
echo "【成功】作业创建脚本执行完成!"
echo "【提示】可通过以下命令查看作业:"
echo " /opt/mssql-tools/bin/sqlcmd -S $SQL_SERVER -U $SQL_USER -P $SQL_PWD -Q 'SELECT name, enabled FROM msdb.dbo.sysjobs WHERE name = N''$JOB_NAME'';'"
echo "【提示】查看作业执行历史:"
echo " /opt/mssql-tools/bin/sqlcmd -S $SQL_SERVER -U $SQL_USER -P $SQL_PWD -Q 'EXEC msdb.dbo.sp_help_jobhistory @job_name = N''$JOB_NAME'';'"
else
echo "【错误】作业创建脚本执行失败!"
exit 1
fi
二、使用步骤
1. 前置准备
- 确保Linux服务器已安装SQL Server并启动服务;
- 安装sqlcmd工具:
- Debian/Ubuntu:
sudo apt-get update && sudo apt-get install -y mssql-tools unixodbc-dev - RHEL/CentOS:
sudo yum install -y mssql-tools unixODBC-devel
- Debian/Ubuntu:
- 在
master库创建dbo.ShrinkUser_DATABASESLogFile存储过程(核心收缩逻辑); - 创建低权限SQL运维账户(可选,替代sa)。
2. 脚本部署与执行
bash
# 1. 将脚本保存为sqlserver_shrink_log_job.sh
vim sqlserver_shrink_log_job.sh
# 2. 添加执行权限
chmod +x sqlserver_shrink_log_job.sh
# 3. 设置SQL密码环境变量(关键:避免硬编码)
export SQL_PWD="你的SQL账户密码"
# 4. 执行脚本
./sqlserver_shrink_log_job.sh
3. 验证作业创建结果
bash
# 查看创建的作业
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P "$SQL_PWD" -Q "SELECT name, enabled FROM msdb.dbo.sysjobs WHERE name = N'ShrinkFile_DB_Logfile';"
# 手动执行作业(测试)
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P "$SQL_PWD" -Q "EXEC msdb.dbo.sp_start_job @job_name = N'ShrinkFile_DB_Logfile';"
# 查看作业执行历史
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P "$SQL_PWD" -Q "EXEC msdb.dbo.sp_help_jobhistory @job_name = N'ShrinkFile_DB_Logfile';"
三、重要注意事项
1. 安全规范
- 禁止硬编码密码:永远不要将SQL密码直接写入脚本,通过环境变量或配置文件传入;
- 最小权限原则 :避免使用
sa账户,创建专门的运维账户(授予SQLAgentOperatorRole、db_owner(master库)等最小权限); - 脚本权限 :限制脚本的读取权限(
chmod 700 sqlserver_shrink_log_job.sh),避免密码泄露。
2. 性能与业务风险
- 收缩日志的适用场景:仅用于日志异常增长的应急场景,不要作为常规运维手段;
- 完整恢复模式处理 :若数据库为完整恢复模式,需在收缩前执行日志备份(
BACKUP LOG),否则收缩无效; - 避免碎片化:频繁收缩会导致日志文件碎片化,建议收缩后调整日志文件的自动增长规则(如按固定大小增长,而非百分比)。
3. 运维监控
- 定期检查作业执行历史,确认收缩逻辑正常运行;
- 监控数据库日志文件大小,从根源解决增长问题(如优化长事务、调整恢复模式、配置定时日志备份);
- Linux下可通过
cron定时检查作业状态(可选)。