从其他驱动迁移到 go-mssqldb

本指南帮助 Go 开发者从 PostgreSQL、MySQL 及其他数据库驱动迁移到 go-mssqldb SQL Server 驱动。 它涵盖了SQL语法差异、驱动程序特定的变更以及常见操作的等效模式。

注释

本文中的示例与 AdventureWorks2025 样本数据库比较。

为什么使用 go-mssqldb 迁移到 SQL Server

功能 SQL Server 与 go-mssqldb
企业安全性 始终加密、行级安全、动态数据掩蔽、透明数据加密(TDE)。
Authentication Microsoft Entra ID(前称Azure AD)、管理身份、Windows 身份验证、Kerberos.
Azure 集成 原生支持Azure SQL 数据库,支持自动加密、无密码连接和故障切换处理。
Performance 列存储索引、内存中 OLTP、用于自动计划更正的查询存储。
数据格式 内置JSON和XML支持,支持服务器端索引和查询。
Tooling SQL Server Management Studio (SSMS)、Azure Data Studio、Visual Studio Code SQL 扩展。

迁移考虑清单

在开始逐行移植查询前,请逐条查看以下内容:

注意事项 要更改的内容
占位符语法与许多 Go 驱动程序不同。 ?$1 及类似占位符替换为 @name@p1,然后在适当情况下传递 sql.Named() 参数。
不支持 LastInsertId() 请改用 OUTPUT INSERTED.<column>SELECT SCOPE_IDENTITY()
Go string 值默认映射到 Unicode 类型。 除非你选择用于非 Unicode 数据的驱动程序特定类型(例如 mssql.VarChar),否则应预期采用 nvarchar 的语义。
临时表的作用域限定在物理连接内。 如果后续语句依赖该临时表,请将创建和使用临时表的逻辑放在同一连接上或同一事务内执行。
Microsoft Entra 身份验证使用不同的驱动程序注册方式。 导入github.com/microsoft/go-mssqldb/azuread,使用sql.Open("azuresql", ...),并使用encrypt=true&TrustServerCertificate=false显式设置 Azure SQL TLS 选项。

有关这些领域的更深入指导,请参见查询与语句限制、数据类型映射Microsoft Entra ID 认证故障排除

从 lib/pq(PostgreSQL)迁移

更改导入和驱动名称

将导入和驱动名称替换 lib/pqgo-mssqldb

import _ "github.com/lib/pq"
db, err := sql.Open("postgres", connString)

// After (SQL Server with go-mssqldb):
import _ "github.com/microsoft/go-mssqldb"
db, err := sql.Open("sqlserver", connString)

更改连接字符串格式

将 PostgreSQL 的键值格式转换为 SQL Server URL:

// lib/pq connection string:
"host=<server> port=5432 user=<user> password=<password> dbname=AdventureWorks2025 sslmode=require"

// go-mssqldb connection string:
"sqlserver://<user>:<password>@<server>:1433?database=AdventureWorks2025&encrypt=true"

更改参数占位符

PostgreSQL 使用 $1$2 位置占位符。 SQL Server 使用带有 @ 的命名参数:

// PostgreSQL (lib/pq):
db.QueryContext(ctx, "SELECT * FROM users WHERE id = $1 AND status = $2", id, status)

// SQL Server (go-mssqldb):
db.QueryContext(ctx, "SELECT ProductID, Name FROM Production.Product WHERE ProductID = @id AND Color = @color",
    sql.Named("id", id),
    sql.Named("color", color))

SQL 语法变更

运算 PostgreSQL SQL Server
自动递增 SERIALGENERATED ALWAYS AS IDENTITY IDENTITY(1,1)
获取插入的 ID RETURNING id OUTPUT INSERTED.idSELECT SCOPE_IDENTITY()
布尔类型 BOOLEAN BIT
字符串拼接 \|\| +CONCAT()
当前时间戳 NOW()CURRENT_TIMESTAMP GETUTCDATE()SYSDATETIMEOFFSET()
限制行数 LIMIT 10 OFFSET 20 OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY
字符串类型 TEXTVARCHAR NVARCHAR(MAX)NVARCHAR(n)
JSON 提取 column->>'key' JSON_VALUE(column, '$.key')
Upsert INSERT ... ON CONFLICT DO UPDATE MERGE 语句
数组类型 INTEGER[] 没有原生阵列。 使用表值参数。
事例敏感性 默认区分大小写 默认不区分大小写(取决于排序)。

获取插入的 ID

将PostgreSQL的RETURNING子句替换为SQL Server的OUTPUT子句:

var id int
err := db.QueryRowContext(ctx,
    "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id",
    name, email).Scan(&id)

// SQL Server (go-mssqldb) using OUTPUT:
var id int
err := db.QueryRowContext(ctx,
    "INSERT INTO HumanResources.Department (Name, GroupName) OUTPUT INSERTED.DepartmentID VALUES (@name, @groupName)",
    sql.Named("name", name),
    sql.Named("groupName", groupName)).Scan(&id)

分页

OFFSET/FETCH NEXT语法替换LIMIT/OFFSET语法。

// PostgreSQL:
"SELECT * FROM employees ORDER BY id LIMIT $1 OFFSET $2"

// SQL Server (requires ORDER BY):
"SELECT ProductID, Name, ListPrice FROM Production.Product ORDER BY ProductID OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY"

从 pgx(PostgreSQL)迁移

pgx驱动使用自己的连接池和 API,与 database/sql 不同。 迁移时,切换到标准 database/sql 接口。

从 pgx 池改为数据库/SQL

用标准 database/sql 接口替换 pgxpool API。

// Before (pgx):
import "github.com/jackc/pgx/v5/pgxpool"
pool, err := pgxpool.New(ctx, "postgres://user:pass@<server>:5432/AdventureWorks2025")
defer pool.Close()
rows, err := pool.Query(ctx, "SELECT * FROM users WHERE id = $1", id)

// After (go-mssqldb with database/sql):
import (
    "database/sql"
    _ "github.com/microsoft/go-mssqldb"
)
db, err := sql.Open("sqlserver", "sqlserver://user:pass@<server>:1433?database=AdventureWorks2025")
defer db.Close()
rows, err := db.QueryContext(ctx,
    "SELECT ProductID, Name FROM Production.Product WHERE ProductID = @id", sql.Named("id", id))

替换 PGX 专属功能

PGX功能 go-mssqldb 的对应项
pgx.CollectRows 手动 rows.Next()rows.Scan() 循环。
pgx.RowToStructByName 手动将 rows.Scan 写入结构体字段。
批量查询(pgx.Batch 多个 ExecContext 调用或存储过程。
COPY FROM 用于批量插入 mssql.CopyIn 用于批量插入。
pgx.ConnConfig msdsn.Config 或基于 URL 的连接字符串。
pgxpool.Pool sql.DB 带有 SetMaxOpenConnsSetMaxIdleConns

从 go-sql-driver/mysql (MySQL) 迁移

把MySQL导入和驱动名互换一下

将MySQL导入和驱动名称替换为 go-mssqldb

// Before (MySQL):
import _ "github.com/go-sql-driver/mysql"
db, err := sql.Open("mysql", connString)

// After (SQL Server):
import _ "github.com/microsoft/go-mssqldb"
db, err := sql.Open("sqlserver", connString)

转换MySQL DSN格式

将MySQL DSN格式转换为SQL Server URL。

// MySQL DSN:
"user:password@tcp(<server>:3306)/AdventureWorks2025?tls=true"

// go-mssqldb URL:
"sqlserver://user:password@<server>:1433?database=AdventureWorks2025&encrypt=true"

替换问号占位符

MySQL 使用 ? 位置占位符。

// MySQL:
db.QueryContext(ctx, "SELECT * FROM users WHERE id = ? AND status = ?", id, status)

// SQL Server:
db.QueryContext(ctx, "SELECT ProductID, Name FROM Production.Product WHERE ProductID = @id AND Color = @color",
    sql.Named("id", id),
    sql.Named("color", color))

比较MySQL和SQL Server语法

运算 MySQL SQL Server
自动递增 AUTO_INCREMENT IDENTITY(1,1)
获取插入的 ID LAST_INSERT_ID() SCOPE_IDENTITY()OUTPUT INSERTED.id
限制行数 LIMIT 10 OFFSET 20 OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY
当前时间戳 NOW() GETUTCDATE()
如果 零 IFNULL(expr, default) ISNULL(expr, default)COALESCE(expr, default)
字符串长度 LENGTH(str) LEN(str)
子字符串 SUBSTRING(str, start, len) SUBSTRING(str, start, len)(相同)
日期格式 DATE_FORMAT(d, '%Y-%m-%d') FORMAT(d, 'yyyy-MM-dd')CONVERT(VARCHAR, d, 23)
Upsert INSERT ... ON DUPLICATE KEY UPDATE MERGE 语句
回溯引用 `column` [column]

替换MySQL专用语法

MySQL 语法 SQL Server 对应项
AUTO_INCREMENT IDENTITY(1,1)
LIMIT n OFFSET m OFFSET m ROWS FETCH NEXT n ROWS ONLY (需要 ORDER BY

AUTO_INCREMENT 到 IDENTITY:

-- MySQL:
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100)
);

-- SQL Server:
CREATE TABLE HumanResources.NewDepartment (
    DepartmentID SMALLINT IDENTITY(1,1) PRIMARY KEY,
    Name NVARCHAR(50) NOT NULL
);

将 LIMIT 改为 OFFSET/FETCH:

-- MySQL:
SELECT * FROM users LIMIT 10 OFFSET 20;

-- SQL Server:
SELECT ProductID, Name, ListPrice
FROM Production.Product
ORDER BY ProductID
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;

常见迁移模式

获取最后插入的标识值

使用 OUTPUT 子句 或 SCOPE_IDENTITY() 来获取插入行的身份值:

// Pattern 1: OUTPUT clause (recommended, works with batch inserts).
var id int64
err := db.QueryRowContext(ctx,
    "INSERT INTO HumanResources.Department (Name, GroupName) OUTPUT INSERTED.DepartmentID VALUES (@name, @groupName)",
    sql.Named("name", "Engineering"),
    sql.Named("groupName", "Research and Development")).Scan(&id)

// Pattern 2: SCOPE_IDENTITY (works with single-row inserts).
var id int64
err := db.QueryRowContext(ctx,
    "INSERT INTO HumanResources.Department (Name, GroupName) VALUES (@name, @groupName); SELECT SCOPE_IDENTITY()",
    sql.Named("name", "Engineering"),
    sql.Named("groupName", "Research and Development")).Scan(&id)

Tip

偏好该 OUTPUT 条款而非 SCOPE_IDENTITY()。 该 OUTPUT 条款适用于批量插入,不依赖于语句顺序。

Upsert(插入或更新)

PostgreSQL 的 ON CONFLICT 和 MySQL 的 ON DUPLICATE KEY UPDATE 映射到 SQL Server 的 MERGE

_, err := db.ExecContext(ctx, `
    MERGE HumanResources.Department AS target
    USING (SELECT @id AS DepartmentID, @name AS Name, @groupName AS GroupName) AS source
    ON target.DepartmentID = source.DepartmentID
    WHEN MATCHED THEN
        UPDATE SET Name = source.Name, GroupName = source.GroupName, ModifiedDate = GETDATE()
    WHEN NOT MATCHED THEN
        INSERT (Name, GroupName, ModifiedDate) VALUES (source.Name, source.GroupName, GETDATE());`,
    sql.Named("id", dept.ID),
    sql.Named("name", dept.Name),
    sql.Named("groupName", dept.GroupName))

批量插入

mssql.CopyIn 替换 PostgreSQL 的 COPY 或 MySQL 的 LOAD DATA

import mssql "github.com/microsoft/go-mssqldb"

stmt, err := db.Prepare(mssql.CopyIn("HumanResources.Department", mssql.BulkOptions{}, "Name", "GroupName", "ModifiedDate"))
if err != nil {
    return err
}

for _, dept := range departments {
    _, err = stmt.Exec(dept.Name, dept.GroupName, time.Now())
    if err != nil {
        return err
    }
}

// Flush the buffer.
_, err = stmt.Exec()
if err != nil {
    return err
}
stmt.Close()

处理 NULL 值

所有 Go 数据库驱动对 NULL 值的处理方式都相同,因为它们使用 database/sql 类型。

var color sql.NullString
err := db.QueryRowContext(ctx,
    "SELECT Color FROM Production.Product WHERE ProductID = @id",
    sql.Named("id", 1)).Scan(&color)

if color.Valid {
    fmt.Println(color.String)
} else {
    fmt.Println("NULL")
}

Transactions

所有 Go 数据库驱动都使用相同的 database/sql 事务 API。

tx, err := db.BeginTx(ctx, nil)
if err != nil {
    return err
}
defer tx.Rollback()

_, err = tx.ExecContext(ctx,
    "UPDATE Production.ProductInventory SET Quantity = Quantity - @qty WHERE ProductID = @pid AND LocationID = @fromLoc",
    sql.Named("qty", qty),
    sql.Named("pid", productID),
    sql.Named("fromLoc", fromLocationID))
if err != nil {
    return err
}

_, err = tx.ExecContext(ctx,
    "UPDATE Production.ProductInventory SET Quantity = Quantity + @qty WHERE ProductID = @pid AND LocationID = @toLoc",
    sql.Named("qty", qty),
    sql.Named("pid", productID),
    sql.Named("toLoc", toLocationID))
if err != nil {
    return err
}

return tx.Commit()

迁移清单

Step Action
安装驱动程序 go get github.com/microsoft/go-mssqldb
更改导入 _ "github.com/microsoft/go-mssqldb" 替换旧的驱动导入语句。
更新连接字符串 更改为 SQL Server URL 格式: sqlserver://user:pass@host?database=db
替换参数占位符 使用 sql.Named$1/$2? 更改为 @name
更新SQL语法 更改 LIMIT/OFFSETRETURNINGNOW()SERIAL及其他数据库专用的 SQL 代码。
替换批量操作 变更 COPYLOAD DATA 变为 mssql.CopyIn
更新架构 DDL SERIAL/AUTO_INCREMENT 更改为 IDENTITYTEXT 更改为 NVARCHARBOOLEAN 更改为 BIT
测试所有查询 把你的测试套件对比到SQL Server实例上,以发现语法差异。
配置加密 在生产环境中添加 encrypt=true(或依靠 Azure 自动检测)。
设置身份验证 配置 Microsoft Entra ID for Azure,或配置 SQL Server 认证用于本地。