SQL Server Developer Edition、容器部署与团队数据库治理手册
从一次“连得上”却连错库的事故开始
SQL Server 在企业里常常同时连接 .NET 应用、SSMS、SSDT / DACPAC、Windows 身份体系、审计和备份平台。最危险的开发事故往往不是服务没启动,而是服务启动了、客户端也显示“连接成功”,开发者却连到了本机旧实例,随后把表建进错误数据库;或者容器没有挂载 volume,重建后数据全部消失;又或者应用沿用了 sa,凭证随着截图和配置文件扩散。
所以第一次成功不能用“客户端绿灯”定义。一个可信的开发环境至少要同时回答四个问题:当前连接的是哪台实例、哪个 database、以哪个 login/user 身份工作、数据和日志落在哪个持久化目录。后面的安装、实验和治理都围绕这四个答案展开。
动手前先确定环境身份
准备一台用于实验的开发机或临时目录,并确认:
Docker Engine / Docker Desktop,并确认 docker compose version 可用;如果使用本机安装,提前确认 Windows 版本、安装权限和实例命名。本机至少预留一个开发端口,例如 14333,避免误连本机默认 1433 或已有企业 VPN 转发端口。一个强密码占位符,例如 YOUR_STRONG_SQLSERVER_SA_PASSWORD;.env.example 保留占位符,真实值进入本机 .env 或密钥系统。
至少一种客户端:sqlcmd、SSMS 22、VS Code MSSQL 扩展、DBeaver、DataGrip 或应用驱动。一个只用于实验的项目目录,例如 your-project/。为环境贴上明确用途标签。Developer Edition 的许可用途是开发和测试,不能因为功能完整就承接生产或准生产流量。
先检查端口:
netstat -ano | findstr ":1433"
netstat -ano | findstr ":14333"Linux / macOS:
lsof -i :1433
lsof -i :14333如果开发机是 Apple Silicon、Windows on Arm 或其他 Arm 环境,不要把模拟层里的偶然启动当作平台兼容。SQL Server Linux 容器支持说明要求宿主为使用 Intel / AMD x86-64 CPU 的 Linux,Rosetta、Prism、QEMU 等模拟或翻译环境不在测试与支持范围内;Arm 团队更适合使用远程共享开发实例或按时销毁的 Azure SQL 开发库。
先选 edition,再选运行方式
SQL Server 2025 版本把开发场景拆成几种不同成本模型。edition 与功能矩阵列出了 EnterpriseDeveloper 和 StandardDeveloper 两个产品 ID:它们分别提供对应生产 edition 的功能,但许可用途限定为开发和测试;Express 是免费入门 edition,适合学习、桌面应用和小型服务;LocalDB 又是 Express 的用户模式轻量形态,启动快,却不适合远程共享。Evaluation 具备 Enterprise 功能,但有 180 天期限,不能被当作长期环境。
“SQL Server 2025 版本”不足以唯一标识运行环境。实例还要记录完整 ProductVersion、ProductLevel、Edition 和镜像 digest,因为 CU 会改变缺陷、驱动兼容和故障判断。2025-latest 是版本 tag,适合第一次实验;团队基线应在验证后固定具体 tag 或 digest。
客户端同样需要定型:Windows 管理和完整诊断使用 SSMS 22;跨平台编辑与查询使用 VS Code MSSQL 扩展;自动化脚本使用 sqlcmd,并注明采用 Go 版还是 ODBC 版。Azure Data Studio 退役说明要求团队迁移到 VS Code MSSQL 扩展或 SSMS,不应再把它加入新的安装基线。
驱动名称也必须具体到实现与版本。Java 对应 Microsoft JDBC Driver,.NET 优先 Microsoft.Data.SqlClient,原生或语言生态常经 Microsoft ODBC Driver 接入。TLS 默认值会随驱动代际变化,因此升级驱动要同时回归证书链、主机名和加密参数,不能只看 SQL 是否执行成功。
主流部署方式
SQL Server 的部署方式不能只按“能不能启动”来分,要按授权、团队共享、平台、数据持久化和运维责任来分。
| 方式 | 定义 | 适用场景 | 不适合 |
|---|---|---|---|
| LocalDB | Express 的轻量用户模式本地实例 | Windows 个人开发、单机 demo、临时验证 | 团队共享、远程连接、容器化联调 |
| Express | 免费入门 edition | 小型桌面 / Web / server 应用、学习和轻量生产 | 核心企业生产、高容量、高可用 |
| Enterprise Developer / Standard Developer | 免费非生产开发测试 edition,分别对齐 Enterprise / Standard 功能边界 | 本机开发、共享开发实例、CI / 测试环境 | 任何生产或准生产流量 |
| Windows 本机服务 | 安装 SQL Server 服务和实例 | Windows 企业开发机、需要 SSMS / LocalDB / SSDT 配套 | 快速销毁、跨平台一致性 |
| Linux 容器 | mcr.microsoft.com/mssql/server 容器 | 可重复开发环境、CI、短期联调、团队基线 | ARM 本机、缺少持久化和运维设计的生产环境 |
| Compose 共享开发库 | 用 Compose 固化端口、volume、健康检查和初始化 | 小团队统一开发依赖 | 多租户强隔离、高可用和正式审计 |
| Azure SQL / Managed Instance | 云托管 SQL Server 兼容服务 | 远程开发库、云上集成测试、接近生产的连接验证 | 本机离线开发、免费用意识的长期常开环境 |
选择标准:
个人 Windows 开发、只跑单机 demo:LocalDB 或 Express。团队需要共享一个开发实例:Developer 容器或单独开发服务器,但必须有 owner、端口、账号和清理规则。跨平台项目需要可复现环境:Linux 容器 + Compose。
Mac ARM 开发机:优先远程 SQL Server 或 Azure SQL 开发库,不要把 QEMU 下容器成功启动当可靠方案。接近生产网络、证书、权限和云集成:Azure SQL / Managed Instance 开发环境,但要写费用和清理策略。生产:重新设计授权、高可用、备份、监控、容量和接管流程,开发 Compose 不能直接晋级为生产编排。
Windows 本机
Windows 上常见组合:
SQL Server Developer Edition:适合非生产开发和测试。SQL Server Express:适合轻量应用和小型服务。LocalDB:适合个人开发、Visual Studio / .NET 项目和无需共享的本地库。
SSMS 22:适合图形化管理、查询、备份恢复和权限检查。VS Code MSSQL extension:适合轻量查询、脚本和跨平台编辑。
团队安装记录需要写清:
版本和 edition。实例名:默认实例、命名实例还是 LocalDB。认证方式:Windows Authentication、SQL Login、Entra / Azure 认证。
端口:固定端口还是动态端口。初始管理员:谁负责,是否允许 sa,密码如何保管。卸载和清理路径:本机实例、服务、数据目录和防火墙规则。
Docker 单容器
下面先建立一个可销毁的开发实例:
docker volume create sqlserver-dev-data
docker run -d \
--name sqlserver-dev \
-e ACCEPT_EULA=Y \
-e MSSQL_PID=EnterpriseDeveloper \
-e MSSQL_SA_PASSWORD=YOUR_STRONG_SQLSERVER_SA_PASSWORD \
-p 14333:1433 \
-v sqlserver-dev-data:/var/opt/mssql \
mcr.microsoft.com/mssql/server:2025-latest关键点:
14333:1433 是宿主端口到容器端口的映射,团队示例建议避开宿主 1433。MSSQL_SA_PASSWORD 只在初始化空数据目录时可靠。旧 volume 会保留旧 master、旧 login、旧密码和旧数据库。MSSQL_PID=EnterpriseDeveloper 代表非生产开发测试用途,不是免费生产授权;需要验证 Standard 功能边界时改用 StandardDeveloper。
容器启动不等于 SQL Server ready。要用 sqlcmd -Q "SELECT 1" 做健康检查。
Docker Compose
多人项目可以把相同参数固化进 Compose:
services:
sqlserver:
image: mcr.microsoft.com/mssql/server:2025-latest
container_name: tool-sqlserver-dev
environment:
ACCEPT_EULA: "Y"
MSSQL_PID: "EnterpriseDeveloper"
MSSQL_SA_PASSWORD: "${MSSQL_SA_PASSWORD}"
MSSQL_COLLATION: "Chinese_PRC_CI_AS"
MSSQL_MEMORY_LIMIT_MB: "2048"
ports:
- "14333:1433"
volumes:
- sqlserver-data:/var/opt/mssql
- ./db/sqlserver/backup:/var/opt/mssql/backup
healthcheck:
test:
[
"CMD-SHELL",
"/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P \"$${MSSQL_SA_PASSWORD}\" -C -Q \"SELECT 1\" || exit 1"
]
interval: 10s
timeout: 5s
retries: 30
start_period: 40s
volumes:
sqlserver-data:说明:
-C 代表信任服务器证书,适合本地容器健康检查;共享环境要使用正式证书和 CA 信任。/opt/mssql-tools18/bin/sqlcmd 路径取决于镜像和工具版本,团队应以实际镜像验证后固化。备份目录单独挂载,避免 .bak 只留在容器层里。
如果容器镜像没有内置预期 sqlcmd,应另起工具容器或在宿主安装 sqlcmd,不要把 healthcheck 写成永远失败。
.env.example:
MSSQL_SA_PASSWORD=YOUR_STRONG_SQLSERVER_SA_PASSWORD
MSSQL_APP_PASSWORD=YOUR_STRONG_SQLSERVER_APP_PASSWORD真实 .env 不提交。
从单实例走向生产架构
容器能证明驱动、SQL 和权限模型可用,却不能证明生产可靠性。先把业务目标翻译成 RTO、RPO、可接受写入延迟、只读流量比例、故障域和预算,再从下面几类架构中选择:
| 架构 | 数据与故障切换方式 | 适合 | 关键代价与证据 |
|---|---|---|---|
| 单实例 + 备份恢复 | 一个读写实例,依赖完整、差异和日志备份恢复 | 可接受分钟到小时恢复、成本敏感的非核心系统 | 主机故障期间不可用;必须用异机 restore 证明 RTO / RPO |
| Failover Cluster Instance | WSFC 在节点间切换同一个 SQL Server 实例和共享存储 | 需要实例级本地高可用,且已有 Windows 集群与共享存储能力 | FCI保护实例,不消除共享存储故障域;要演练节点、存储和网络名切换 |
| Always On Availability Group | 主库把事务日志发送到独立副本,可同步或异步提交,并通过 listener 接入 | 数据库级高可用、跨站灾备、只读副本 | 同步提交增加写延迟;异步强制切换可能丢数据;Standard 只有 Basic AG 等功能边界,必须按 edition 核对 |
| Log Shipping | 周期性备份、复制并恢复事务日志到备用库 | 低成本灾备、允许人工切换和分钟级 RPO | 恢复延迟与作业失败决定数据缺口;没有透明自动切换 |
| Read-scale AG | 把报表和只读查询路由到可读副本 | 主库读压力高,但不要求集群自动故障切换 | 无集群 read-scale AG本身不是高可用;应用还要接受复制延迟和只读语义 |
| Azure SQL Database / Managed Instance | 云平台负责大量基础设施、补丁和可用性能力 | 团队希望降低主机与集群运维,或需要云身份和弹性 | 费用、服务层能力、网络出口、厂商依赖和迁移兼容必须进入评审 |
Availability Group 架构说明明确指出副本不是备份。AG 能缩短部分故障的恢复时间,但误删、逻辑损坏和勒索操作也可能复制到副本,因此完整备份、日志链、异地副本和恢复演练仍然独立存在。
选型时按因果顺序判断:单机 restore 能满足 RTO / RPO 就不要先引入集群;需要实例级本地切换且共享存储风险可接受时考虑 FCI;需要数据库级副本、跨站或读扩展时考虑 AG;只需要低成本灾备时可评估 Log Shipping。同步 AG 的“零数据丢失”还依赖副本处于 SYNCHRONIZED、集群有 quorum 且按计划切换;异步副本强制 failover 必须按可能丢数据处理。
生产验收至少保存以下状态,而不是只截一张“绿色”面板:
SELECT ar.replica_server_name,
ars.role_desc,
ars.connected_state_desc,
drs.synchronization_state_desc,
drs.synchronization_health_desc,
drs.log_send_queue_size,
drs.redo_queue_size
FROM sys.dm_hadr_database_replica_states AS drs
JOIN sys.availability_replicas AS ar
ON ar.replica_id = drs.replica_id
JOIN sys.dm_hadr_availability_replica_states AS ars
ON ars.replica_id = ar.replica_id
AND ars.group_id = ar.group_id;切换演练要同时记录客户端通过 listener 的重连时间、未提交事务结果、同步状态、发送与 redo 队列、备份作业落点和应用错误率。把只读请求路由到副本前,还要用业务实验测出可接受的可见性延迟;不能把“能 SELECT”误当作强一致读取。
初始化变量
| 变量 | 作用 | 落地提醒 |
|---|---|---|
ACCEPT_EULA | 接受最终用户许可协议,镜像必需 | 设置后仍需独立完成授权合规判断 |
MSSQL_SA_PASSWORD | 设置 sa 密码 | SA_PASSWORD 已弃用;旧 volume 不会自动改密码 |
MSSQL_PID | 设置 edition / 产品密钥 | SQL Server 2025 版本的开发基线用 EnterpriseDeveloper 或 StandardDeveloper,生产不要复制 |
MSSQL_TCP_PORT | 设置 SQL Server 监听端口 | 改容器内端口时宿主映射也要同步 |
MSSQL_COLLATION | 设置实例默认排序规则 | 建库前确认,后期修改成本很高 |
MSSQL_MEMORY_LIMIT_MB | 限制 SQL Server 可用内存 | 本机开发避免吃满 Docker 内存 |
MSSQL_BACKUP_DIR | 设置默认备份目录 | 仍需挂载宿主目录或 volume |
MSSQL_DATA_DIR | 新数据库 .mdf 默认目录 | 目录权限和 volume 要匹配 |
MSSQL_LOG_DIR | 新数据库 .ldf 默认目录 | 日志暴涨时更容易定位 |
不要把这些变量理解成“每次启动都重建数据库”。SQL Server 的 master 数据库、login、database 和配置都在数据目录里。只要 /var/opt/mssql 已经存在,很多初始化假设都会被旧状态覆盖。
账号和权限
开发环境也不要让应用用 sa。最小模板:
CREATE DATABASE tool_efficiency_sqlserver;
GO
USE tool_efficiency_sqlserver;
GO
CREATE LOGIN app_tool_efficiency
WITH PASSWORD = 'YOUR_STRONG_SQLSERVER_APP_PASSWORD',
CHECK_POLICY = ON;
GO
CREATE USER app_tool_efficiency
FOR LOGIN app_tool_efficiency;
GO
CREATE SCHEMA app AUTHORIZATION dbo;
GO
ALTER ROLE db_datareader ADD MEMBER app_tool_efficiency;
ALTER ROLE db_datawriter ADD MEMBER app_tool_efficiency;
GO更稳的团队做法是分三类账号:
| 账号 | 权限 | 用途 |
|---|---|---|
migration_xxx | schema 变更权限 | Flyway、Liquibase、DACPAC 或初始化脚本 |
runtime_xxx | 目标 schema 读写 | 应用运行 |
readonly_xxx | 只读 | 调试、报表、人工排查 |
db_owner、sysadmin 和 sa 只留给 DBA / owner 和受控脚本,不给业务应用默认使用。
Collation
SQL Server 的 collation 会影响大小写敏感、重音敏感、排序、比较和索引行为。开发环境常见坑是:本机默认 collation、容器 collation、共享开发库 collation 和生产 collation 不一致。
建库时显式写:
CREATE DATABASE tool_efficiency_sqlserver
COLLATE Chinese_PRC_CI_AS;
GO如果团队需要大小写敏感测试,要明确使用 CS collation。不要等上线后发现唯一索引、排序和搜索行为不同,再试图硬改线上库。
连接加密
Microsoft JDBC Driver 示例中,本地自签证书场景常见:
jdbc:sqlserver://localhost:14333;databaseName=tool_efficiency_sqlserver;encrypt=true;trustServerCertificate=true;applicationName=tool-efficiency-dev这只适合本地或临时测试,因为 trustServerCertificate=true 不验证服务器 TLS 证书。共享开发、测试和生产应改成:
jdbc:sqlserver://sqlserver-dev.example.test:1433;databaseName=tool_efficiency_sqlserver;encrypt=true;trustServerCertificate=false;hostNameInCertificate=sqlserver-dev.example.test;applicationName=tool-efficiency-dev证书、CA、域名和连接串模板应该由团队统一维护,不要让每个项目自己猜。
启动:
docker compose up -d
docker compose ps
docker logs tool-sqlserver-dev --tail 80确认 SQL Server ready:
sqlcmd -S localhost,14333 -U sa -P "YOUR_STRONG_SQLSERVER_SA_PASSWORD" -C -Q "SELECT @@VERSION AS version;"
sqlcmd -S localhost,14333 -U sa -P "YOUR_STRONG_SQLSERVER_SA_PASSWORD" -C -Q "SELECT SERVERPROPERTY('Edition') AS edition, SERVERPROPERTY('ProductVersion') AS product_version;"如果使用新版 Go sqlcmd,本地自签证书场景也可用 -No 跳过证书验证。团队文档必须写清当前使用的是 Go 版还是 ODBC 版 sqlcmd,不要混用参数。
创建数据库、账号、schema、表:
sqlcmd -S localhost,14333 -U sa -P "YOUR_STRONG_SQLSERVER_SA_PASSWORD" -C -Q "
IF DB_ID('tool_efficiency_sqlserver') IS NULL
CREATE DATABASE tool_efficiency_sqlserver COLLATE Chinese_PRC_CI_AS;
"sqlcmd -S localhost,14333 -U sa -P "YOUR_STRONG_SQLSERVER_SA_PASSWORD" -C -d tool_efficiency_sqlserver -Q "
IF NOT EXISTS (SELECT 1 FROM sys.schemas WHERE name = 'app')
EXEC('CREATE SCHEMA app AUTHORIZATION dbo');
IF OBJECT_ID('app.user_demo', 'U') IS NULL
CREATE TABLE app.user_demo(
id INT IDENTITY(1,1) PRIMARY KEY,
username NVARCHAR(64) NOT NULL UNIQUE,
created_at DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME()
);
INSERT INTO app.user_demo(username)
SELECT N'demo'
WHERE NOT EXISTS (SELECT 1 FROM app.user_demo WHERE username = N'demo');
SELECT id, username, created_at FROM app.user_demo;
"创建普通应用账号:
sqlcmd -S localhost,14333 -U sa -P "YOUR_STRONG_SQLSERVER_SA_PASSWORD" -C -Q "
IF NOT EXISTS (SELECT 1 FROM sys.sql_logins WHERE name = 'app_tool_efficiency')
CREATE LOGIN app_tool_efficiency WITH PASSWORD = 'YOUR_STRONG_SQLSERVER_APP_PASSWORD', CHECK_POLICY = ON;
"
sqlcmd -S localhost,14333 -U sa -P "YOUR_STRONG_SQLSERVER_SA_PASSWORD" -C -d tool_efficiency_sqlserver -Q "
IF NOT EXISTS (SELECT 1 FROM sys.database_principals WHERE name = 'app_tool_efficiency')
CREATE USER app_tool_efficiency FOR LOGIN app_tool_efficiency;
ALTER ROLE db_datareader ADD MEMBER app_tool_efficiency;
ALTER ROLE db_datawriter ADD MEMBER app_tool_efficiency;
"用普通账号验证。成功输出中的 db_name、login_name、db_user 应分别是 tool_efficiency_sqlserver、app_tool_efficiency、app_tool_efficiency:
sqlcmd -S localhost,14333 -U app_tool_efficiency -P "YOUR_STRONG_SQLSERVER_APP_PASSWORD" -C -d tool_efficiency_sqlserver -Q "SELECT DB_NAME() AS db_name, SUSER_SNAME() AS login_name, USER_NAME() AS db_user;"接着故意越权建表,确认运行账号不能修改结构:
sqlcmd -S localhost,14333 -U app_tool_efficiency -P "YOUR_STRONG_SQLSERVER_APP_PASSWORD" -C -d tool_efficiency_sqlserver -Q "CREATE TABLE app.permission_probe(id int);"预期出现 Msg 262 和 CREATE TABLE permission denied。如果命令成功,说明账号继承了 db_ddladmin、db_owner 或更高权限;先查询 role membership 并回收多余授权,再继续项目接入。这个反向实验比检查一张静态授权表更可靠,因为它验证了数据库最终执行的权限路径。
备份和校验:
sqlcmd -S localhost,14333 -U sa -P "YOUR_STRONG_SQLSERVER_SA_PASSWORD" -C -Q "
BACKUP DATABASE tool_efficiency_sqlserver
TO DISK = '/var/opt/mssql/backup/tool_efficiency_sqlserver.bak'
WITH INIT, COMPRESSION;
RESTORE VERIFYONLY
FROM DISK = '/var/opt/mssql/backup/tool_efficiency_sqlserver.bak';
"RESTORE VERIFYONLY 成功只证明备份集可读、结构完整,不能证明应用级恢复目标已满足。关键共享库还要定期恢复到临时 database,执行行数、约束、账号和应用 smoke test,再删除临时库。
清理测试表:
sqlcmd -S localhost,14333 -U sa -P "YOUR_STRONG_SQLSERVER_SA_PASSWORD" -C -d tool_efficiency_sqlserver -Q "
DELETE FROM app.user_demo WHERE username = N'demo';
"把以上正向查询、越权失败和备份校验放进目标开发机或 CI 的 smoke job,并记录镜像 tag / digest、sqlcmd 变体和证书策略。只有三类证据同时出现,环境才算可复现:身份查询符合预期、应用账号越权失败、销毁重建后 migration 仍能恢复基线。
Java / JDBC
Maven 依赖使用团队验证并锁定的 Microsoft JDBC Driver 版本:
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>替换为项目锁定版本</version>
</dependency>本地连接串:
spring.datasource.url=jdbc:sqlserver://localhost:14333;databaseName=tool_efficiency_sqlserver;encrypt=true;trustServerCertificate=true;applicationName=tool-efficiency-dev
spring.datasource.username=app_tool_efficiency
spring.datasource.password=YOUR_STRONG_SQLSERVER_APP_PASSWORD团队共享环境连接串:
spring.datasource.url=jdbc:sqlserver://sqlserver-dev.example.test:1433;databaseName=tool_efficiency_sqlserver;encrypt=true;trustServerCertificate=false;hostNameInCertificate=sqlserver-dev.example.test;applicationName=tool-efficiency-dev注意:
trustServerCertificate=true 只用于本地容器和临时自签测试。applicationName 必填,方便在 sessions、日志和监控里定位应用。连接池最大连接数不能照搬 MySQL / PostgreSQL;共享 SQL Server 实例要按项目配额。
不要让应用账号拥有 db_owner 或 sysadmin。
.NET
.NET 项目常用 Microsoft.Data.SqlClient:
Server=localhost,14333;Database=tool_efficiency_sqlserver;User Id=app_tool_efficiency;Password=YOUR_STRONG_SQLSERVER_APP_PASSWORD;Encrypt=True;TrustServerCertificate=True;Application Name=tool-efficiency-dev;Windows 域环境或 Entra 集成环境可能使用集成认证。生产认证设计还要明确身份提供方、服务主体生命周期、令牌刷新、失败降级、审计主体和 DBA 紧急访问流程。
Node / Python / ODBC
Node、Python 和其他语言通常通过 Microsoft ODBC Driver 或语言生态驱动连接。升级或更换驱动时逐项验证:
驱动是否支持当前 OS、CPU 和容器基础镜像。默认是否启用加密。TrustServerCertificate、CA、hostname 校验如何配置。
连接池在哪里配置。Application Name / appName 是否能写入连接。
只要驱动能连上,不代表安全和治理完成。SQL Server 在企业环境里,证书、身份、审计和权限比“端口通了”更重要。
迁移工具
常见方式:
| 工具 | 适用 | 注意 |
|---|---|---|
| Flyway | Java / 多语言 SQL migration | SQL Server 方言、schema、事务和权限要验证 |
| Liquibase | 企业变更管理 | changeSet 权限、rollback 和审计要写清 |
| SSDT / DACPAC | Microsoft 数据库项目 | 适合 .NET / Visual Studio / Azure DevOps 体系 |
| sqlcmd 脚本 | 简单初始化 | 不适合长期复杂演进 |
初始化脚本只负责建库、建 schema、建基础账号和最小表。后续结构演进必须进入 migration,不要让 Docker init、人工 SSMS 操作和 Flyway 同时改 schema。
核心机制与排障入口
实例、数据库、schema、login、user
SQL Server 的权限模型容易和 MySQL / PostgreSQL 混淆:
login 是 server 级身份。user 是 database 级身份。schema 是 database 内对象命名和授权边界。
database 是隔离数据、日志、恢复模型和权限的基本单元。sa 是 server 级高权限账号,不是应用账号。
排查:
SELECT name, type_desc, is_disabled FROM sys.server_principals;
SELECT name, type_desc FROM sys.database_principals;
SELECT name FROM sys.schemas;
SELECT DB_NAME() AS db_name, SUSER_SNAME() AS login_name, USER_NAME() AS db_user;事务日志和恢复模型
SQL Server 每个数据库都有事务日志。日志文件不是普通临时文件,不能因为 .ldf 大就删除。
查看:
SELECT name, recovery_model_desc
FROM sys.databases
WHERE name = 'tool_efficiency_sqlserver';
DBCC SQLPERF(LOGSPACE);开发库常用 Simple recovery model,避免日志无限增长:
ALTER DATABASE tool_efficiency_sqlserver SET RECOVERY SIMPLE;接近生产的测试库要按生产恢复模型演练。Full recovery model 下,只有形成连续日志备份链,才能把恢复点推进到某个时间或 LSN;单纯切成 Full 不会自动截断日志。完整恢复模式的 restore sequence要求按顺序恢复完整备份、可选差异备份和后续日志备份。缺失任何一段日志链,RPO 承诺就失去证据。
页、索引、缓存与执行计划
表和索引最终落在数据页中,Buffer Pool 缓存热点页;写事务先产生日志记录,再由后台过程把脏页刷到数据文件。一次提交的延迟因此可能来自日志盘同步写、锁等待、CPU 编译、数据页读取或网络,不能只用“磁盘慢”解释。
SQL Server 常见 B+ 树索引把 key 有序保存,聚集索引叶子就是数据行,非聚集索引叶子保存定位行所需的 key。优化器根据统计信息、参数和代价选择 scan、seek、join 和并行度,计划进入缓存后还可能因参数分布变化产生性能回归。先用实际执行计划和 Query Store 证明计划变化,再决定更新统计信息、改 SQL、增删索引或暂时固定计划。
ALTER DATABASE tool_efficiency_sqlserver
SET QUERY_STORE = ON (WAIT_STATS_CAPTURE_MODE = ON);
SELECT actual_state_desc, current_storage_size_mb, max_storage_size_mb
FROM sys.database_query_store_options;Query Store保存查询文本、计划、运行时统计和每查询等待统计。它也占数据库空间;达到容量边界进入只读后,新的回归证据会停止写入,因此团队既要设保留策略,也要告警 actual_state_desc 和存储用量。
Blocking 和 deadlock
SQL Server 性能问题经常被笼统说成“数据库慢”,实际可能是 blocking、deadlock、连接池打满、锁升级、长事务或执行计划变化。
排障入口:
SELECT
session_id,
blocking_session_id,
wait_type,
wait_time,
status,
command
FROM sys.dm_exec_requests
WHERE blocking_session_id <> 0;如果等待链正常而执行仍慢,再进入执行计划、统计信息、索引、参数嗅探和业务 SQL 诊断。先区分 blocking 与算力消耗,可以避免把锁等待误治成“缺索引”。
可以用两个 sqlcmd 应用账号会话稳定复现阻塞。先由迁移账号建立独立探针,避免依赖前面已经清理的样例行:
CREATE TABLE app.lock_probe (
id INT PRIMARY KEY,
marker NVARCHAR(32) NOT NULL
);
INSERT INTO app.lock_probe(id, marker) VALUES (1, N'ready');会话 A 先更新但不提交:
BEGIN TRANSACTION;
UPDATE app.lock_probe SET marker = N'writer-a' WHERE id = 1;
-- 保持会话打开,不提交。会话 B 更新同一行时会等待,而不是立刻成功:
SET LOCK_TIMEOUT 5000;
UPDATE app.lock_probe SET marker = N'writer-b' WHERE id = 1;预期会话 B 等待约 5 秒后得到 Msg 1222。此时由管理员查询 sys.dm_exec_requests,应看到 B 的 blocking_session_id 指向 A;回到 A 执行 ROLLBACK 后,重新运行 B 并 COMMIT 应成功。若没有等待,先确认两个会话连接的是同一实例、database 和记录。实验结束后由迁移账号执行 DROP TABLE app.lock_probe。这个实验把“长事务持锁 -> 请求等待 -> 超时 -> 回滚释放锁”的链路变成了可保存证据。
tempdb
tempdb 被排序、hash、临时表、版本存储和内部操作频繁使用。开发共享实例里,某个项目的大查询可能拖慢所有人。
排查入口:
SELECT name, size, max_size, growth
FROM tempdb.sys.database_files;共享开发库要设置项目配额和 owner,不要让一个临时批处理把 tempdb、日志或磁盘打满。
启动:
docker compose up -d
docker compose ps查看日志:
docker logs tool-sqlserver-dev --tail 120连接:
sqlcmd -S localhost,14333 -U sa -P "YOUR_STRONG_SQLSERVER_SA_PASSWORD" -C执行脚本:
sqlcmd -S localhost,14333 -U sa -P "YOUR_STRONG_SQLSERVER_SA_PASSWORD" -C -i db/sqlserver/init/001_init.sql查看数据库:
SELECT name, create_date, compatibility_level, collation_name, recovery_model_desc
FROM sys.databases;查看连接:
SELECT session_id, login_name, host_name, program_name, status
FROM sys.dm_exec_sessions
WHERE is_user_process = 1;备份:
BACKUP DATABASE tool_efficiency_sqlserver
TO DISK = '/var/opt/mssql/backup/tool_efficiency_sqlserver.bak'
WITH INIT, COMPRESSION;恢复校验:
RESTORE VERIFYONLY
FROM DISK = '/var/opt/mssql/backup/tool_efficiency_sqlserver.bak';清理本地开发环境:
docker compose down
docker volume ls | grep sqlserver只有明确是本地开发 volume,才允许删除:
docker compose down -v容器一启动就退出
现象:
docker ps 看不到容器。docker logs 出现 EULA、密码策略或权限错误。
判断:
docker logs tool-sqlserver-dev --tail 120处理:
确认 ACCEPT_EULA=Y。确认 MSSQL_SA_PASSWORD 满足复杂度。确认宿主 volume 权限可写。
确认宿主机架构在支持范围内。
sa 密码改了不生效
原因通常是旧 volume 还在。SQL Server 已初始化 master,环境变量不会重置已有 login。
判断:
docker volume ls
docker inspect tool-sqlserver-dev处理:
本地个人开发库可以受控 docker compose down -v 后重建。共享开发库不能删 volume,要用 SQL 变更密码并记录 owner。不要把“改 .env”当成改数据库状态。
容器 running 但连不上
原因:
SQL Server 还在启动恢复。端口映射写错。连接到了旧实例。
TLS / 证书参数缺失。
判断:
docker compose ps
docker logs tool-sqlserver-dev --tail 80
sqlcmd -S localhost,14333 -U sa -P "YOUR_STRONG_SQLSERVER_SA_PASSWORD" -C -Q "SELECT @@SERVERNAME, @@VERSION;"处理:
healthcheck 用 SELECT 1。开发环境固定宿主端口,例如 14333。连接串写明端口,不依赖 named instance 动态解析。
本地自签证书临时使用 -C 或 trustServerCertificate=true,共享环境使用 CA。
JDBC / ODBC 证书错误
现象:
老驱动能连,新驱动失败。Java 报 TLS certificate、hostname 或 SSL 初始化错误。
处理:
本地容器:encrypt=true;trustServerCertificate=true。团队共享:encrypt=true;trustServerCertificate=false,导入 CA,配置 hostNameInCertificate。记录驱动版本和默认加密行为。
日志文件撑满磁盘
判断:
SELECT name, recovery_model_desc FROM sys.databases;
DBCC SQLPERF(LOGSPACE);处理:
本地开发库通常使用 Simple recovery model。Full recovery model 必须配套 log backup。不允许直接删除 .ldf。
找长事务和大批量写入。
备份文件恢复不了
备份存在不等于备份可用。每次关键备份后至少做:
RESTORE VERIFYONLY
FROM DISK = '/var/opt/mssql/backup/tool_efficiency_sqlserver.bak';更严格的团队要在临时库做真实 restore 演练。容器路径和宿主路径要分清,.bak 留在容器层里等于没有备份。
SQL Server 的风险通常不是“没有密码”,而是权限和连接模板过度随意。
需要治理:
sa 只用于初始化和受控管理,不给应用和普通开发者长期使用。应用账号、迁移账号、只读账号分离。SSMS / VS Code / DataGrip 保存密码必须遵循团队安全策略。
.env、连接串、JDBC properties、ODBC DSN 和截图不能提交真实密码。共享实例使用强制 TLS,正式证书和 CA 由团队维护。备份 .bak、导出 .bacpac、日志和错误 dump 都可能含敏感数据。
Azure SQL / Managed Instance 要写费用 owner、资源组、权限、IP 防火墙和销毁策略。
敏感文件建议忽略:
db/sqlserver/backup/*.bak
db/sqlserver/backup/*.bacpac
db/sqlserver/export/*.csv
db/sqlserver/export/*.sql
.env目录模板
compose.yaml
.env.example
db/
sqlserver/
init/
001_database.sql
002_login_user.sql
migration/
verify/
verify.sql
backup/
docs/
dependency-setup.md
scripts/
sqlserver-up.sh
sqlserver-verify.sh
sqlserver-backup.sh
sqlserver-reset-dev.sh命名规范
| 对象 | 示例 | 规则 |
|---|---|---|
| 宿主端口 | 14333 | 避免默认 1433 冲突和误连 |
| database | tool_efficiency_sqlserver | 项目名 + 环境用途 |
| schema | app | 不把所有对象塞进 dbo |
| login | app_tool_efficiency | 区分 runtime / migration / readonly |
| backup | tool_efficiency_sqlserver_<RUN_ID>.bak | 带库名和运行批次 |
初始化和迁移边界
init 脚本只做基线。schema 演进进入 Flyway、Liquibase 或 DACPAC。共享开发库禁用人工 SSMS 随手改表。
每次 migration 后跑最小查询和权限验证。破坏性变更需要备份和恢复演练。
共享实例治理
共享 SQL Server 开发实例至少要有:
owner。端口、域名和证书说明。每个项目的 database / schema / login。
连接池配额。备份和清理窗口。危险操作审批:DROP DATABASE、大批量 DELETE、TRUNCATE、全库 restore。
费用和资源 owner,如果是云托管。
容量、升级与退出
容量规划至少拆成数据文件、事务日志、tempdb、备份、Query Store 和高可用副本六本账。观察增长趋势和峰值工作集,不用脱离负载的固定百分比冒充阈值:
SELECT DB_NAME(database_id) AS database_name,
type_desc,
size * 8.0 / 1024 AS size_mb,
max_size,
growth
FROM sys.master_files
ORDER BY database_id, type_desc;
SELECT counter_name, cntr_value
FROM sys.dm_os_performance_counters
WHERE counter_name IN ('Page life expectancy', 'Batch Requests/sec');新增副本会增加计算、存储、日志传输、备份与监控成本;按 core 授权的自建实例还会让“多加一台只读副本”成为许可决策。云服务则要持续看计算层级、存储、备份保留、跨区流量和长期闲置开发库。owner 每个评审周期保存数据增长率、日志产生率、峰值连接、P95 / P99 延迟、CPU、内存、IOPS、备份时长和 restore 时长,据此决定扩容、归档、索引治理或拆库。
升级前固定完整 build、edition、数据库 compatibility level、驱动和镜像 digest,在恢复副本上回归登录、TLS、执行计划、migration、备份恢复和故障切换。回退不能假设“换回旧镜像即可”:新版本创建或升级后的数据库备份通常不能直接恢复到更旧引擎。退出路径应是升级前可恢复备份、经验证的旧环境、可逆 migration 或前滚修复,以及切回连接入口的步骤;AG 滚动升级还要遵守版本混跑和 failover 顺序,并在真实拓扑演练。
| 深水区 | 现象 | 判断入口 | 配置 / 命令 / 取舍 |
|---|---|---|---|
| Developer 被拿去跑准生产 | 免费功能全,被长期接真实用户流量 | 查 edition、用途和访问来源 | Developer 仅非生产;生产用 Standard / Enterprise / 云托管 |
| ARM 容器不稳定 | Mac ARM / Windows ARM 容器启动慢或异常 | docker logs、宿主 CPU | 官方容器支持 x86-64 Linux;ARM 优先远程或云开发库 |
| LocalDB / Express / Developer 混用 | 连接串在不同人机器上指向不同实例 | 查 @@SERVERNAME、实例名、端口 | 个人用 LocalDB;团队共享用固定端口实例 |
| 旧 volume 保留旧密码 | 改 .env 后 sa 密码不变 | docker volume ls、master 状态 | 本地可重建 volume;共享库用 SQL 改密码 |
| 容器 running 但未 ready | 应用启动即连接失败 | healthcheck、SELECT 1 | 等 SQL Server 恢复完成再启动应用 |
| 1433 误连旧实例 | 查到的库不是目标库 | SELECT @@SERVERNAME, DB_NAME(); | 开发映射 14333,连接串写明端口 |
| named instance 动态端口 | 本机能连,别人连不上 | SQL Browser、防火墙、端口 | 开发文档优先固定端口 |
| TLS 证书失败 | JDBC / ODBC 新驱动连接失败 | driver 版本、连接串 | 本地临时 trust;共享环境配置 CA |
sa 泄漏 | 应用、GUI、截图里都有 sa | secret scan、连接模板 | 应用不用 sa,账号分层 |
| collation 不一致 | 中文排序、大小写敏感和生产不同 | SELECT collation_name FROM sys.databases | 建库显式 collation,生产对齐 |
| 日志撑满磁盘 | .ldf 暴涨,写入失败 | recovery model、DBCC SQLPERF(LOGSPACE) | 开发库 Simple;Full 必须 log backup |
| 备份不可恢复 | .bak 存在但 restore 失败 | RESTORE VERIFYONLY | 备份后做校验,关键库做临时恢复 |
| 初始化和 migration 打架 | Docker init、SSMS、Flyway 都改表 | migration 历史表、schema diff | init 建基线,演进交给迁移工具 |
| 权限过大 | 应用账号是 db_owner 或 sysadmin | sys.server_principals、role members | runtime / migration / readonly 分离 |
| 连接池打满实例 | 共享库大量 sleeping sessions | sys.dm_exec_sessions | 限制 pool max,设置 applicationName |
| blocking 被误判为慢 | 某些请求突然卡住 | sys.dm_exec_requests | 查 blocking_session_id,不盲目加索引 |
| tempdb 被打满 | 所有项目同时慢 | tempdb files、sessions | 共享实例设置配额和 owner |
.bak 泄漏敏感数据 | 备份被提交或传到群里 | git status、制品扫描 | 备份进受控目录,导出脱敏 |
| Azure SQL 成本失控 | 开发库长期不关 | 资源组、账单、owner | 云开发库写 TTL 和销毁责任 |
| Azure Data Studio 继续当主工具 | 新人按旧文档安装退役工具 | 官方状态、工具清单 | 更新到 SSMS 22 或 VS Code MSSQL 扩展 |
SQL Server 的深水区不是命令复杂,而是企业数据库工具链一旦进入团队共享,就会牵出授权、证书、账号、备份、日志、连接池、客户端和审计。架构师要把这些边界写进模板,而不是留给新人踩。
团队交付门槛
一个 SQL Server 开发环境达到可共享状态时,应留下这些可复核证据:
版本清单记录 edition、完整 build、镜像 digest、sqlcmd 变体和应用驱动版本;Developer 资源没有承接生产流量。连接清单记录实例名、固定端口、database、schema、TLS 主机名和证书链;身份查询能排除误连默认 1433 或旧实例。配置使用 MSSQL_SA_PASSWORD,真实 secret 不进入仓库;旧 volume 的密码变更通过 SQL 完成,而不是仅修改 .env。
Compose 持久化 data、log 和 backup 目录,healthcheck 执行真实查询;销毁重建能由 migration 恢复结构。runtime、migration、readonly 和管理员账号分离,应用账号的越权实验稳定失败,sa 不出现在应用配置和客户端共享文件中。collation、recovery model、日志备份责任和连接池上限有明确值;blocking、deadlock、tempdb 与日志空间都有第一诊断命令。
备份完成后执行 RESTORE VERIFYONLY,关键数据还会恢复到临时库做应用验证;.bak、.bacpac、SQL / CSV 导出和 .env 进入受控存储并接受敏感数据扫描。SSMS 22 或 VS Code MSSQL 扩展进入团队工具清单,Azure Data Studio 已移除;云开发库还要有费用 owner、TTL 和销毁记录。
SQL Server 的提效价值,不在“装一个数据库”,而在让 Microsoft 数据库生态进入团队后仍然可复现、可审计、可恢复、可治理。开发环境把这些基础打稳,后面的生产 runbook 才不会被本地坏习惯拖垮。
