從其他驅動程式遷移到 go-mssqldb

本指南協助 Go 開發者從 PostgreSQL、MySQL 及其他資料庫驅動程式遷移到 go-mssqldb SQL Server 驅動程式。 它涵蓋了 SQL 語法差異、驅動程式特定的變更,以及常見操作的等效模式。

備註

本文範例與 AdventureWorks2025 範例資料庫對比。

為什麼要使用 go-mssqldb 遷移至 SQL Server

Feature SQL Server 搭配 go-mssqldb
企業安全 始終加密、列級安全、動態資料遮蔽、透明資料加密(TDE)。
Authentication Microsoft Entra ID(前稱 Azure AD)、managed identity、Windows 驗證、Kerberos.
Azure integration 原生 Azure SQL Database 支援,支援自動加密、無密碼連線及故障轉移處理。
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 資料的 nvarchar,否則應預期為 mssql.VarChar 語意。
暫存資料表的作用範圍限於實體連線。 如果後續陳述式會依賴該資料表,請將建立並使用臨時資料表的邏輯保留在同一個連線或同一個交易中。
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/pq成:go-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
自動遞增 SERIAL 或 GENERATED ALWAYS AS IDENTITY IDENTITY(1,1)
請輸入身分證 RETURNING id OUTPUT INSERTED.id 或 SELECT SCOPE_IDENTITY()
布林型 BOOLEAN BIT
字串串連 \|\| + 或 CONCAT()
目前時間戳記 NOW() 或 CURRENT_TIMESTAMP GETUTCDATE() 或 SYSDATETIMEOFFSET()
限制行數 LIMIT 10 OFFSET 20 OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY
字串類型 TEXT 或 VARCHAR 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)

Pagination

以 LIMIT/OFFSET 語法取代 OFFSET/FETCH NEXT 語法。

// 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

用標準pgxpool介面取代 database/sql 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 搭配 SetMaxOpenConns 和 SetMaxIdleConns。

從 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)
請輸入身分證 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
);

偏移/取回上限:

-- 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)

小提示

優先使用 OUTPUT 子句,而非 SCOPE_IDENTITY()。 該 OUTPUT 子句適用於批次插入,且不依賴於語句的順序。

Upsert(插入或更新)

PostgreSQL ON CONFLICT 與 MySQL ON DUPLICATE KEY UPDATE 對應至 SQL ServerMERGE:

_, 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))

批量插入

將 PostgreSQL COPY 或 MySQL LOAD DATA 替換為 mssql.CopyIn。

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.
替換參數佔位符 使用$1將/$2?或@name變更為sql.Named。
更新 SQL 語法 變更LIMIT/OFFSET、RETURNING、NOW()SERIAL及其他資料庫專用的 SQL。
取代批次作業 將 COPY 或 LOAD DATA 變更為 mssql.CopyIn。
更新 schema DDL 將 SERIAL/AUTO_INCREMENT 改為 IDENTITY,將 TEXT 改為 NVARCHAR,將 BOOLEAN 改為 BIT。
測試所有查詢 用 SQL Server 實例來測試你的測試套件,以捕捉語法差異。
設定加密 新增encrypt=true(或依賴 Azure 自動偵測)用於生產環境。
設定認證 為 Azure 設定 Microsoft Entra ID,或為本地部署設定 SQL Server 認證。