Microsoft ODBC 드라이버 - SQL Server용

ODBC 드라이버 다운로드

ODBC는 C 및 C++로 작성된 애플리케이션을 위한 네이티브 데이터 접근 애플리케이션 프로그래밍 인터페이스(API)입니다. Microsoft ODBC Driver for SQL Server는 SQL Server, Azure SQL Database, Azure SQL Managed Instance, Azure Synapse Analytics, 그리고 Microsoft Fabric의 SQL 데이터베이스에 연결됩니다. 각 드라이버 릴리스가 지원하는 데이터베이스 버전에 대해서는 SQL 버전 호환성을 참조하세요.

애플리케이션은 ODBC 드라이버 관리자를 호출하고, 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을(를) 전체 연결 문자열로 설정하세요. 다음 예시는 시스템이 할당한 관리 ID를 사용합니다. 호스트가 해당 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를 사용합니다. 클라이언트가 신뢰하고 서버와 이름이 일치하는 인증서를 설정하세요. 설정 및 검증에 대해서는 연결 암호화 문제 해결을 참조하세요.

자격 증명은 소스 파일과 셸 기록에 넣지 마세요. 환경 변수를 설정하는 명령어에 대해서는 연결 구성을 참조하세요. 리눅스와 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에서 명령어를 실행하세요. 리눅스와 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에서는 unset ODBC_CONNECTION_STRING을(를) 사용하고, Bash 또는 Zsh에서는 Remove-Item Env:\ODBC_CONNECTION_STRING을(를) 사용하세요.

가용성 그룹 리스너와 장애 전환 설정에는 고가용성과 재해 복구를 사용하세요.

주요 기능

  • 크로스 플랫폼: Windows, Linux, macOS에서 동일한 API를 사용합니다.
  • Microsoft Entra ID 인증: 관리형 신원, 서비스 주체, 통합 인증, 그리고 Windows에서의 인터랙티브 인증. 사용 가능한 방법은 호스트와 데이터베이스 구성에 따라 다릅니다.
  • 엄격한 암호화: 버전 18 및 이후 버전에서 완전한 인증서 검증이 가능한 TDS 8.0 연결.
  • 항상 암호화: 민감한 열에 대한 클라이언트 측 암호화, 맞춤형 키스토어 제공자 지원
  • 연결 복원력: 유휴 상태에서 끊긴 연결을 투명하게 복원합니다.
  • 고가용성: MultiSubnetFailover을 사용하는 가용성 그룹 수신기 지원.
  • 데이터 분류: 분류된 열의 민감도 메타데이터.
  • 벡터 데이터 타입: 벡터 타입에 대한 네이티브 지원.
  • 분산 트랜잭션: Microsoft Distributed Transaction Coordinator(MSDTC)를 통한 XA 트랜잭션 지원.
  • 동반 도구: sqlcmd 와 bcp, 별도로 설치.

시작하기

문서 Description
SQL Server용 ODBC 드라이버 다운로드 지원되는 모든 드라이버 버전에 대한 설치 및 패키지 다운로드가 세 플랫폼 모두에서 제공됩니다.
ODBC 드라이버로 C 및 C++ 애플리케이션을 개발하세요 어떤 헤더를 포함할지, 어떤 순서로, 어떤 라이브러리를 연결할지, 그리고 비동기 실행과 스레드 중 어떻게 선택할지에 관한 것입니다.
C++로 데이터베이스에 연결하고 쿼리합니다 연결, 쿼리 실행, 결과를 읽는 완전한 C++ 샘플로, 처음부터 끝까지 설정을 확인할 수 있습니다.
지원 수명 주기 어떤 드라이버 버전이 아직 지원되는지, 그리고 각 버전이 언제 지원 종료일인지 알려주세요.
주 버전 차이점 버전 17에서 버전 18로 넘어가면 무엇이 깨지는지, 암호화 기본 변경부터 시작됩니다.

드라이버 설치

문서 Description
시스템 요구사항, 설치 및 드라이버 파일 (Windows) 지원되는 Windows 버전, 무음 배포를 위한 설치 명령줄, 그리고 각 드라이버 파일이 디스크에 위치하는 위치.
시스템 요구사항 (리눅스 및 macOS) 각 드라이버 버전이 지원하는 리눅스 배포판과 macOS 릴리스, 그리고 SQL Server 버전 호환성도 고려해야 합니다.
Linux에 ODBC 드라이버를 설치하세요 Alpine, Debian, Red Hat, SUSE, Ubuntu, Azure Linux용 패키지 관리자 단계와 오프라인 설치, 드라이버 파일 위치 등도 포함됩니다.
macOS에 ODBC 드라이버를 설치하세요 macOS용 홈브류 탭 및 공식 단계, 버전 18, 17, 또는 13.1 설치 방법도 포함됩니다.
유닉스 ODBC 드라이버 관리자 (리눅스 및 macOS) 설치 Linux와 macOS에서 ODBC 드라이버를 로드하는 드라이버 관리자인 unixODBC를 설치하거나 업그레이드하세요.

구성 및 연결

문서 Description
DSN 및 연결 문자열 키워드 및 특성 연결 문자열 키워드, DSN 항목, SQLSetConnectAttr 속성의 전체 카탈로그와 각 항목에 대한 허용 가능한 값이 포함되어 있습니다.
연결 문자열 키워드 및 데이터 소스 이름 (리눅스 및 macOS) Linux와 macOS에서 odbc.ini 및 odbcinst.ini를 사용하여 DSN을 정의하는 방법과, 해당 플랫폼에 특화된 전송 계층 보안(TLS) 및 전송 제어 프로토콜(TCP) keep-alive 설정입니다.
ODBC 데이터 소스 관리자 DSN (Windows) 데이터 소스는 연결 문자열 대신 Windows 사용자 인터페이스를 통해 구성하세요.
드라이버 인식 연결 풀링 (Windows) 어떤 연결 문자열 키워드와 특성이 연결을 별도의 풀로 분리하며, 또 어떤 항목이 재설정 시 추가 왕복 비용을 발생시키나요?

인증 및 보안

문서 Description
Microsoft Entra ID를 ODBC 드라이버와 함께 사용하세요 모든 Authentication 키워드 값(관리 ID 및 서비스 주체부터 대화형 및 통합까지)과 각각에 필요한 설정을 포함합니다.
ODBC 드라이버와 함께 항상 암호화를 사용하세요 민감한 열 값은 데이터베이스로 보내기 전에 드라이버의 API 요약과 문서화된 제한 사항을 참고하여 암호화하세요.
데이터 분류 서버가 기밀 열에 부착하는 민감성 라벨을 읽으면, 애플리케이션이 자체 데이터 보호 정책을 적용할 수 있습니다.
통합 인증 사용(리눅스 및 macOS) Kerberos를 설정해서 리눅스나 macOS 클라이언트가 SQL Server 로그인 대신 Windows 자격 증명으로 연결할 수 있게 하세요.

고가용성 및 복원력

문서 Description
연결 복원력 서버가 유휴 상태인 동안 연결을 끊을 때 IMCxx와 ConnectRetryCount가 연결을 복원하는 방법과, 복구가 불가능할 때 드라이버가 반환하는 ConnectRetryInterval 오류입니다.
고가용성 및 재해 복구 가용성 그룹 리스너를 통해 연결하고 MultiSubnetFailover를 사용하여 서브넷 시간 초과로 인해 장애 조치가 지연되지 않도록 하세요.
투명한 네트워크 IP 해상도를 사용하세요 레거시 TransparentNetworkIPResolution 폴백이 여러 인터넷 프로토콜(IP) 주소에 걸친 연결 시도 순서를 어떻게 정하는지, 그리고 MultiSubnetFailover가 이를 대체하는 이유.

데이터 작업

문서 Description
벡터 데이터 형식 벡터 타입을 원래의 C 표현과 대량 복사 지원을 포함해 바인딩, 전송, 검색을 할 수 있습니다.
DTC와 함께 XA 거래를 사용하세요 SQL Server를 Windows, Linux 또는 macOS의 Microsoft Distributed Transaction Coordinator를 통해 분산 트랜잭션에 참여시키세요.
프로그래밍 가이드라인 (리눅스 및 macOS) 기능 상태, 문자 집합 고려사항, OpenSSL, Alpine Linux를 포함한 Linux 및 macOS 프로그래밍 지침을 찾아보세요.

진단 및 문제 해결

문서 Description
연결 암호화 문제 해결 버전 18이 기본적으로 암호화하기 때문에 나타나는 인증서 및 암호화 오류를 수정하세요.
데이터 접근 추적 (리눅스 및 macOS) 드라이버 추적을 켜고, 애플리케이션이 실제로 어떤 호출을 하는지 볼 수 있을 때 로그 파일을 캡처하세요.
알려진 문제들 (리눅스와 macOS) 확인된 결함과 그 우회 방법들. 지원 요청을 접수하기 전에 여기를 확인하세요.
자주 묻는 질문 (리눅스와 macOS) 리눅스와 macOS에서 드라이버에 대해 가장 자주 묻는 질문들에 대한 짧은 답변입니다.

출시 노트 및 버그 수정

문서 Description
Windows용 릴리스 노트 각 Windows 드라이버 릴리스마다 새로운 기능, 동작 변경 사항, 그리고 수정 사항이 추가되었습니다.
리눅스와 macOS용 릴리스 노트 새로운 기능, 동작 변경, 그리고 각 리눅스 및 macOS 드라이버 릴리스의 수정 사항들.
SQL Server 도구 릴리스 노트 Linux와 macOS에서 드라이버와 별도로 설치되는 sqlcmd 및 bcp 유틸리티의 변경 사항.

Reference

문서 Description
Windows용 ODBC 드라이버 Windows에서 드라이버가 지원하는 버전을 요약한 버전별 요약과 Windows 전용 문서들의 색인입니다.
Windows에서 ODBC 드라이버의 특징 어떤 릴리스가 각 Windows 기능과 그에 따른 동작 변화를 도입했는지.

기능 요청

기능을 요청하려면 SQL Server 피드백을 통해 아이디어를 제출하세요.