Microsoft SQL Server 的 go-mssqldb 驅動程式

該go-mssqldb驅動程式是官方的 Microsoft Go 驅動程式,適用於 Microsoft SQL。 它是純粹 Go 實作的 TDS(Tabular Data Stream)協定,使用標準 database/sql 介面。 它不需要 ODBC 或其他 C 語言函式庫。

此驅動程式可在 Windows、Linux 和 macOS 上,讓 Go 應用程式連線至所有受支援版本的 SQL Server、Azure SQL Database、Azure SQL 受控執行個體、Fabric 中的 SQL 資料庫、Fabric Data Warehouse,以及 Azure Synapse Analytics。

選擇你的起點

Azure SQL 的生產環境基準

使用此範例作為生產導向 Azure SQL 連線的起點。 它結合了受管理身份、明確的 TLS 設定、有界池設定、請求範圍逾時、抖動暫態重試邏輯以及結構化日誌。

package main

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"log/slog"
	"math/rand"
	"net/url"
	"os"
	"time"

	mssql "github.com/microsoft/go-mssqldb"
	_ "github.com/microsoft/go-mssqldb/azuread"
)

const (
	maxAttempts    = 4
	baseBackoff    = 200 * time.Millisecond
	maxBackoff     = 3 * time.Second
	queryTimeout   = 2 * time.Second
	startupTimeoutDefault = 30 * time.Second
)

func main() {
	baseLogger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
		Level: slog.LevelInfo,
	}))

	server, ok := getenvRequired("SQL_SERVER")
	if !ok {
		baseLogger.Error("invalid configuration", "error", "missing SQL_SERVER")
		os.Exit(1)
	}

	database, ok := getenvRequired("SQL_DATABASE")
	if !ok {
		baseLogger.Error("invalid configuration", "error", "missing SQL_DATABASE")
		os.Exit(1)
	}

	fedAuth := getenvOrDefault("SQL_FEDAUTH", "ActiveDirectoryManagedIdentity")
	appName := getenvOrDefault("SQL_APP_NAME", "go-mssqldb-example")
	startupTimeout := getenvDurationOrDefault("SQL_STARTUP_TIMEOUT", startupTimeoutDefault)
	logger := baseLogger.With(
		slog.String("server", server),
		slog.String("database", database),
		slog.String("fedauth", fedAuth),
		slog.String("appName", appName),
	)

	connString := buildConnString(server, database, appName, fedAuth)

	db, err := sql.Open("azuresql", connString)
	if err != nil {
		logger.Error("open database", "error", err)
		os.Exit(1)
	}
	defer db.Close()

	db.SetMaxOpenConns(20)
	db.SetMaxIdleConns(10)
	db.SetConnMaxIdleTime(2 * time.Minute)
	db.SetConnMaxLifetime(5 * time.Minute)

	rootCtx := context.Background()

	startupCtx, cancel := context.WithTimeout(rootCtx, startupTimeout)
	defer cancel()
	err = withRetry(startupCtx, logger, func(ctx context.Context) error {
		return db.PingContext(ctx)
	})
	if err != nil {
		logger.Error("ping database", "error", err)
		os.Exit(1)
	}

	var databaseName string
	err = withRetry(rootCtx, logger, func(ctx context.Context) error {
		queryCtx, cancel := context.WithTimeout(ctx, queryTimeout)
		defer cancel()

		return db.QueryRowContext(queryCtx, "SELECT DB_NAME()").Scan(&databaseName)
	})
	if err != nil {
		logger.Error("query database", "error", err)
		os.Exit(1)
	}

	logger.Info("database ready", "database", databaseName)
}

func withRetry(ctx context.Context, logger *slog.Logger, fn func(context.Context) error) error {
	var lastErr error

	for attempt := 1; attempt <= maxAttempts; attempt++ {
		lastErr = fn(ctx)
		if lastErr == nil {
			return nil
		}

		if !isTransient(lastErr) || attempt == maxAttempts {
			return lastErr
		}

		delay := backoffWithJitter(baseBackoff, maxBackoff, attempt)
		logger.WarnContext(ctx, "transient SQL error; retrying",
			"attempt", attempt,
			"delay", delay.String(),
			"error", lastErr,
		)

		timer := time.NewTimer(delay)
		select {
		case <-ctx.Done():
			timer.Stop()
			return ctx.Err()
		case <-timer.C:
		}
	}

	return lastErr
}

func backoffWithJitter(baseDelay, maxDelay time.Duration, attempt int) time.Duration {
	delay := baseDelay * time.Duration(1<<(attempt-1))
	if delay > maxDelay {
		delay = maxDelay
	}

	jitterFraction := 0.20
	multiplier := (1 - jitterFraction) + rand.Float64()*(2*jitterFraction)
	return time.Duration(float64(delay) * multiplier)
}

func buildConnString(server, database, appName, fedAuth string) string {
	query := url.Values{
		"database":               []string{database},
		"fedauth":                []string{fedAuth},
		"encrypt":                []string{"true"},
		"TrustServerCertificate": []string{"false"},
		"app name":               []string{appName},
		"log":                    []string{"1"},
	}

	return fmt.Sprintf("sqlserver://%s?%s", server, query.Encode())
}

func getenvRequired(key string) (string, bool) {
	value := os.Getenv(key)
	if value == "" {
		return "", false
	}
	return value, true
}

func getenvOrDefault(key, defaultValue string) string {
	value, ok := getenvRequired(key)
	if !ok {
		return defaultValue
	}
	return value
}

func getenvDurationOrDefault(key string, defaultValue time.Duration) time.Duration {
	value := os.Getenv(key)
	if value == "" {
		return defaultValue
	}

	parsed, err := time.ParseDuration(value)
	if err != nil {
		return defaultValue
	}

	return parsed
}

func isTransient(err error) bool {
	var sqlErr mssql.Error
	if !errors.As(err, &sqlErr) {
		return false
	}

	switch sqlErr.Number {
	case
		// Connection-establishment and transport transient errors.
		64, 233, 4060, 4221,
		10053, 10054,
		10928, 10929,

		// Azure SQL failover, throttling, and availability errors.
		40020, 40143, 40166,
		40197, 40501, 40540, 40613,
		42108, 42109,
		49918, 49919, 49920,

		// Common retryable statement-level contention errors.
		1205, 1222:
		return true
	default:
		return false
	}
}

本範例期望 SQL_SERVER 和 SQL_DATABASE 環境變數。

可選環境變數:

  • SQL_FEDAUTH (預設值: ActiveDirectoryManagedIdentity)
  • SQL_APP_NAME (預設值: go-mssqldb-example)
  • SQL_STARTUP_TIMEOUT (預設: 30s,解析為 time.ParseDuration)

欲了解更多本範例各部分的資訊,請參閱 Azure SQL Database、連線池化、錯誤處理與重試模式,以及日誌與診斷。

主要功能

  • Pure Go:不需要 CGo 或外部 C 依賴。
  • 三種 連接字串 格式:URL (sqlserver://)、ADO (key=value和 ODBC (odbc:key=value)。
  • Microsoft Entra ID 認證:透過azuread套件支援多種憑證類型,包括管理身份、服務主體及工作負載身份。
  • SQL Server 與 Windows 驗證:支援 SQL 認證、NTLM、Kerberos 及 Windows 上的單一登入(SSO)。
  • Always Encrypted:客戶端加密,使用本地憑證、Windows 憑證商店及 Azure Key Vault 金鑰提供者。
  • 批量複製:高效能大量插入作業。
  • 資料表值參數(TVP):將結構化資料傳遞給預存程序。
  • 多種協定:TCP、命名管線、共享記憶體,以及專用管理員連線(DAC)。
  • TDS 8.0 加密:端對端加密,採用嚴格模式。

開始

文章 Description
安裝與系統需求 安裝驅動模組並確認你的 Go 環境。
快速入門:連結與查詢 連接到本地或測試的 SQL Server 實例,並在幾分鐘內執行你的第一個查詢。
從其他驅動程式遷移 從 lib/pq、pgx 或 go-sql-driver/mysql 遷移到 go-mssqldb。

設定連線

文章 Description
連接字串 URL、ADO 及 ODBC 連接字串 格式及範例。
連線選項 逾時、封包大小、容錯移轉、SessionInitSQL和NewConnector。
加密與憑證 TLS 加密模式、憑證驗證及 TDS 8.0。
SQL Server 與 Windows Authentication SQL 認證、NTLM、Kerberos 和 SSO 設定。
Microsoft Entra ID 驗證 Microsoft Entra ID 連線選項、憑證流程及令牌提供者範例。
安全性最佳做法 SQL 注入防護、秘密管理、加密以及最小特權。

處理資料

文章 Description
數據類型對應 Go 與 SQL 之間的型別轉換表,以及驅動程式特定型別。
查詢與陳述 參數化查詢、 Exec、 Query、 QueryRow及多個結果集。
交易 隔離等級、存檔點、死鎖偵測和重試模式。
錯誤處理與重試模式 SQL Server 錯誤結構、暫時錯誤偵測與指數退讓。
預存程序 輸出參數, ReturnStatus以及程序的結果集。
大量作業 使用 CopyIn 進行高效能大量插入。
資料表值參數 將結構化資料傳給儲存程序,使用 mssql.TVP。
JSON 與 XML 資料 使用 FOR JSON、OPENJSON 和 FOR XML 查詢、插入及轉換 JSON 和 XML 資料。
一律加密 客戶端加密,使用本地憑證、Windows 憑證儲存庫及 Azure Key Vault 提供者。

部署和操作

文章 Description
連接共用 設定 database/sql 連線池。
同時播出節目 Goroutine 安全、員工池、平行查詢,以及優雅關機。
性能調校 池調校、封包大小、已準備好的報表,以及批量文案。
Azure SQL Database 無密碼認證、連線限制、限速與故障轉移處理。
Troubleshooting 常見錯誤、日誌設定與憑證診斷。
Testing 整合測試模式與測試資料庫設置。

平台與協定指南

文章 Description
Linux 和 macOS 跨平台設定、Kerberos、NTLM 及憑證路徑。
通訊協定 TCP、命名管線、共享記憶體、DAC 以及 SQL 瀏覽器。
記錄和診斷 記錄旗標、SetLogger 和 SetContextLogger。

提出功能要求

要申請功能,請在 go-mssqldb GitHub 倉庫中開啟一個問題。