微軟 SQL Server 的 ODBC 驅動程式

下載 ODBC 驅動程式

ODBC 是一種原生資料存取應用程式設計介面(API),用於使用 C 和 C++ 撰寫的應用程式。 Microsoft ODBC 的 SQL Server 驅動程式可連接 SQL Server、Azure SQL Database、Azure SQL 受控執行個體、Azure Synapse Analytics 以及 Microsoft Fabric 中的 SQL 資料庫。 關於每個驅動版本所支援的資料庫版本,請參見 SQL 版本相容性。

你的應用程式呼叫 ODBC 驅動程式管理器,該管理器會載入驅動程式並將 ODBC 呼叫傳給它。 在 Windows 上使用 Windows 的 ODBC 驅動程式管理器,Linux 和 macOS 則使用 unixODBC。

可以使用 ODBC 的其他語言包括 COBOL、Perl、PHP 和 Python。 ODBC 廣泛應用於資料整合情境,Microsoft PHP for SQL Server 驅動程式即基於此驅動程式。

sqlcmd 的 ODBC 變體與 bcp 工具使用此驅動程式,但它們是分別安裝的:Linux 與 macOS 上的 mssql-tools18 套件,以及 Windows 上的 Microsoft 命令列工具。 sqlcmd 的 Go 版本並未使用這個驅動程式。 使用 sqlcmd 執行 Transact-SQL(T-SQL)語句、系統程序及腳本檔案。 使用 bcp 在 SQL Server 實例與資料檔案之間批量複製資料,無論雙向皆然。

選擇你的起點

Azure SQL 的生產環境基準

將這個完整的 C++17 程式作為 Azure 託管應用程式的起點。 它會連接、執行 SELECT 1、檢查結果,然後釋放 ODBC 的句柄。 在建置前先安裝 Driver 18 和平台的 ODBC 開發檔案。 關於前置條件,請參見 使用 ODBC 驅動程式開發 C 與 C++ 應用程式。

在應用程式的環境中將ODBC_CONNECTION_STRING設為完整的 連接字串。 以下範例使用系統指派的受控識別。 主機必須已啟用該身分識別,且資料庫必須授權它存取。 關於主機需求、使用者指派身分識別,以及本機開發驗證,請參閱 使用 Microsoft Entra ID 搭配 ODBC 驅動程式。

Driver={ODBC Driver 18 for SQL Server};Server=tcp:<server>.database.windows.net,1433;Database=<database>;Authentication=ActiveDirectoryMsi;Encrypt=strict;ConnectRetryCount=3;ConnectRetryInterval=2;

Encrypt=strict 需要一個支援表格資料串流(TDS)8.0 的端點。 否則,請使用 Encrypt=yes;TrustServerCertificate=no。 設定一個客戶端信任且名稱與伺服器相符的憑證。 關於設定與驗證,請參見 連線加密故障排除。

避免憑證出現在原始檔案和 shell 歷史中。 關於設定環境變數的指令,請參見「設定連線」。 在 Linux 和 macOS 上,以 UTF-8 提供其值。 將以下程式儲存為 odbc-baseline.cpp:

#ifdef _WIN32
#include <windows.h>
#else
#include <iconv.h>
#endif
#include <sql.h>
#include <sqlext.h>

#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <random>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>

struct Handles {
    SQLHENV env = SQL_NULL_HENV;
    SQLHDBC dbc = SQL_NULL_HDBC;
    SQLHSTMT stmt = SQL_NULL_HSTMT;
    bool connected = false;

    ~Handles() {
        if (stmt) SQLFreeHandle(SQL_HANDLE_STMT, stmt);
        if (connected) SQLDisconnect(dbc);
        if (dbc) SQLFreeHandle(SQL_HANDLE_DBC, dbc);
        if (env) SQLFreeHandle(SQL_HANDLE_ENV, env);
    }
};

bool logDiagnostics(SQLSMALLINT type, SQLHANDLE handle) {
    bool retry = false;
    bool permanent = false;
    for (SQLSMALLINT record = 1; ; ++record) {
        SQLWCHAR state[6] = {};
        SQLWCHAR message[1024] = {};
        SQLINTEGER native = 0;
        SQLRETURN rc = SQLGetDiagRecW(type, handle, record, state, &native,
                                     message, 1024, nullptr);
        if (rc == SQL_NO_DATA) break;
        if (!SQL_SUCCEEDED(rc)) {
            std::cerr << "event=diagnostics_failed rc=" << rc << '\n';
            return false;
        }
        std::string sqlstate;
        for (int i = 0; i < 5; ++i) {
            sqlstate += static_cast<char>(state[i]);
        }
        std::cerr << "event=odbc_diagnostic sqlstate=" << sqlstate
                  << " native_error=" << native << '\n';
        permanent |= sqlstate.substr(0, 2) == "28" ||
                     sqlstate.substr(0, 2) == "IM" ||
                     sqlstate == "HY024" || sqlstate == "HY092";
        retry |= sqlstate == "08S01" || sqlstate == "HYT00" ||
                 sqlstate == "HYT01";
        switch (native) {
            case 10928: case 10929: case 10936:
            case 40197: case 40501: case 40613:
            case 49918: case 49919: case 49920:
                retry = true;
        }
    }
    return retry && !permanent;
}

void requireSuccess(SQLRETURN rc, SQLSMALLINT type, SQLHANDLE handle,
                    const char* operation) {
    if (rc == SQL_SUCCESS) return;
    logDiagnostics(type, handle);
    if (!SQL_SUCCEEDED(rc)) throw std::runtime_error(operation);
}

std::vector<SQLWCHAR> readConnectionString() {
    static_assert(sizeof(SQLWCHAR) == 2, "Use a driver manager with 16-bit SQLWCHAR");
#ifdef _WIN32
    size_t length = 0;
    if (_wgetenv_s(&length, nullptr, 0, L"ODBC_CONNECTION_STRING") != 0 ||
        length == 0) {
        throw std::runtime_error("ODBC_CONNECTION_STRING is required");
    }
    std::vector<wchar_t> raw(length);
    if (_wgetenv_s(&length, raw.data(), raw.size(), L"ODBC_CONNECTION_STRING") != 0) {
        throw std::runtime_error("Cannot read ODBC_CONNECTION_STRING");
    }
    std::vector<SQLWCHAR> result(raw.begin(), raw.end() - 1);
#else
    const char* raw = std::getenv("ODBC_CONNECTION_STRING");
    if (!raw) throw std::runtime_error("ODBC_CONNECTION_STRING is required");
    iconv_t converter = iconv_open("UTF-16LE", "UTF-8");
    if (converter == reinterpret_cast<iconv_t>(-1)) {
        throw std::runtime_error("Cannot create UTF-8 converter");
    }
    size_t inputBytes = std::strlen(raw);
    std::vector<SQLWCHAR> result(inputBytes + 1);
    char* input = const_cast<char*>(raw);
    char* output = reinterpret_cast<char*>(result.data());
    size_t outputBytes = result.size() * sizeof(SQLWCHAR);
    const size_t capacity = outputBytes;
    size_t status = iconv(converter, &input, &inputBytes, &output, &outputBytes);
    int closeStatus = iconv_close(converter);
    if (status == static_cast<size_t>(-1) || inputBytes != 0 || closeStatus != 0) {
        throw std::runtime_error("ODBC_CONNECTION_STRING must be valid UTF-8");
    }
    result.resize((capacity - outputBytes) / sizeof(SQLWCHAR));
#endif
    if (result.empty() || result.size() >= 32767) {
        throw std::runtime_error("Invalid ODBC_CONNECTION_STRING length");
    }
    result.push_back(0);
    return result;
}

void connectWithRetry(SQLHDBC dbc, std::vector<SQLWCHAR>& connectionString) {
    constexpr int maxAttempts = 4;
    std::mt19937 random{std::random_device{}()};
    std::uniform_int_distribution<int> jitter{0, 250};
    for (int attempt = 1; attempt <= maxAttempts; ++attempt) {
        requireSuccess(SQLSetConnectAttr(
            dbc, SQL_ATTR_LOGIN_TIMEOUT,
            reinterpret_cast<SQLPOINTER>(std::uintptr_t{30}), 0),
            SQL_HANDLE_DBC, dbc, "Set login timeout");
        SQLRETURN rc = SQLDriverConnectW(
            dbc, nullptr, connectionString.data(), SQL_NTS,
            nullptr, 0, nullptr, SQL_DRIVER_NOPROMPT);
        if (SQL_SUCCEEDED(rc)) {
            if (rc == SQL_SUCCESS_WITH_INFO) logDiagnostics(SQL_HANDLE_DBC, dbc);
            std::cout << "event=connected attempt=" << attempt << '\n';
            return;
        }
        bool transient = logDiagnostics(SQL_HANDLE_DBC, dbc);
        if (!transient || attempt == maxAttempts) {
            throw std::runtime_error("Connect failed");
        }
        auto delay = std::chrono::milliseconds{
            (1 << (attempt - 1)) * 10000 + jitter(random)};
        std::cerr << "event=connect_retry attempt=" << attempt
                  << " delay_ms=" << delay.count() << '\n';
        std::this_thread::sleep_for(delay);
    }
}

int main() {
    try {
        auto connectionString = readConnectionString();
        Handles handles;
        if (!SQL_SUCCEEDED(SQLAllocHandle(
                SQL_HANDLE_ENV, SQL_NULL_HANDLE, &handles.env))) {
            throw std::runtime_error("Allocate environment");
        }
        requireSuccess(SQLSetEnvAttr(
            handles.env, SQL_ATTR_ODBC_VERSION,
            reinterpret_cast<SQLPOINTER>(std::uintptr_t{SQL_OV_ODBC3}), 0),
            SQL_HANDLE_ENV, handles.env, "Set ODBC version");
        requireSuccess(SQLAllocHandle(SQL_HANDLE_DBC, handles.env, &handles.dbc),
                       SQL_HANDLE_ENV, handles.env, "Allocate connection");
        connectWithRetry(handles.dbc, connectionString);
        handles.connected = true;
        requireSuccess(SQLAllocHandle(SQL_HANDLE_STMT, handles.dbc, &handles.stmt),
                       SQL_HANDLE_DBC, handles.dbc, "Allocate statement");
        requireSuccess(SQLSetStmtAttr(
            handles.stmt, SQL_ATTR_QUERY_TIMEOUT,
            reinterpret_cast<SQLPOINTER>(std::uintptr_t{10}), 0),
            SQL_HANDLE_STMT, handles.stmt, "Set query timeout");
        SQLWCHAR query[] = {'S', 'E', 'L', 'E', 'C', 'T', ' ', '1', 0};
        requireSuccess(SQLExecDirectW(handles.stmt, query, SQL_NTS),
                       SQL_HANDLE_STMT, handles.stmt, "Execute probe");
        requireSuccess(SQLFetch(handles.stmt),
                       SQL_HANDLE_STMT, handles.stmt, "Fetch probe");
        SQLINTEGER value = 0;
        SQLLEN indicator = 0;
        requireSuccess(SQLGetData(handles.stmt, 1, SQL_C_SLONG,
                                 &value, sizeof(value), &indicator),
                       SQL_HANDLE_STMT, handles.stmt, "Read probe");
        if (indicator == SQL_NULL_DATA || value != 1) {
            throw std::runtime_error("Unexpected probe result");
        }
        std::cout << "event=ready result=" << value << '\n';
        return 0;
    } catch (const std::exception& error) {
        std::cerr << "event=failed operation=\"" << error.what() << "\"\n";
        return 1;
    }
}

程式會請求最多四次連線嘗試中,每次的連線逾時為 30 秒,並請求 10 秒查詢逾時。 在連線暫時失敗的嘗試之間,會等待10、20和40秒,每次隨機抖動時間介於0到250毫秒。 這些限制並不對驗證設定一個固定的整體期限。 範例分類了選擇的暫態連線錯誤;請根據你的工作負載調整策略,使用 Azure SQL 暫態故障錯誤目錄。 它不會重試應用程式指令或寫入作業。

ConnectRetryCount 和 ConnectRetryInterval 會為閒置時中斷的合格連線設定連線韌性。 它們不會重試初始連線,也不會重播應用程式指令。 讓閒置恢復的延遲時間比查詢逾時短。 範例日誌記錄 SQLSTATE 值及原生錯誤編號,而不是連接字串或完整診斷訊息。 如果你需要訊息文字來排解故障,請將其記錄在受保護的診斷日誌中。

建立並執行基準

使用與你應用程式處理器架構相符的編譯器環境。 在 Windows 上,請在 Visual Studio 的開發者 PowerShell 中執行這些指令。 在 Linux 和 macOS 上,使用包含編譯器和 unixODBC 開發檔案的 shell。

cl /nologo /std:c++17 /EHsc /W4 odbc-baseline.cpp odbc32.lib
.\odbc-baseline.exe

第一次成功連線時,會顯示以下輸出。 還可能出現額外的診斷紀錄。

event=connected attempt=1
event=ready result=1

完成後清除 ODBC_CONNECTION_STRING:在 PowerShell 使用 Remove-Item Env:\ODBC_CONNECTION_STRING,在 Bash 或 Zsh 使用 unset ODBC_CONNECTION_STRING。

對於可用性群組監聽器和故障切換設定,請使用 高可用性和災難復原。

主要功能

  • 跨平台:Windows、Linux 和 macOS 使用相同的 API。
  • Microsoft Entra ID 認證:管理式身分識別、服務主體與整合式認證,以及 Windows 上的互動式認證。 可用的方法取決於主機與資料庫的配置。
  • 嚴格加密:在第 18 版及更新版本中,TDS 8.0 連線會採用完整的憑證驗證。
  • 始終加密:用戶端加密以處理敏感欄位,並支援自訂金鑰儲存提供者。
  • 連線復原能力:可透明地重新建立在閒置時中斷的連線。
  • 高可用性:可用性群組監聽器支援。MultiSubnetFailover
  • 資料分類:分類欄位的敏感性元資料。
  • 向量資料型別:原生支援 向量 型別。
  • 分散式交易:透過 Microsoft Distributed Transaction Coordinator(MSDTC)支援 XA 交易。
  • 配套工具: sqlcmd 和 bcp,分別安裝。

開始

文章 說明
下載 SQL Server 的 ODBC 驅動程式 每個支援驅動版本的安裝程式和套件下載,涵蓋三個平台。
使用 ODBC 驅動程式開發 C 與 C++ 應用程式 要包含哪些標頭、順序、連結哪些函式庫,以及如何在非同步執行與執行緒之間選擇。
使用 C++ 連接並查詢資料庫 完整的 C++ 範例,能連接、執行查詢並讀取結果,讓你能從頭到尾確認你的設定。
支援生命週期 哪些驅動程式版本仍受支援,以及每個版本的停止支援日期。
主要版本差異 從版本 17 移轉到版本 18 時,哪些地方會失效,先從預設加密設定的變更談起。

安裝驅動程式

文章 說明
系統需求、安裝與驅動程式檔案(Windows) 支援的 Windows 版本、用於靜默部署的安裝程式命令列,以及各驅動程式檔案在磁碟上的存放位置。
系統需求(Linux 與 macOS) 每個驅動程式版本支援哪些 Linux 發行版和 macOS 版本,以及與 SQL Server 版本的相容性。
在 Linux 上安裝 ODBC 驅動程式 Alpine、Debian、Red Hat、SUSE、Ubuntu 和 Azure Linux 的套件管理步驟,以及離線安裝和驅動程式檔案的位置。
在 macOS 上安裝 ODBC 驅動程式 適用於 macOS 的 Homebrew tap 與 formula 步驟,包括如何安裝 18、17 或 13.1 版。
安裝 unixODBC 驅動管理器(Linux 和 macOS) 安裝或升級 unixODBC,這個驅動管理器負責在 Linux 和 macOS 上載入 ODBC 驅動程式。

設定和連線

文章 說明
DSN 與連接字串關鍵字與屬性 完整的 連接字串 關鍵字、DSN 條目與SQLSetConnectAttr屬性目錄,並附有各項可接受的值。
連線字串關鍵字與資料來源名稱(Linux 與 macOS) 如何在 Linux 和 macOS 上odbc.iniodbcinst.ini定義 DSN,以及針對這些平台的傳輸層安全性(TLS)和傳輸控制協定(TCP)keep-alive 設定。
ODBC 資料來源管理員 DSN(Windows) 透過 Windows 使用者介面設定資料來源,而非使用 連接字串。
驅動程式感知連線池(Windows) 哪些 連接字串 關鍵字和屬性會將連線放入獨立的池中,哪些則需要額外往返重置。

認證與安全

文章 說明
使用 Microsoft Entra ID 搭配 ODBC 驅動程式 每個 Authentication 關鍵字值,從管理身份、服務主體到互動式與整合式,並附有各自所需的設定。
搭配 ODBC 驅動程式使用 Always Encrypted 在將敏感欄位值傳送到資料庫前,先加密這些值,並提供驅動程式的 API 摘要及記載的限制。
資料分類 閱讀伺服器附在機密欄位上的敏感性標籤,讓您的應用程式能執行自己的資料保護政策。
使用整合驗證(Linux 和 macOS) 設定 Kerberos,讓 Linux 或 macOS 用戶端能用 Windows 憑證連線,而非 SQL Server 登入。

高可用性與韌性

文章 說明
連線韌性 ConnectRetryCount 和 ConnectRetryInterval 如何在伺服器於連線閒置時中斷連線後恢復連線,以及在無法恢復時驅動程式會傳回的 IMCxx 錯誤。
高可用性與災難復原 透過可用性群組監聽器連接,並使用 MultiSubnetFailover 故障轉移,避免在子網路逾時卡頓。
使用透明網路 IP 解析 舊有 TransparentNetworkIPResolution 備援如何排序跨多個網際網路協定(IP)位址的連線嘗試,以及為什麼 MultiSubnetFailover 會取代它。

處理資料

文章 說明
Vector 資料類型 綁定、傳送及擷取 vector 型別,包括其原生 C 表示法及大量複製支援。
用 XA 交易來處理 DTC 透過 Windows、Linux 或 macOS 上的 Microsoft Distributed Transaction Coordinator,將 SQL Server 登錄到分散式交易中。
程式設計指引(Linux 與 macOS) 查找 Linux 與 macOS 的程式設計指引,包括功能狀態、字元集考量、OpenSSL 及 Alpine Linux。

診斷和疑難解答

文章 說明
連線加密故障排除 修正版本 18 因預設加密而出現的憑證與加密錯誤。
資料存取追蹤(Linux 與 macOS) 開啟驅動追蹤,並在需要查看應用程式實際呼叫時擷取日誌檔。
已知問題(Linux 與 macOS) 已確認缺陷及其解決方法。 在提出支援案件前,請先確認這裡。
常見問題(Linux 與 macOS) 針對 Linux 和 macOS 上驅動程式最常被問到的問題,提供簡短回答。

發行說明與錯誤修正

文章 說明
Windows 版本的發行說明 每個 Windows 驅動版本中的新功能、行為變更與修正。
Linux 與 macOS 版本的發行說明 每個 Linux 和 macOS 驅動版本中都有新功能、行為變更與修正。
SQL Server 工具的發行說明 修改了 sqlcmd 和 bcp 工具,這些工具在 Linux 和 macOS 上是獨立安裝於驅動程式之外的。

Reference

文章 說明
Windows 上的 ODBC 驅動程式 針對驅動程式在 Windows 上支援的版本逐版本摘要,以及 Windows 專屬文章的索引。
Windows 上 ODBC 驅動程式的功能 哪個版本引入了每個 Windows 功能,以及隨之而來的行為變更。

提出功能要求

若要請求功能,請透過 SQL Server 回饋提交想法。