☰
用docker-compose快速部署SQL Server:从环境配置到日常运维全攻略
2026/10/1 11:32:12 网站建设 项目流程

做后端的兄弟,尤其是公司里历史系统比较多、跑着微软技术栈项目的,一定对 SQL Server 不陌生。这数据库本身很能打,事务处理、BI 能力、生态工具都成熟,但最让人头疼的往往不是 SQL 怎么写,而是环境怎么搭。以前我在 Windows 上装 SQL Server,从安装包下载到环境检查、配置管理器、服务启动,再到各种权限和防火墙问题,一套流程走下来,中途任何一步出错都可能让人血压升高。后来改用 docker-compose 启动 sql-server,整个体验立刻不一样了。本文就把我用 docker-compose 部署 SQL Server 的完整过程、踩过的坑和日常实操经验整理出来,想省事的同学可以直接照抄,想搞懂原理的也可以慢慢看。

这篇内容适合谁?适合那些需要在本地开发环境、测试环境或轻量生产环境快速拉起一个 SQL Server 的开发者,也适合运维同学想用统一编排方式管理数据库服务。无论你是用 Windows、macOS 还是 Linux,只要装了 Docker 和 Docker Compose,都能用同一套配置把 SQL Server 跑起来。下面进入正题。

1. 为什么我最终选了 docker-compose 部署 SQL Server

1.1 绕开 Windows 安装的“老大难”问题

先说背景。SQL Server 的原生安装方式虽然 GUI 做得很友好,但实际踩坑的人非常多。搜索“sqlserver 安装教程”时,能看到几十篇“史上最详细”的教程,评论区照样一堆报错。常见的有安装程序遇到句柄无效、异常来自 HRESULT、安装到一半回滚、服务启动失败、SQL Server 配置管理器里看不到实例等等。这些问题绝大多数和 Windows 系统环境有关:注册表残留、.NET 环境不干净、杀毒软件拦截、安装包损坏、权限不够,每一样都能把人逼疯。

用 docker-compose 启动 sql-server 之后,这些桌面安装的坑基本上全部绕开了。因为数据库跑在容器里,宿主机不需要安装 SQL Server 本体,也就不存在注册表、服务管理器、配置管理器这些乱七八糟的依赖。装好 Docker,拉镜像,跑容器,就这么简单。我见过很多同事第一次在 Linux 服务器上用 docker 跑 SQL Server,五分钟不到就把以前折腾一整天的事情搞定了。

当然也有一个坏处:如果你必须要用 SQL Server Agent 的某些图形化运维界面,或者依赖 Windows 集成认证连域环境,容器方案会麻烦一些。但对于开发、测试、以及大量常规业务场景,容器方案完全够用,而且团队协作时环境一致性更高,配置直接放进仓库,谁都能一键复现。

1.2 镜像版本怎么选:2022-latest 还是 2019

微软官方镜像mcr.microsoft.com/mssql/server目前主推 2022 系列,也有 2019、2017 的 tag 可选。我的建议是默认用2022-latest,除非你的项目有老版本兼容性限制。2022 相比 2019 在性能、安全、JSON 支持、查询优化方面都有提升,而且兼容性做得很好,绝大多数 2019 甚至 2016 的库都能平滑迁过去。

这里有个细节要注意:官方镜像的 Linux 容器版本是 SQL Server 的 Evaluation(评估版),180 天授权期,功能上是完整的企业版功能。对开发、测试环境来说这完全够用,生产环境如果要用,就需要考虑微软的 License 策略,不在本文讨论范围内。

另外镜像体积不小,大概 1.5GB 左右,拉取的时候要有耐心。拉完之后可以打一个自己的 tag,比如mssql-custom:2022,后续如果基于这个镜像做初始化脚本、装工具之类的扩展,方便管理。

1.3 部署前的资源规划和检查

SQL Server 不是轻量数据库,容器跑起来内存吃得很凶。微软官方要求最低 2GB 内存,实际开发环境建议至少给 4GB,如果你的机器只有 8GB 内存,再跑一个 SQL Server 容器会比较紧张。磁盘方面,镜像加数据卷至少预留 10GB 以上,数据库文件会随时间膨胀,别等满了再想办法。

端口方面默认是 1433,这个端口在日常开发中非常常用,容易被本机已装的 MySQL、其他 SQL Server 实例或杂七杂八的中间件占用。部署前先检查一下:

sudo lsof -i :1433 # 或者 sudo netstat -tunlp | grep 1433

如果端口被占用,要么停掉占用进程,要么在 compose 文件里把宿主机端口映射改成别的,比如11433:1433。我个人更倾向于保持容器内部 1433 不变,只在宿主机端口上做调整,这样容器配置始终是标准化的,宿主机端口冲突时只需要改映射关系,不用改容器内部任何东西。

Docker Compose 本身要提前确认好版本。现在 Docker 官方推荐的是docker compose插件方式,命令是docker compose,中间没有横杠。老用户可能还在用 python 的docker-compose独立工具。两种方式的 compose 文件语法基本一致,但命令行格式略有差别,后者一般是docker-compose。本文我统一用新版docker compose语法写示例,老版用户把命令换成docker-compose即可。

2. 手把手配置 docker-compose.yml,逐项拆解

2.1 最小可用的 compose 配置

先给出一份我实际在用的完整配置,然后逐项解释为什么这么写。

services: mssql: image: mcr.microsoft.com/mssql/server:2022-latest container_name: sqlserver environment: ACCEPT_EULA: "Y" MSSQL_SA_PASSWORD: "YourStrong!Passw0rd" TZ: "Asia/Shanghai" ports: - "1433:1433" volumes: - mssql_data:/var/opt/mssql restart: unless-stopped volumes: mssql_data:

看起来很短,但每一个字段都有讲究,展开说一下。

ACCEPT_EULA必须设置成Y,表示接受 SQL Server 的最终用户许可协议,不设置这一项,镜像启动会直接报错退出。MSSQL_SA_PASSWORD是系统管理员 sa 账号的密码,这个密码有严格策略要求:至少 8 个字符,并且必须包含大写字母、小写字母、数字和符号四类中的至少三类。别图省事设置一个12345678,容器会在启动阶段直接拒绝并退出,日志里会提示密码强度不符合要求。这个密码也是后续所有数据库连接的管理员凭证,一定要放到安全的配置管理里,不要硬编码提交到公开仓库。

TZ设置时区,这个很多人会忽略。容器默认是 UTC 时间,如果你在业务 SQL 里用了GETDATE(),返回的时间和本地时间会差 8 个小时,排查起来很痛苦。设置成Asia/Shanghai后,SQL Server 内部的时间函数就会使用中国时区,减少很多不必要的麻烦。

端口映射1433:1433,左边是宿主机端口,右边是容器内 SQL Server 监听的端口。如果宿主机 1433 端口已经被占,改成11433:1433即可。

数据卷mssql_data:/var/opt/mssql是最关键的部分。SQL Server 的数据文件、日志文件、系统数据库都存在/var/opt/mssql目录下。不挂数据卷的话,容器一旦被删除,整个数据库里的数据全部消失,等于白干。用命名卷mssql_data的好处是数据保存在 Docker 管理的卷目录中,即使容器删除重建,数据还在。而且多容器共享同一套 compose 文件时,每个项目的卷是隔离的,不会互相干扰。

restart: unless-stopped表示容器异常退出时自动重启,除非你手动 stop。对服务器环境来说很重要,机器重启后数据库能自动拉起来,不用人工干预。

2.2 启动容器并完成首次连接验证

配置写好之后,在 docker-compose.yml 所在的目录执行:

docker compose up -d

-d表示后台运行,不加的话终端会被日志刷屏。启动后先看容器状态:

docker ps

正常状态下STATUS列会显示Up,比如Up 2 minutes。接着看一眼日志,确认没有异常:

docker logs sqlserver

日志里如果出现类似SQL Server is ready for client connections或者Recovery is complete的提示,说明启动成功。如果一直卡住,或者能看到ERROR: [Microsoft][ODBC Driver 18 for SQL Server]这类信息,多半是密码策略或资源问题,后面常见问题里我会详细说。

然后进入容器用 sqlcmd 验证连接。这里有个关键差异要注意:2022 镜像里的 sqlcmd 路径是/opt/mssql-tools18/bin/sqlcmd,2019 及更早镜像里是/opt/mssql-tools/bin/sqlcmd。因为 2022 默认启用强制加密连接,sqlcmd 18 版本需要额外加-C参数来信任服务器证书,否则会报证书相关的错误。

docker exec -it sqlserver /opt/mssql-tools18/bin/sqlcmd \ -S localhost -U sa -P 'YourStrong!Passw0rd' -C \ -Q "SELECT @@VERSION"

参数说明:-S指定服务器地址,容器内部就是localhost;-U和-P是账号密码;-C是信任服务器证书;-Q是执行一段 SQL 后退出。如果能输出版本信息,比如 SQL Server 2022 的版本号,说明整个链路已经通了。

2.3 客户端接入:连接串、JDBC 驱动和图形化工具

容器跑起来只是第一步,大家日常肯定要用各种工具连进去操作。这里把常见连接方式整理一下。

使用官方图形化工具的话,Windows 上最顺手的是 SQL Server Management Studio(SSMS),macOS 上则是 Azure Data Studio。DBeaver 也可以,它对各种数据库统一管理,习惯用 DBeaver 的也能直接连。连接时服务器地址填写localhost,1433,注意是英文逗号,不是冒号;登录方式选 SQL Server 身份验证,账号sa,密码就用刚才设的强密码。如果宿主机端口改了,比如映射到 11433,就填localhost,11433。

开发场景里,Java 项目用 JDBC 连接是高频需求。连接串格式如下:

jdbc:sqlserver://localhost:1433;encrypt=true;trustServerCertificate=true;databaseName=master

这里有两个参数非常关键:encrypt=true和trustServerCertificate=true。SQL Server 2022 默认强制加密连接,如果不在连接串里显式信任,客户端证书校验会失败,IDE 里报错会让人百思不得其解。加了trustServerCertificate=true就是告诉驱动“本地开发环境不需要校验服务器证书”,问题立刻解决。

顺带说一个很多人痛点:IntelliJ IDEA 里新建 SQL Server 数据源时,IDEA 尝试自动下载 JDBC 驱动,结果卡在download from maven failed,半天连不上。这个问题的根源不在数据库配置,而是 IDEA 从 Maven 仓库拉取mssql-jdbc驱动时受网络环境影响失败。最省心的解决办法是手动去微软官方下载对应版本的mssql-jdbcjar 包,放进 IDEA 数据源的驱动列表里,或者放到项目libs目录并引入。Kettle(PDI)连 SQL Server 也是类似的道理,把官方 JDBC 驱动放进 Kettle 的lib目录即可,驱动类名是com.microsoft.sqlserver.jdbc.SQLServerDriver。

3. 部署 SQL Server 后的常用实操经验

3.1 数据持久化、备份与还原

挂载了数据卷之后,数据库文件已经持久化了,但备份还是得自己做。Docker 容器里做备份,核心思路是把备份文件写到容器内挂载的卷目录中,再拷贝到宿主机存档。

先创建备份目录。因为容器内 SQL Server 进程是以mssql用户运行的,备份目录如果权限不对,写入会报“拒绝访问”的错误:

docker exec -it sqlserver mkdir -p /var/opt/mssql/backup docker exec -it sqlserver chown mssql:mssql /var/opt/mssql/backup

然后进容器或用 sqlcmd 执行备份:

docker exec -it sqlserver /opt/mssql-tools18/bin/sqlcmd \ -S localhost -U sa -P 'YourStrong!Passw0rd' -C \ -Q "BACKUP DATABASE [YourDB] TO DISK = N'/var/opt/mssql/backup/YourDB.bak' WITH INIT"

备份文件在容器内生成后,用docker cp拉出来:

docker cp sqlserver:/var/opt/mssql/backup/YourDB.bak /your/host/path/

还原的操作是反过来的。先把备份文件docker cp进容器,再执行还原语句。注意还原时如果数据库文件路径和原库不一致,可能需要用到WITH MOVE把数据文件和日志文件挪到容器内正确的目录。这个细节很烦人,但实际工作中绕不开。我的建议是第一次还原之前,用RESTORE FILELISTONLY FROM DISK = N'/var/opt/mssql/backup/YourDB.bak'查看备份内部的文件逻辑名,再决定WITH MOVE怎么写,避免盲目还原报错。

3.2 开发中高频踩坑的三个 SQL 场景

数据库环境跑起来了,业务开发时有些高频 SQL 场景也值得展开说说,因为很多人都是临时上网搜。这里挑了三个和 SQL Server 最常被搜索的场景相关的问题。

字符串转数字,这个几乎是每天都会遇到的。SQL Server 提供CAST和CONVERT函数,但直接转换非数字字符串会直接报错中断整个批处理。比如SELECT CAST('abc' AS INT)会抛出转换失败错误。更稳妥的做法是使用TRY_CAST或TRY_CONVERT,转换失败时返回 NULL 而不是报错,程序里再处理 null 逻辑就行:

SELECT TRY_CAST('123' AS INT) AS ok, TRY_CAST('abc' AS INT) AS failed; -- ok 返回 123,failed 返回 NULL

多行合并成一行,也是搜索量很高的需求。SQL Server 2017 以上版本提供了STRING_AGG,比老办法FOR XML PATH清晰太多:

SELECT DepartmentID, STRING_AGG(EmployeeName, ',') AS NameList FROM Employees GROUP BY DepartmentID;

排序有要求的话,STRING_AGG函数内支持WITHIN GROUP (ORDER BY ...),这一点比FOR XML PATH写法直观得多。老版本环境只能用FOR XML PATH配合 STUFF 实现,语法比较绕,不展开细说了。

单表上亿数据导致存储空间膨胀,这个场景越到项目后期越头疼。如果表结构已经稳定,最直接有效的降空间手段是开启页级压缩:

ALTER TABLE YourBigTable REBUILD PARTITION = ALL WITH (DATA_COMPRESSION = PAGE);

页级压缩对数字、字符串这种重复性高的数据效果明显,但会增加 CPU 开销,OLTP 高频写入场景要压测后再上。更系统的做法是考虑分区表或归档旧数据,把历史数据挪到独立的归档库或冷存储中,让在线表瘦身。这属于优化级别的方案,真要做了,建议先分析表占用的空间分布,定位是哪类数据占了大头,再决定压缩还是分区。

3.3 存储过程、触发器与变更记录的正确姿势

有一些网友会问“存储记录时触发怎么写”,其实就是想在数据变更时自动记录日志。触发器可以实现,比如在表上建AFTER INSERT, UPDATE, DELETE触发器,把变更前后的数据写入审计表。没接触过的同学可能会写出非常臃肿的逐字段判断逻辑。实际上可以利用INSERTED和DELETED临时表一次拿到变更前后的整行数据,然后统一记录 JSON 全文或差异列。

不过我想多提一句:如果是正经的业务审计、数据同步场景,SQL Server 内置了变更数据捕获(CDC)和更改跟踪(Change Tracking)功能,尤其以 CDC 能力更强,可以读取到变更流,而不需要自己写触发器。对开发者来说,这两者的学习成本比造轮子触发器低得多,还能避免触发器带来的隐性死锁和事务膨胀问题。触发器适合业务规则非常明确的场景,比如订单状态流转时强制校验合法性,而不是拿来记录日志。

3.4 日常运维:日志、重启策略和版本升级

容器跑起来以后不是彻底不管。数据库容器如果长期不重启,内存会持续增长,这是 SQL Server 在 Linux 容器下的常见表现。我的做法是定期执行一次docker compose restart,或者设置计划任务在低峰期重启容器。重启后内存会释放,但注意恢复数据库同样需要时间,不要在高负载时段做这个事。

因为设置了restart: unless-stopped,容器异常退出后会自己拉起,但真正宕机原因还是要看日志。日志路径直接用:

docker logs --tail 200 sqlserver

镜像更新也是运维的一部分。微软推送新版本补丁后,旧的镜像不会自动升级。升级流程是:先备份所有业务库,然后拉取新镜像,重新执行docker compose up -d,确认 SQL Server 版本变化后检查业务 SQL 是否有兼容性波动。这个过程在 compose 文件管理下非常顺滑,不像物理机升级那样有很长的停机窗口。

4. 常见问题与排查技巧实录

4.1 容器起不来或者反复重启

容器启动后一直处于Restarting状态,是最常见的坑。先别急着重建容器,执行docker logs看具体日志,基本一看就知道问题在哪。我把高频错误整理成一张速查表:

报错特征根本原因处理办法
日志提示SQL Server 2019 is not supported on this OS镜像和宿主机内核/系统版本不兼容换用官方支持的镜像 tag,比如 2022-latest,或者升级 Docker
日志提示密码强度校验失败MSSQL_SA_PASSWORD不符合复杂度要求改成 8 位以上、含大小写字母和数字符号的强密码
启动没报错但端口连不上容器映射端口和实际监听不一致,或宿主机防火墙拦截确认docker ps中的端口映射,检查防火墙入站规则
容器反复重启且日志有Permission denied数据卷目录权限不对,mssql 用户无法写入手动创建目录并 chown 给 101 用户(mssql)
内存不足导致 SQL Server 进程被杀宿主机内存小于 2GB加内存或限制容器内存,甚至换更轻量的数据库方案

4.2 客户端连不上数据库的排查路径

容器明明起来了,客户端就是连不上。遇到这种问题,我建议按六步走。第一步,宿主机上确认端口映射是不是对得上,docker port sqlserver能看到实际映射。第二步,检查容器内 sqlcmd 能不能连,如果能连说明数据库服务本身没问题,问题在网络链路。第三步,检查宿主机防火墙,特别是云服务器,安全组里 1433 端口放行没有。第四步,看连接串是不是把encrypt=true和trustServerCertificate=true加上了,2022 版本缺这个就是连不上。第五步,确认 sa 账号有没有被锁定,密码输错太多次会导致账号锁死。第六步,换个客户端工具再试一次,很多诡异问题其实是工具自身缓存或驱动版本导致。

4.3 安装类问题与面板识别问题的避坑建议

网上还有很多“sqlserver 安装程序遇到句柄无效”“已经在服务器面板安装了 SQL Server 但面板无法识别”这类问题。如果你走的是容器部署,上面这些问题基本不会碰到,因为容器内服务不受宿主机 init 系统管理,服务器面板扫不到也是正常的。数据库是否在跑,只看docker ps的输出,不要依赖面板里的服务列表。

如果实在遇到了需要在 Windows 本机安装的硬性需求,那我只提醒几点:使用管理员身份运行安装程序、暂时关闭杀毒软件和防火墙、安装前清理旧版本残留的注册表项、优先用 SQL Server Configuration Manager 检查服务账号权限。这些经验都是从一次次“句柄无效”“HRESULT 异常”的报错里攒出来的,尤其是 “句柄无效”,很大程度上是用户账户权限不够或安装包来源不干净,重新下载官方安装包并右键管理员运行往往能解决。

说起容器方案,我在实际项目里的体会是:它解决的不仅是“装环境”的速度问题,更是团队协作的一致性问题。以前新同学入职配开发环境要花半天,现在 docker compose 一条命令,五分钟就有一个和线上配置几乎一致的 SQL Server。数据卷备份、灾后重建这些常规操作也比物理机或云主机 RDS 更可控。

最后分享一个小技巧:docker-compose.yml 文件本身就是最好的运维文档。你可以在服务环境变量下方写注释,把 sa 密码的存放位置、备份策略、端口变更原因都记进去,下次任何人接手都能快速了解这套 SQL Server 的治理逻辑。我自己就把这份 compose 文件放进了团队的知识库,数据库相关的初始化脚本、定时备份脚本都围绕它展开,后续如果有更多服务需要编排,也可以在这个文件里直接扩展。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询