Microsoft ODBC Driver for SQL Server (Microsoft SQL Server 用 ODBC ドライバー)

ODBC ドライバーのダウンロード

ODBCは、CおよびC++で書かれたアプリケーション向けのネイティブデータアクセスアプリケーションプログラミングインターフェース(API)です。 Microsoft ODBC Driver for SQL Serverは、Microsoft Fabricの SQL Server、Azure SQL Database、Azure SQL Managed Instance、Azure Synapse Analytics、SQL データベースに接続しています。 各ドライバーリリースがサポートするデータベースバージョンについては、 SQLバージョン互換性を参照してください。

アプリケーションはODBCドライバーマネージャーを呼び出し、それがドライバーを読み込み、ODBC呼び出しをドライバーに渡します。 WindowsではWindowsのODBCドライバーマネージャーを使い、LinuxとmacOSではunixODBCを使います。

他には、COBOL、Perl、PHP、Python などの言語で ODBC を使用できます。 ODBCはデータ統合のシナリオで広く使われており、Microsoft Drivers for PHP for SQL Serverはこのドライバに基づいて構築されています。

sqlcmdのODBCバリアントとbcpユーティリティはこのドライバーを使用しますが、これらは別途インストールする必要があります。Linux と macOS では mssql-tools18 パッケージとして、Windows では Microsoft Command Line Utilities として提供されます。 Go版のsqlcmdはこのドライバーを使用しません。 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をアプリケーションの環境変数に完全な接続文字列として設定してください。 以下の例はシステム割り当ての管理型アイデンティティを使用しています。 ホストでそのIDが有効になっている必要があり、データベースはそのIDにアクセスを許可しなければなりません。 ホスト要件、ユーザー割り当てのアイデンティティ、ローカル開発認証については、「ODBCドライバーでMicrosoft Entra IDを使用する」を参照してください。

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 には、Tabular Data Stream (TDS) 8.0 をサポートするエンドポイントが必要です。 それ以外の場合は、Encrypt=yes;TrustServerCertificate=no を使用します。 クライアントが信頼し、名前がサーバー名と一致する証明書を設定しましょう。 設定と検証については、 接続暗号化トラブルシューティングを参照してください。

認証情報はソースファイルやシェル履歴に含めないでください。 環境変数を設定するコマンドについては、「 接続の設定」を参照してください。 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;
    }
}

プログラムは最大4回の各接続試行に対して30秒の接続タイムアウトと10秒のクエリタイムアウトを要求します。 一時的な接続試行の失敗の間には、10秒、20秒、40秒待ち、それぞれ0から250ミリ秒のランダムなジッターが加わります。 これらの制限は認証に厳密な期限を課すものではありません。 この例では、選択された一時的な接続エラーを分類しています。Azure SQLの一時的故障エラーカタログを使って、ポリシーをワークロードに合わせて適応させてください。 アプリケーションのコマンドを再試行したり書き込みを行ったりしません。

ConnectRetryCount and ConnectRetryInterval アイドル時に切断される対象の接続に対して、接続の回復性を構成します。 初期接続の再試行やアプリケーションのコマンドの再実行はしません。 アイドルリカバリーの遅延はクエリのタイムアウトより短く保ちましょう。 サンプルログは、接続文字列や完全な診断メッセージの代わりに、SQLSTATEの値やネイティブエラー番号を記録しています。 トラブルシューティングのためにメッセージテキストが必要な場合は、保護された診断ログに記録してください。

ベースラインを構築して実行する

アプリケーションのプロセッサアーキテクチャに合ったコンパイラ環境を使いましょう。 Windowsでは、Visual Studio用のDeveloper PowerShellでコマンドを実行してください。 LinuxやmacOSでは、コンパイラとunixODBC開発ファイルが揃ったシェルを使いましょう。

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上のインタラクティブ認証。 利用可能な方法はホストおよびデータベースの設定によって異なります。
  • 厳密暗号化:TDS 8.0接続で、バージョン18以降の完全な証明書検証が可能。
  • 常に暗号化:クライアント側で機密カラムを暗号化し、カスタムキーストアプロバイダーのサポート。
  • 接続レジリエンス:アイドル中に切断された接続を透明に復元すること。
  • 高可用性: MultiSubnetFailoverによる可用性グループリスナーサポート。
  • データ分類:分類された列の感度メタデータ。
  • ベクターデータタイプ: ベクター タイプのネイティブサポート。
  • 分散トランザクション:Microsoft 分散トランザクション コーディネーター(MSDTC)を通じたXAトランザクションサポート。
  • 補助ツール: sqlcmd と bcp、別々にインストール。

概要

[アーティクル] 説明
ODBC Driver for SQL Server のダウンロード サポートされているすべてのドライバーバージョンのインストーラーとパッケージのダウンロードが3つのプラットフォームすべてで提供されています。
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のホームブルータップとフォーミュラの手順、バージョン18、17、または13.1のインストール方法も含まれます。
UnixODBCドライバーマネージャー(LinuxおよびmacOS)をインストールしてください。 LinuxとmacOSでODBCドライバーを読み込むドライバーマネージャーUnixODBCをインストールしたりアップグレードしたりしてください。

構成と接続

[アーティクル] 説明
DSN と接続文字列のキーワードと属性 接続文字列のキーワード、DSN エントリ、SQLSetConnectAttr 属性の完全なカタログ。各項目で使用可能な値も含みます。
接続文字列キーワードおよびデータソース名(LinuxおよびmacOS) odbc.ini と odbcinst.ini がLinuxやmacOSでどのようにDSNを定義するか、さらにそれらのプラットフォームに特化したトランスポート層セキュリティ(TLS)や伝送制御プロトコル(TCP)のキープアライブ設定について。
ODBCデータソース管理者DSN(Windows) データソースは接続文字列ではなく、Windowsユーザーインターフェースを通じて構成してください。
ドライバー対応接続プーリング (Windows) どの接続文字列のキーワードと属性が接続を独自のプールに格納し、どのキーワードと属性がリセットのために余分なラウンド トリップ コストを発生させるのか。

認証とセキュリティ

[アーティクル] 説明
ODBCドライバーでMicrosoft Entra IDを使います マネージド ID やサービス プリンシパルからインタラクティブや統合まで、すべての Authentication キーワード値を、それぞれに必要な設定とともに紹介します。
ODBCドライバーでAlways Encryptedを使いましょう データベースに送信する前に、ドライバの API の概要と文書化された制限事項とともに、機密列の値を暗号化してください。
データの分類 サーバーが機密列に付けるセンシティブラベルを読み、アプリケーションが独自のデータ保護ポリシーを適用できるようにします。
統合認証(LinuxおよびmacOS)を使います Kerberosを設定して、LinuxやmacOSクライアントがSQL ServerのログインではなくWindowsの認証情報で接続できるようにしてください。

高い可用性と回復性

[アーティクル] 説明
接続の回復性 サーバーがアイドル中に接続を切断した際、どうやってConnectRetryCountConnectRetryInterval復旧し、復旧が不可能なときにドライバーが返すIMCxxエラーについても教えてください。
高可用性とディザスター リカバリー 可用性グループのリスナーを通じて接続し、フェイルオーバーがサブネットのタイムアウトで停止しないように MultiSubnetFailover を使いましょう。
透過的なネットワークIP解決を使用 レガシー TransparentNetworkIPResolution フォールバックが複数のインターネットプロトコル(IP)アドレス間で接続試行を順序付け、なぜ MultiSubnetFailover それに取って代わるのか。

データの処理

[アーティクル] 説明
ベクトル データ型 vector 型をバインド、送信、取得します。これには、そのネイティブの C 表現および一括コピーのサポートが含まれます。
DTCでのXAトランザクションをご利用ください SQL Serverを分散トランザクションに組み込み、Windows、Linux、macOSのMicrosoft 分散トランザクション コーディネーターを通じて活用できます。
プログラミングガイドライン(LinuxおよびmacOS) 機能状況、文字セットの考慮事項、OpenSSL、Alpine Linuxを含むLinuxおよびmacOSのプログラミングガイダンスを見つけられます。

診断とトラブルシューティング

[アーティクル] 説明
接続暗号化のトラブルシューティング バージョン18がデフォルトで暗号化しているため、証明書や暗号化エラーを修正してください。
データアクセストレーシング(LinuxおよびmacOS) ドライバートレースをオンにして、アプリケーションが実際に行う呼び出しを確認するためにログファイルを取得してください。
既知の問題点(LinuxおよびmacOS) 確認された欠陥とその回避策。 サポートケースを提出する前に、こちらを確認してください。
よくある質問(LinuxおよびmacOS) LinuxやmacOSのドライバーについてよく出てくる質問への短い回答です。

リリースノートとバグ修正

[アーティクル] 説明
Windows向けリリースノート 各Windowsドライバーリリースには新機能、挙動の変更、修正が含まれています。
LinuxおよびmacOS向けのリリースノート LinuxおよびmacOSの各ドライバーリリースにおける新機能、挙動変更、修正。
SQL Serverツールのリリースノート LinuxおよびmacOSではドライバーとは別にインストールされる sqlcmd および bcp ユーティリティの変更点。

Reference

[アーティクル] 説明
Windows上のODBCドライバー Windowsでドライバーがサポートしている内容のバージョンごとの概要と、Windows専用記事の索引です。
Windows上のODBCドライバーの特徴 どのリリースがWindowsの各機能とそれに伴う挙動の変更を導入したか。

機能をリクエストする

機能をリクエストするには、SQL Serverのフィードバックを通じてアイデアを提出してください。