## I. MySQL C API 소개 MySQL C API는 C 프로그램이 MySQL 데이터베이스와 직접 소통할 수 있게 해주는 핵심 인터페이스다. 이 API는 MySQL과 함께 제공되는 `libmysqlclient` 라이브러리에 구현되어 있으며, MySQL 클라이언트/서버 프로토콜에 대한 로우레벨 접근을 제공한다. MySQL C API의 중요성은 단순히 C 애플리케이션 개발에만 국한되지 않는다. Connector/J나 Connector/NET 같은 몇몇 예외를 제외하면, 대부분의 MySQL 클라이언트 API와 커넥터들이 내부적으로 `libmysqlclient`를 사용하고 있기 때문이다. 따라서 C API를 이해하면 이런 상위 레벨 인터페이스들의 동작 원리도 더 깊이 파악할 수 있다. ### 개발 환경 구성 요소 MySQL C API 개발을 위해서는 특정 헤더 파일들, 특히 `mysql.h`를 포함해야 한다. 이 헤더 파일에는 API의 공개 인터페이스를 구성하는 필수 요소들이 정의되어 있다: - 데이터 구조체 정의 - 함수 프로토타입 - 상수 및 매크로 (예: `MYSQL_FIELD` 멤버를 편리하게 테스트할 수 있는 매크로들) **중요한 호환성 주의사항**: 컴파일 시 사용한 헤더 파일 버전과 링크된 `libmysqlclient` 라이브러리 버전이 일치해야 한다. 버전 불일치는 다음과 같은 문제를 야기할 수 있다: - `Commands out of sync` 오류 - 예기치 않은 프로그램 종료 (Core Dump) - 예측할 수 없는 런타임 동작 이런 문제가 발생하면 올바른 헤더와 라이브러리로 클라이언트 프로그램을 재컴파일해야 한다. ## II. 개발 환경 설정 ### A. 시스템 전제 조건 및 MySQL 설치 먼저 MySQL을 설치해야 한다. MySQL C API 클라이언트 라이브러리인 `libmysqlclient`는 일반적으로 표준 MySQL 배포판에 포함되어 있다. 설치 방법(패키지 관리자, 바이너리 배포판, 소스 컴파일 등)에 따라 라이브러리와 헤더 파일의 위치가 달라질 수 있다. 런타임 라이브러리 외에도 개발을 위해서는 헤더 파일들이 필요하다. 많은 리눅스 배포판에서는 이런 개발 파일들이 별도 패키지로 제공된다: - Debian/Ubuntu: `libmysqlclient-dev` - Red Hat/CentOS: `mysql-devel` 또는 `mariadb-devel` **버전 호환성**: 설치된 `libmysqlclient`와 헤더의 버전이 대상 MySQL 서버 버전과 호환되는지 확인해야 한다. MySQL은 일반적으로 우수한 하위 호환성을 유지하지만, 너무 오래된 클라이언트를 최신 서버와 함께 사용하거나 그 반대의 경우 문제가 발생할 수 있다. ### B. 컴파일 및 링크 MySQL C API 애플리케이션은 GCC 같은 표준 C 컴파일러로 컴파일할 수 있다. 이 과정을 단순화하기 위해 MySQL은 `mysql_config` 유틸리티를 제공한다. #### mysql_config 사용법 ```bash # 컴파일러 플래그 확인 $ mysql_config --cflags -I/usr/include/mysql # 링커 플래그 확인 $ mysql_config --libs -L/usr/lib/mysql -lmysqlclient -lpthread -lz -lm -lrt -ldl # 실제 컴파일 예시 $ gcc my_program.c -o my_program `mysql_config --cflags --libs` ``` `mysql_config`를 사용하면 다음과 같은 이점이 있다: - 시스템별 경로 차이를 자동으로 처리 - 필요한 종속성 라이브러리를 자동으로 포함 - 빌드 스크립트의 이식성 향상 경로를 직접 하드코딩하는 것보다는 `mysql_config`나 `pkg-config`를 사용하는 것이 좋다. ## III. 기본 C API 개념 및 워크플로우 MySQL C API를 효과적으로 사용하려면 핵심 데이터 구조와 표준 작업 흐름을 이해해야 한다. ### A. 주요 데이터 구조 MySQL C API는 몇 가지 중요한 데이터 구조를 중심으로 구성되어 있다: #### MYSQL - 연결 핸들러 - **역할**: 단일 데이터베이스 연결을 나타내는 불투명 구조체 - **사용법**: 거의 모든 C API 함수에 전달되는 핵심 요소 - **주의사항**: 내부 멤버에 직접 접근하지 말고, API 함수를 통해서만 상호작용해야 한다 #### MYSQL_RES - 결과 집합 - **역할**: `SELECT`, `SHOW`, `DESCRIBE` 등의 쿼리 결과를 담는 구조체 - **특징**: 검색된 행과 관련 메타데이터를 보유 - **접근법**: API 함수를 통해서만 내용에 접근 가능 #### MYSQL_ROW - 단일 행 데이터 - **정의**: `char*` 배열로 구현된 타입 정의 - **데이터 표현**: 모든 값이 null로 끝나는 C 문자열로 표현됨 - **접근 방법**: `row[i]`로 i번째 열에 직접 접근 - **메모리 관리**: `MYSQL_RES` 구조체에 의해 관리되며, 다음 행을 가져오거나 결과 집합이 해제될 때까지만 유효 #### MYSQL_FIELD - 열 메타데이터 - **역할**: 결과 집합의 각 열에 대한 상세 정보 제공 - **포함 정보**: - `name`: 필드 이름 - `org_name`: 원본 이름 (별칭이 있는 경우) - `table`: 소속 테이블 - `type`: 데이터 타입 - `length`: 표시 길이 (바이트 단위) - `max_length`: 현재 결과 집합에서 실제 최대 길이 - `flags`: 다양한 속성 플래그 **중요**: `length`는 문자 집합에 따라 달라질 수 있다. 예를 들어, `CHAR(3)` 열의 경우: - `latin1`: length = 3 - `utf8mb4`: length = 12 (문자당 최대 4바이트) ### B. 표준 워크플로우 MySQL C API를 사용하는 일반적인 순서는 다음과 같다: #### 1. 라이브러리 초기화 ```c // 특히 다중 스레드 환경에서 중요 // 모든 MySQL API 사용 전에 한 번만 호출 if (mysql_library_init(0, NULL, NULL)) { fprintf(stderr, "라이브러리 초기화 실패\n"); exit(EXIT_FAILURE); } ``` **다중 스레드 주의사항**: `mysql_library_init()`는 스레드 안전하지 않으므로, 다중 스레드 애플리케이션에서는 다른 스레드가 생성되기 전에 호출하거나 뮤텍스로 보호해야 한다. #### 2. 연결 핸들 초기화 ```c MYSQL *conn = mysql_init(NULL); if (conn == NULL) { fprintf(stderr, "MYSQL 객체 할당 실패\n"); exit(EXIT_FAILURE); } ``` `mysql_init(NULL)`은 새 `MYSQL` 객체를 할당하고 초기화한다. 메모리 부족 시 `NULL`을 반환하므로 반드시 확인해야 한다. #### 3. 서버 연결 ```c if (mysql_real_connect(conn, "localhost", "user", "password", "mydb", 0, NULL, 0) == NULL) { fprintf(stderr, "연결 실패: %s\n", mysql_error(conn)); mysql_close(conn); exit(EXIT_FAILURE); } ``` **매개변수 설명**: - `host`: 서버 주소 ("localhost", IP 주소 등) - `user`: 사용자명 - `password`: 암호 - `db`: 기본 데이터베이스 (NULL 가능) - `port`: 포트 번호 (0이면 기본값 사용) - `unix_socket`: Unix 소켓 경로 (TCP/IP 사용 시 NULL) - `client_flag`: 클라이언트 옵션 플래그 **중요한 클라이언트 플래그들**: - `CLIENT_COMPRESS`: 압축 활성화 - `CLIENT_SSL`: SSL 암호화 - `CLIENT_MULTI_STATEMENTS`: 다중 구문 지원 #### 4. SQL 쿼리 실행 ```c // 일반적인 쿼리 (null로 끝나는 문자열) if (mysql_query(conn, "SELECT * FROM users")) { fprintf(stderr, "쿼리 실행 실패: %s\n", mysql_error(conn)); // 오류 처리 } // 바이너리 데이터나 길이가 명시된 쿼리 const char *query = "INSERT INTO data VALUES (?)"; unsigned long length = strlen(query); if (mysql_real_query(conn, query, length)) { fprintf(stderr, "쿼리 실행 실패: %s\n", mysql_error(conn)); // 오류 처리 } ``` **선택 기준**: - `mysql_query()`: 일반적인 텍스트 쿼리용 - `mysql_real_query()`: 바이너리 데이터나 NULL 바이트가 포함된 쿼리용 #### 5. 결과 처리 **결과 집합이 있는 쿼리 (SELECT, SHOW 등)**: ```c MYSQL_RES *result = mysql_store_result(conn); if (result == NULL) { // 결과가 없는 쿼리인지 오류인지 확인 if (mysql_field_count(conn) > 0) { fprintf(stderr, "결과 저장 실패: %s\n", mysql_error(conn)); } // else: INSERT, UPDATE, DELETE 등의 쿼리 } else { // 결과 처리 MYSQL_ROW row; unsigned int num_fields = mysql_num_fields(result); while ((row = mysql_fetch_row(result))) { unsigned long *lengths = mysql_fetch_lengths(result); for (unsigned int i = 0; i < num_fields; i++) { if (row[i]) { printf("열 %u: %.*s (길이 %lu)\n", i, (int)lengths[i], row[i], lengths[i]); } else { printf("열 %u: NULL\n", i); } } } mysql_free_result(result); } ``` **버퍼링 방식 선택**: | 방식 | 함수 | 장점 | 단점 | 적합한 상황 | |------|------|------|------|-------------| | 버퍼링 | `mysql_store_result()` | • 전체 행 수 파악 가능<br>• 무작위 접근 가능<br>• 서버 리소스 빠른 해제 | 대용량 결과 시 메모리 부족 위험 | 작은~중간 크기 결과 집합 | | 비버퍼링 | `mysql_use_result()` | 메모리 효율적 | • 순차 접근만 가능<br>• 서버 리소스 장기 점유<br>• 빠른 처리 필요 | 대용량 결과 집합 | #### 6. 영향받은 행 수 확인 (INSERT, UPDATE, DELETE) ```c my_ulonglong affected = mysql_affected_rows(conn); if (affected == (my_ulonglong)-1) { fprintf(stderr, "오류 또는 쿼리 실패: %s\n", mysql_error(conn)); } else { printf("%llu개 행이 영향받음\n", affected); } ``` #### 7. 리소스 정리 ```c // 결과 집합 해제 (사용한 경우) if (result) { mysql_free_result(result); } // 연결 종료 mysql_close(conn); // 라이브러리 종료 (프로그램 끝에서) mysql_library_end(); ``` ### C. 오류 처리 견고한 애플리케이션을 위해서는 철저한 오류 처리가 필수다. #### 오류 정보 함수들 - `mysql_errno(conn)`: 숫자 오류 코드 반환 - `mysql_error(conn)`: 사람이 읽을 수 있는 오류 메시지 반환 - `mysql_sqlstate(conn)`: 표준 SQLSTATE 오류 코드 반환 #### 일반적인 오류 처리 패턴 ```c if (mysql_query(conn, "SOME SQL STATEMENT")) { fprintf(stderr, "쿼리 실패: 오류 %u (%s): %s\n", mysql_errno(conn), mysql_sqlstate(conn), mysql_error(conn)); // 적절한 복구 또는 종료 처리 } ``` **주의사항**: 오류 정보는 의심되는 작업 직후에 즉시 확인해야 한다. 성공한 API 호출이 이전 오류 정보를 재설정할 수 있기 때문이다. ## IV. SQL 구문 실행: CRUD 작업 MySQL C API는 모든 종류의 SQL 명령을 실행할 수 있는 통합된 메커니즘을 제공한다. 차이점은 실행 방법이 아니라 서버 응답을 처리하는 방식에 있다. ### A. SELECT: 데이터 검색 ```c // 예시: 전자제품 카테고리의 상품 조회 if (mysql_query(conn, "SELECT id, name, price FROM products WHERE category = 'electronics'")) { fprintf(stderr, "SELECT 오류: %s\n", mysql_error(conn)); } else { MYSQL_RES *result = mysql_store_result(conn); if (result == NULL) { fprintf(stderr, "결과 저장 오류: %s\n", mysql_error(conn)); } else { printf("발견된 상품: %llu개\n", mysql_num_rows(result)); MYSQL_ROW row; while ((row = mysql_fetch_row(result))) { printf("ID: %s, 이름: %s, 가격: %s\n", row[0] ? row[0] : "NULL", row[1] ? row[1] : "NULL", row[2] ? row[2] : "NULL"); } mysql_free_result(result); } } ``` ### B. INSERT: 새 레코드 추가 ```c // 보안상 준비된 문이나 mysql_real_escape_string 사용 권장 // 여기서는 예시를 위해 하드코딩된 값 사용 if (mysql_query(conn, "INSERT INTO products (name, price, category) " "VALUES ('신상품', 299000, 'electronics')")) { fprintf(stderr, "INSERT 오류: %s\n", mysql_error(conn)); } else { my_ulonglong affected = mysql_affected_rows(conn); printf("%llu개 행이 삽입됨\n", affected); // AUTO_INCREMENT 키가 있는 경우 생성된 ID 확인 if (affected > 0) { my_ulonglong last_id = mysql_insert_id(conn); if (last_id > 0) { printf("생성된 ID: %llu\n", last_id); } } } ``` **AUTO_INCREMENT 관련 주의사항**: - `mysql_insert_id()`는 연결별로 관리됨 - 여러 행을 삽입하는 경우 첫 번째 행의 ID를 반환 - 관련 INSERT 작업 직후에 호출해야 함 ### C. UPDATE: 기존 데이터 수정 ```c // 실제 애플리케이션에서는 준비된 구문 사용 권장 char update_query[256]; int product_id = 1; double new_price = 279000; // 주의: sprintf는 SQL 인젝션에 취약할 수 있음 snprintf(update_query, sizeof(update_query), "UPDATE products SET price = %.2f WHERE id = %d", new_price, product_id); if (mysql_query(conn, update_query)) { fprintf(stderr, "UPDATE 오류: %s\n", mysql_error(conn)); } else { my_ulonglong affected = mysql_affected_rows(conn); printf("%llu개 행이 수정됨\n", affected); } ``` ### D. DELETE: 레코드 제거 ```c char delete_query[256]; int product_id = 2; snprintf(delete_query, sizeof(delete_query), "DELETE FROM products WHERE id = %d", product_id); if (mysql_query(conn, delete_query)) { fprintf(stderr, "DELETE 오류: %s\n", mysql_error(conn)); } else { my_ulonglong affected = mysql_affected_rows(conn); printf("%llu개 행이 삭제됨\n", affected); } ``` ### E. mysql_affected_rows() 이해하기 `mysql_affected_rows()`는 DML 문의 결과를 파악하는 핵심 함수다: **반환값 의미**: - 양수: 실제로 영향받은 행 수 - `0`: 조건에 맞는 행이 없음 - `(my_ulonglong)-1`: 오류 발생 **UPDATE의 특별한 동작**: - 기본적으로 실제로 변경된 행 수만 반환 - 값이 동일하게 업데이트되면 0 반환 - `CLIENT_FOUND_ROWS` 플래그 사용 시 WHERE 절에 일치하는 모든 행 수 반환 ```c my_ulonglong affected = mysql_affected_rows(conn); if (affected == (my_ulonglong)-1) { fprintf(stderr, "오류 발생: %s\n", mysql_error(conn)); } else if (affected == 0) { printf("조건에 맞는 행이 없음\n"); } else { printf("%llu개 행 처리됨\n", affected); } ``` ## V. 고급 C API 프로그래밍 ### A. Prepared Statements Prepared Statement는 안전하고 효율적인 데이터베이스 프로그래밍의 핵심이다. #### 장점 1. **보안**: SQL 인젝션 공격 완전 차단 2. **성능**: 반복 실행 시 구문 분석 오버헤드 감소 3. **편의성**: 데이터 타입 자동 변환 #### 핵심 구조체 **MYSQL_STMT**: 준비된 구문 핸들 ```c MYSQL_STMT *stmt = mysql_stmt_init(conn); if (stmt == NULL) { fprintf(stderr, "문 초기화 실패: %s\n", mysql_error(conn)); } ``` **MYSQL_BIND**: 매개변수와 결과 바인딩 ```c MYSQL_BIND bind[3]; memset(bind, 0, sizeof(bind)); // 예시: INT, VARCHAR, DOUBLE 바인딩 int id = 1; char name[100] = "제품명"; double price = 50000.0; my_bool is_null[3] = {0, 0, 0}; // 첫 번째 매개변수 (INT) bind[0].buffer_type = MYSQL_TYPE_LONG; bind[0].buffer = &id; bind[0].is_null = &is_null[0]; // 두 번째 매개변수 (VARCHAR) bind[1].buffer_type = MYSQL_TYPE_STRING; bind[1].buffer = name; bind[1].buffer_length = sizeof(name); bind[1].is_null = &is_null[1]; // 세 번째 매개변수 (DOUBLE) bind[2].buffer_type = MYSQL_TYPE_DOUBLE; bind[2].buffer = &price; bind[2].is_null = &is_null[2]; ``` #### 완전한 사용 예시 ```c // 1. 구문 준비 const char *sql = "INSERT INTO products (name, price, category) VALUES (?, ?, ?)"; if (mysql_stmt_prepare(stmt, sql, strlen(sql))) { fprintf(stderr, "문 준비 실패: %s\n", mysql_stmt_error(stmt)); mysql_stmt_close(stmt); return; } // 2. 매개변수 바인딩 char name[100] = "노트북"; double price = 1500000.0; char category[50] = "전자제품"; MYSQL_BIND bind[3]; memset(bind, 0, sizeof(bind)); bind[0].buffer_type = MYSQL_TYPE_STRING; bind[0].buffer = name; bind[0].buffer_length = strlen(name); bind[1].buffer_type = MYSQL_TYPE_DOUBLE; bind[1].buffer = &price; bind[2].buffer_type = MYSQL_TYPE_STRING; bind[2].buffer = category; bind[2].buffer_length = strlen(category); if (mysql_stmt_bind_param(stmt, bind)) { fprintf(stderr, "매개변수 바인딩 실패: %s\n", mysql_stmt_error(stmt)); mysql_stmt_close(stmt); return; } // 3. 실행 if (mysql_stmt_execute(stmt)) { fprintf(stderr, "문 실행 실패: %s\n", mysql_stmt_error(stmt)); } else { printf("행 삽입 성공, 영향받은 행: %llu\n", mysql_stmt_affected_rows(stmt)); } // 4. 정리 mysql_stmt_close(stmt); ``` #### SELECT 구문의 준비된 실행 ```c // 결과를 받을 변수들 int result_id; char result_name[100]; double result_price; my_bool is_null[3]; unsigned long length[3]; // 결과 바인딩 설정 MYSQL_BIND result_bind[3]; memset(result_bind, 0, sizeof(result_bind)); result_bind[0].buffer_type = MYSQL_TYPE_LONG; result_bind[0].buffer = &result_id; result_bind[0].is_null = &is_null[0]; result_bind[1].buffer_type = MYSQL_TYPE_STRING; result_bind[1].buffer = result_name; result_bind[1].buffer_length = sizeof(result_name); result_bind[1].length = &length[1]; result_bind[1].is_null = &is_null[1]; result_bind[2].buffer_type = MYSQL_TYPE_DOUBLE; result_bind[2].buffer = &result_price; result_bind[2].is_null = &is_null[2]; // 쿼리 준비 및 실행 const char *select_sql = "SELECT id, name, price FROM products WHERE category = ?"; if (mysql_stmt_prepare(stmt, select_sql, strlen(select_sql)) == 0) { // 매개변수 바인딩 (category) // ... (위와 유사한 바인딩 코드) mysql_stmt_execute(stmt); mysql_stmt_bind_result(stmt, result_bind); // 결과 가져오기 while (mysql_stmt_fetch(stmt) == 0) { printf("ID: %d, 이름: %s, 가격: %.2f\n", result_id, is_null[1] ? "NULL" : result_name, result_price); } } ``` ### B. 트랜잭션 관리 트랜잭션은 여러 데이터베이스 작업을 하나의 원자적 단위로 묶어 데이터 일관성을 보장한다. #### 자동 커밋 모드 MySQL은 기본적으로 자동 커밋 모드로 동작한다. 각 SQL 문이 즉시 커밋되어 되돌릴 수 없게 된다. #### 수동 트랜잭션 제어 ```c // 자동 커밋 비활성화 (트랜잭션 시작) if (mysql_autocommit(conn, 0)) { fprintf(stderr, "자동 커밋 비활성화 실패: %s\n", mysql_error(conn)); return; } // 첫 번째 작업 if (mysql_query(conn, "UPDATE accounts SET balance = balance - 10000 WHERE id = 1")) { fprintf(stderr, "첫 번째 UPDATE 실패: %s\n", mysql_error(conn)); mysql_rollback(conn); return; } // 두 번째 작업 if (mysql_query(conn, "UPDATE accounts SET balance = balance + 10000 WHERE id = 2")) { fprintf(stderr, "두 번째 UPDATE 실패: %s\n", mysql_error(conn)); mysql_rollback(conn); return; } // 모든 작업이 성공하면 커밋 if (mysql_commit(conn)) { fprintf(stderr, "커밋 실패: %s\n", mysql_error(conn)); mysql_rollback(conn); } else { printf("트랜잭션이 성공적으로 커밋됨\n"); } // 자동 커밋 모드 복원 (선택사항) mysql_autocommit(conn, 1); ``` #### SQL 기반 트랜잭션 제어 API 함수 대신 SQL 명령을 사용할 수도 있다: ```c mysql_query(conn, "START TRANSACTION"); // 또는 mysql_query(conn, "BEGIN"); // 작업 수행... mysql_query(conn, "COMMIT"); // 또는 오류 시 mysql_query(conn, "ROLLBACK"); ``` ### C. 여러 결과 집합 처리 저장 프로시저나 다중 구문 실행 시 여러 결과가 반환될 수 있다. #### 설정 요구사항 ```c // 연결 시 다중 문/결과 플래그 설정 if (mysql_real_connect(conn, "localhost", "user", "pass", "db", 0, NULL, CLIENT_MULTI_STATEMENTS)) { // 연결 성공 } ``` #### 여러 결과 처리 패턴 ```c // 다중 구문 실행 if (mysql_query(conn, "SELECT * FROM users; SELECT * FROM products; UPDATE stats SET count = count + 1")) { fprintf(stderr, "다중 쿼리 실행 실패: %s\n", mysql_error(conn)); return; } do { // 현재 결과 처리 MYSQL_RES *result = mysql_store_result(conn); if (result) { // SELECT 쿼리의 결과 집합 printf("결과 집합 - 필드 수: %u, 행 수: %llu\n", mysql_num_fields(result), mysql_num_rows(result)); MYSQL_ROW row; while ((row = mysql_fetch_row(result))) { // 행 처리... } mysql_free_result(result); } else { // 결과 집합이 없는 쿼리 (INSERT, UPDATE 등) if (mysql_field_count(conn) > 0) { // 오류 발생 fprintf(stderr, "결과 검색 오류: %s\n", mysql_error(conn)); break; } else { // 정상적인 비SELECT 쿼리 printf("영향받은 행: %llu\n", mysql_affected_rows(conn)); } } // 다음 결과가 있는지 확인하고 이동 int status = mysql_next_result(conn); if (status > 0) { fprintf(stderr, "다음 결과로 이동 실패: %s\n", mysql_error(conn)); break; } } while (status == 0); // status == 0: 더 많은 결과가 있음, -1: 끝 ``` **주의사항**: 모든 결과를 완전히 처리하지 않으면 "Commands out of sync" 오류가 발생할 수 있다. ### D. 문자 집합 관리 올바른 문자 집합 처리는 데이터 무결성에 매우 중요하다. #### 연결 문자 집합 설정 ```c // 연결 후 즉시 문자 집합 설정 if (mysql_set_character_set(conn, "utf8mb4")) { fprintf(stderr, "문자 집합 설정 실패: %s\n", mysql_error(conn)); } else { printf("문자 집합이 %s로 설정됨\n", mysql_character_set_name(conn)); } ``` **중요**: `mysql_set_character_set()`을 사용해야 하는 이유: - 서버와 클라이언트 모두의 문자 집합을 설정 - `mysql_real_escape_string()`이 올바른 문자 집합으로 이스케이프 수행 - 단순히 `SET NAMES` SQL을 실행하는 것보다 안전 #### 문자 집합 정보 확인 ```c // 현재 문자 집합 이름 확인 printf("현재 문자 집합: %s\n", mysql_character_set_name(conn)); // 상세 정보 확인 MY_CHARSET_INFO charset_info; mysql_get_character_set_info(conn, &charset_info); printf("문자 집합 세부 정보:\n"); printf(" 이름: %s\n", charset_info.name); printf(" 설명: %s\n", charset_info.comment); printf(" 기본 콜레이션: %s\n", charset_info.csname); ``` ## VI. MySQL 데이터 타입과 C 언어 매핑 ### A. 기본 쿼리에서의 데이터 처리 `mysql_fetch_row()`를 사용하면 모든 데이터가 문자열로 반환된다: ```c MYSQL_ROW row; while ((row = mysql_fetch_row(result))) { // 모든 row[i]는 char* 타입 int id = atoi(row[0]); // 정수 변환 char *name = row[1]; // 문자열 그대로 사용 double price = strtod(row[2], NULL); // 실수 변환 // NULL 값 확인 if (row[3] == NULL) { printf("설명: NULL\n"); } else { printf("설명: %s\n", row[3]); } } ``` ### B. Prepared Statement에서의 타입 매핑 Prepared Statement에서는 `MYSQL_BIND`를 통해 직접적인 타입 매핑이 가능하다: #### 주요 타입 매핑표 | MySQL 타입 | MYSQL_BIND 타입 | C 타입 | 특별 고려사항 | |------------|-----------------|--------|---------------| | `TINYINT` | `MYSQL_TYPE_TINY` | `signed char` / `unsigned char` | `is_unsigned` 설정 | | `SMALLINT` | `MYSQL_TYPE_SHORT` | `short` / `unsigned short` | `is_unsigned` 설정 | | `INT` | `MYSQL_TYPE_LONG` | `int` / `unsigned int` | `is_unsigned` 설정 | | `BIGINT` | `MYSQL_TYPE_LONGLONG` | `long long` / `unsigned long long` | `is_unsigned` 설정 | | `FLOAT` | `MYSQL_TYPE_FLOAT` | `float` | - | | `DOUBLE` | `MYSQL_TYPE_DOUBLE` | `double` | - | | `DECIMAL` | `MYSQL_TYPE_NEWDECIMAL` | `char[]` (문자열) | 정밀도 유지 위해 | | `VARCHAR` | `MYSQL_TYPE_STRING` | `char[]` | `length`, `buffer_length` 설정 | | `TEXT` | `MYSQL_TYPE_BLOB` | `char[]` | `length`, `buffer_length` 설정 | | `DATE` | `MYSQL_TYPE_DATE` | `MYSQL_TIME` | 날짜 전용 | | `DATETIME` | `MYSQL_TYPE_DATETIME` | `MYSQL_TIME` | 날짜+시간 | | `TIME` | `MYSQL_TYPE_TIME` | `MYSQL_TIME` | 시간 전용 | #### 시간 데이터 처리 예시 ```c MYSQL_TIME datetime; MYSQL_BIND bind; memset(&bind, 0, sizeof(bind)); bind.buffer_type = MYSQL_TYPE_DATETIME; bind.buffer = &datetime; // 날짜/시간 설정 (입력용) datetime.year = 2024; datetime.month = 12; datetime.day = 25; datetime.hour = 15; datetime.minute = 30; datetime.second = 45; datetime.second_part = 500000; // 마이크로초 (0.5초) // 실행 후 결과 확인 (출력용) printf("날짜: %04d-%02d-%02d %02d:%02d:%02d.%06ld\n", datetime.year, datetime.month, datetime.day, datetime.hour, datetime.minute, datetime.second, datetime.second_part); ``` ### C. NULL 값 처리 #### 기본 쿼리에서 ```c MYSQL_ROW row = mysql_fetch_row(result); if (row[i] == NULL) { // SQL NULL 값 printf("NULL 값입니다\n"); } else { // 정상 값 printf("값: %s\n", row[i]); } ``` #### Prepared Statement에서 ```c my_bool is_null; int value; MYSQL_BIND bind; memset(&bind, 0, sizeof(bind)); bind.buffer_type = MYSQL_TYPE_LONG; bind.buffer = &value; bind.is_null = &is_null; // 실행 후 if (is_null) { printf("NULL 값입니다\n"); } else { printf("값: %d\n", value); } ``` ### D. 데이터 변환 문제 해결 #### 문자열 길이 처리 ```c char buffer[256]; unsigned long actual_length; MYSQL_BIND bind; memset(&bind, 0, sizeof(bind)); bind.buffer_type = MYSQL_TYPE_STRING; bind.buffer = buffer; bind.buffer_length = sizeof(buffer) - 1; // null 터미네이터 공간 bind.length = &actual_length; // 실행 후 길이 확인 if (actual_length >= sizeof(buffer)) { printf("경고: 데이터가 잘림 (실제 길이: %lu, 버퍼 크기: %lu)\n", actual_length, sizeof(buffer) - 1); } ``` #### 큰 정수 안전 처리 ```c // BIGINT를 long long으로 안전하게 처리 long long big_value; my_bool is_unsigned = 0; // signed BIGINT MYSQL_BIND bind; memset(&bind, 0, sizeof(bind)); bind.buffer_type = MYSQL_TYPE_LONGLONG; bind.buffer = &big_value; bind.is_unsigned = is_unsigned; ``` ## VII. 맥락에서 본 MySQL C API ### A. 다른 인터페이스와의 비교 #### MySQL C API vs Connector/C++ | 측면 | MySQL C API | Connector/C++ | |------|-------------|---------------| | **언어** | C | C++ | | **추상화 수준** | 매우 낮음 (직접 제어) | 중간~높음 (객체지향) | | **메모리 관리** | 수동 (malloc/free 스타일) | 자동 (RAII, 스마트 포인터) | | **오류 처리** | 반환값 확인 | C++ 예외 | | **성능** | 최고 (최소 오버헤드) | 약간의 래퍼 오버헤드 | | **개발 편의성** | 복잡함 | 간편함 | ```c // C API 스타일 MYSQL *conn = mysql_init(NULL); if (mysql_real_connect(conn, "host", "user", "pass", "db", 0, NULL, 0)) { if (mysql_query(conn, "SELECT * FROM users")) { fprintf(stderr, "오류: %s\n", mysql_error(conn)); } else { MYSQL_RES *res = mysql_store_result(conn); // 결과 처리... mysql_free_result(res); } mysql_close(conn); } ``` ```cpp // Connector/C++ 스타일 try { std::unique_ptr<Connection> conn(driver->connect("tcp://host:3306", "user", "pass")); conn->setSchema("db"); std::unique_ptr<Statement> stmt(conn->createStatement()); std::unique_ptr<ResultSet> res(stmt->executeQuery("SELECT * FROM users")); while (res->next()) { // 결과 처리... } // 자동 리소스 해제 } catch (SQLException &e) { std::cerr << "오류: " << e.what() << std::endl; } ``` #### MySQL C API vs ODBC | 측면 | MySQL C API | ODBC | |------|-------------|------| | **이식성** | MySQL 전용 | 여러 DB 지원 | | **표준화** | MySQL 고유 | 산업 표준 | | **기능 접근** | MySQL 모든 기능 | 표준 SQL 기능 위주 | | **설정 복잡도** | 단순 | DSN 설정 필요 | | **성능** | 직접 접근으로 빠름 | 드라이버 계층으로 약간 느림 | ### B. 언제 C API를 선택할까? #### C API가 적합한 경우: 1. **최대 성능이 필요한 경우** - 고빈도 거래 시스템 - 실시간 데이터 처리 - 메모리 사용량이 극도로 중요한 임베디드 시스템 2. **MySQL 특화 기능이 필요한 경우** - 특정 MySQL 확장 기능 사용 - 낮은 수준의 프로토콜 제어 - 커스텀 데이터베이스 도구 개발 3. **C 프로젝트와의 통합** - 기존 C 코드베이스 - 시스템 프로그래밍 - 다른 고급 언어 바인딩 개발 #### 다른 옵션을 고려할 경우: - **개발 편의성 중시**: Connector/C++ - **데이터베이스 독립성 필요**: ODBC - **최신 C++ 기능 활용**: X DevAPI - **빠른 프로토타이핑**: 고급 언어 바인딩 (Python, Java 등) ## VIII. 모범 사례 ### A. 보안 우선 고려사항 #### 1. SQL 인젝션 완벽 차단 **DO: Prepared Statement 사용** ```c // 안전한 방법 MYSQL_STMT *stmt = mysql_stmt_init(conn); mysql_stmt_prepare(stmt, "SELECT * FROM users WHERE name = ?", -1); MYSQL_BIND bind; memset(&bind, 0, sizeof(bind)); bind.buffer_type = MYSQL_TYPE_STRING; bind.buffer = user_input; bind.buffer_length = strlen(user_input); mysql_stmt_bind_param(stmt, &bind); mysql_stmt_execute(stmt); ``` **DON'T: 문자열 연결** ```c // 위험한 방법 - 절대 사용 금지! char query[1000]; sprintf(query, "SELECT * FROM users WHERE name = '%s'", user_input); mysql_query(conn, query); // SQL 인젝션 취약점! ``` #### 2. 문자열 이스케이프 (Prepared Statement를 못 쓸 때만) ```c // 마지막 수단으로만 사용 char escaped_input[1000]; unsigned long escaped_length; // 반드시 올바른 문자 집합 설정 후 사용 mysql_set_character_set(conn, "utf8mb4"); escaped_length = mysql_real_escape_string(conn, escaped_input, user_input, strlen(user_input)); char query[2000]; snprintf(query, sizeof(query), "SELECT * FROM users WHERE name = '%s'", escaped_input); ``` #### 3. SSL/TLS 연결 ```c // SSL 설정 (연결 전에 호출) mysql_ssl_set(conn, "/path/to/client-key.pem", // 클라이언트 키 "/path/to/client-cert.pem", // 클라이언트 인증서 "/path/to/ca-cert.pem", // CA 인증서 NULL, // CA 경로 NULL); // 암호 suite // SSL 강제 연결 if (mysql_real_connect(conn, "host", "user", "pass", "db", 3306, NULL, CLIENT_SSL)) { printf("SSL 연결 성공\n"); } else { fprintf(stderr, "SSL 연결 실패: %s\n", mysql_error(conn)); } ``` ### B. 성능 최적화 전략 #### 1. 결과 집합 처리 최적화 ```c // 대용량 데이터: 스트리밍 처리 MYSQL_RES *result = mysql_use_result(conn); MYSQL_ROW row; while ((row = mysql_fetch_row(result))) { // 즉시 처리하여 서버 리소스 해제 process_row_immediately(row); } mysql_free_result(result); // 소용량 데이터: 전체 버퍼링 MYSQL_RES *result = mysql_store_result(conn); printf("총 %llu개 행\n", mysql_num_rows(result)); // 무작위 접근 가능 mysql_data_seek(result, 100); // 100번째 행으로 이동 ``` #### 2. 연결 재사용 패턴 ```c typedef struct { MYSQL *connections[MAX_CONNECTIONS]; int available[MAX_CONNECTIONS]; int count; pthread_mutex_t mutex; } connection_pool_t; MYSQL* get_connection(connection_pool_t *pool) { pthread_mutex_lock(&pool->mutex); for (int i = 0; i < pool->count; i++) { if (pool->available[i]) { pool->available[i] = 0; pthread_mutex_unlock(&pool->mutex); return pool->connections[i]; } } pthread_mutex_unlock(&pool->mutex); return NULL; // 사용 가능한 연결 없음 } void return_connection(connection_pool_t *pool, MYSQL *conn) { pthread_mutex_lock(&pool->mutex); for (int i = 0; i < pool->count; i++) { if (pool->connections[i] == conn) { pool->available[i] = 1; break; } } pthread_mutex_unlock(&pool->mutex); } ``` #### 3. 배치 처리 최적화 ```c // 개별 INSERT 대신 배치 INSERT 사용 mysql_autocommit(conn, 0); // 트랜잭션 시작 MYSQL_STMT *stmt = mysql_stmt_init(conn); mysql_stmt_prepare(stmt, "INSERT INTO products (name, price) VALUES (?, ?)", -1); for (int i = 0; i < 1000; i++) { // 매개변수 바인딩 및 실행 bind_and_execute(stmt, products[i]); // 100개마다 중간 커밋 if (i % 100 == 0) { mysql_commit(conn); } } mysql_commit(conn); // 최종 커밋 mysql_autocommit(conn, 1); // 자동 커밋 복원 ``` ### C. 견고성 강화 #### 1. 포괄적 오류 처리 ```c typedef enum { DB_SUCCESS = 0, DB_CONNECTION_ERROR, DB_QUERY_ERROR, DB_NO_DATA, DB_MEMORY_ERROR } db_result_t; db_result_t execute_query_safe(MYSQL *conn, const char *query, MYSQL_RES **result) { if (!conn || !query) { return DB_CONNECTION_ERROR; } if (mysql_query(conn, query)) { fprintf(stderr, "쿼리 실행 실패 [%u]: %s\n", mysql_errno(conn), mysql_error(conn)); // 연결 상태 확인 if (mysql_errno(conn) == CR_SERVER_GONE_ERROR || mysql_errno(conn) == CR_SERVER_LOST) { return DB_CONNECTION_ERROR; } return DB_QUERY_ERROR; } *result = mysql_store_result(conn); if (!*result && mysql_field_count(conn) > 0) { fprintf(stderr, "결과 저장 실패: %s\n", mysql_error(conn)); return DB_MEMORY_ERROR; } return DB_SUCCESS; } ``` #### 2. 자동 재연결 처리 ```c int reconnect_if_needed(MYSQL *conn) { if (mysql_ping(conn)) { fprintf(stderr, "연결 끊김 감지, 재연결 시도...\n"); mysql_close(conn); conn = mysql_init(NULL); if (!mysql_real_connect(conn, host, user, pass, db, port, NULL, 0)) { fprintf(stderr, "재연결 실패: %s\n", mysql_error(conn)); return -1; } // 문자 집합 재설정 mysql_set_character_set(conn, "utf8mb4"); printf("재연결 성공\n"); } return 0; } ``` #### 3. 리소스 관리 자동화 ```c // RAII 스타일 래퍼 (C11 이상) typedef struct { MYSQL *conn; MYSQL_RES *result; MYSQL_STMT *stmt; } db_context_t; void cleanup_db_context(db_context_t *ctx) { if (ctx->result) { mysql_free_result(ctx->result); ctx->result = NULL; } if (ctx->stmt) { mysql_stmt_close(ctx->stmt); ctx->stmt = NULL; } if (ctx->conn) { mysql_close(ctx->conn); ctx->conn = NULL; } } #define WITH_DB_CONTEXT(ctx) \ for (db_context_t ctx = {0}; \ !ctx.cleanup_done; \ cleanup_db_context(&ctx), ctx.cleanup_done = 1) // 사용 예시 WITH_DB_CONTEXT(db) { db.conn = mysql_init(NULL); if (mysql_real_connect(db.conn, host, user, pass, db_name, 0, NULL, 0)) { // 작업 수행... // 블록 종료 시 자동 정리 } } ``` ### D. 문자 집합 및 국제화 #### 1. UTF-8 완전 지원 ```c // 연결 직후 반드시 설정 if (mysql_set_character_set(conn, "utf8mb4")) { fprintf(stderr, "UTF-8 설정 실패: %s\n", mysql_error(conn)); return -1; } // 현재 설정 확인 printf("현재 문자 집합: %s\n", mysql_character_set_name(conn)); // 서버 변수도 확인 가능 MYSQL_RES *result; if (mysql_query(conn, "SHOW VARIABLES LIKE 'character_set%'") == 0) { result = mysql_store_result(conn); MYSQL_ROW row; while ((row = mysql_fetch_row(result))) { printf("%s: %s\n", row[0], row[1]); } mysql_free_result(result); } ``` #### 2. 바이너리 데이터 처리 ```c // 바이너리 데이터가 포함된 쿼리 const char *binary_data = "\x00\x01\x02\x03\x04"; size_t data_length = 5; char *escaped = malloc(data_length * 2 + 1); unsigned long escaped_length = mysql_real_escape_string(conn, escaped, binary_data, data_length); char query[1000]; snprintf(query, sizeof(query), "INSERT INTO binary_table (data) VALUES ('%s')", escaped); // mysql_real_query 사용 (길이 명시) mysql_real_query(conn, query, strlen(query)); free(escaped); ``` ## IX. 실전 예제: 완전한 애플리케이션 간단한 사용자 관리 시스템을 통해 지금까지 배운 내용을 종합해보겠다. ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <mysql/mysql.h> typedef struct { MYSQL *conn; char *host; char *user; char *password; char *database; } db_config_t; // 연결 설정 및 초기화 int db_connect(db_config_t *config) { config->conn = mysql_init(NULL); if (!config->conn) { fprintf(stderr, "MySQL 초기화 실패\n"); return -1; } // SSL 설정 (선택사항) mysql_ssl_set(config->conn, NULL, NULL, NULL, NULL, NULL); if (!mysql_real_connect(config->conn, config->host, config->user, config->password, config->database, 0, NULL, CLIENT_SSL)) { fprintf(stderr, "연결 실패: %s\n", mysql_error(config->conn)); mysql_close(config->conn); return -1; } // UTF-8 설정 if (mysql_set_character_set(config->conn, "utf8mb4")) { fprintf(stderr, "문자 집합 설정 실패: %s\n", mysql_error(config->conn)); return -1; } printf("데이터베이스 연결 성공 (문자 집합: %s)\n", mysql_character_set_name(config->conn)); return 0; } // 사용자 추가 (Prepared Statement 사용) int add_user(db_config_t *config, const char *name, const char *email, int age) { MYSQL_STMT *stmt = mysql_stmt_init(config->conn); if (!stmt) { fprintf(stderr, "문 초기화 실패\n"); return -1; } const char *query = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)"; if (mysql_stmt_prepare(stmt, query, strlen(query))) { fprintf(stderr, "문 준비 실패: %s\n", mysql_stmt_error(stmt)); mysql_stmt_close(stmt); return -1; } // 매개변수 바인딩 MYSQL_BIND bind[3]; memset(bind, 0, sizeof(bind)); // 이름 bind[0].buffer_type = MYSQL_TYPE_STRING; bind[0].buffer = (void*)name; bind[0].buffer_length = strlen(name); // 이메일 bind[1].buffer_type = MYSQL_TYPE_STRING; bind[1].buffer = (void*)email; bind[1].buffer_length = strlen(email); // 나이 bind[2].buffer_type = MYSQL_TYPE_LONG; bind[2].buffer = (void*)&age; if (mysql_stmt_bind_param(stmt, bind)) { fprintf(stderr, "매개변수 바인딩 실패: %s\n", mysql_stmt_error(stmt)); mysql_stmt_close(stmt); return -1; } // 실행 if (mysql_stmt_execute(stmt)) { fprintf(stderr, "실행 실패: %s\n", mysql_stmt_error(stmt)); mysql_stmt_close(stmt); return -1; } printf("사용자 추가 완료 (ID: %llu)\n", mysql_stmt_insert_id(stmt)); mysql_stmt_close(stmt); return 0; } // 사용자 목록 조회 int list_users(db_config_t *config) { if (mysql_query(config->conn, "SELECT id, name, email, age FROM users ORDER BY id")) { fprintf(stderr, "쿼리 실행 실패: %s\n", mysql_error(config->conn)); return -1; } MYSQL_RES *result = mysql_store_result(config->conn); if (!result) { fprintf(stderr, "결과 저장 실패: %s\n", mysql_error(config->conn)); return -1; } printf("\n=== 사용자 목록 ===\n"); printf("ID\t이름\t\t이메일\t\t\t나이\n"); printf("------------------------------------------------\n"); MYSQL_ROW row; while ((row = mysql_fetch_row(result))) { printf("%s\t%-10s\t%-20s\t%s\n", row[0] ? row[0] : "NULL", row[1] ? row[1] : "NULL", row[2] ? row[2] : "NULL", row[3] ? row[3] : "NULL"); } printf("총 %llu명의 사용자\n\n", mysql_num_rows(result)); mysql_free_result(result); return 0; } // 사용자 수정 (트랜잭션 사용) int update_user(db_config_t *config, int user_id, const char *new_email) { // 트랜잭션 시작 if (mysql_autocommit(config->conn, 0)) { fprintf(stderr, "트랜잭션 시작 실패: %s\n", mysql_error(config->conn)); return -1; } // 사용자 존재 확인 MYSQL_STMT *check_stmt = mysql_stmt_init(config->conn); mysql_stmt_prepare(check_stmt, "SELECT COUNT(*) FROM users WHERE id = ?", -1); MYSQL_BIND check_bind; memset(&check_bind, 0, sizeof(check_bind)); check_bind.buffer_type = MYSQL_TYPE_LONG; check_bind.buffer = &user_id; mysql_stmt_bind_param(check_stmt, &check_bind); mysql_stmt_execute(check_stmt); int count; MYSQL_BIND result_bind; memset(&result_bind, 0, sizeof(result_bind)); result_bind.buffer_type = MYSQL_TYPE_LONG; result_bind.buffer = &count; mysql_stmt_bind_result(check_stmt, &result_bind); mysql_stmt_fetch(check_stmt); mysql_stmt_close(check_stmt); if (count == 0) { printf("사용자 ID %d를 찾을 수 없습니다.\n", user_id); mysql_rollback(config->conn); mysql_autocommit(config->conn, 1); return -1; } // 이메일 업데이트 MYSQL_STMT *update_stmt = mysql_stmt_init(config->conn); mysql_stmt_prepare(update_stmt, "UPDATE users SET email = ? WHERE id = ?", -1); MYSQL_BIND update_bind[2]; memset(update_bind, 0, sizeof(update_bind)); update_bind[0].buffer_type = MYSQL_TYPE_STRING; update_bind[0].buffer = (void*)new_email; update_bind[0].buffer_length = strlen(new_email); update_bind[1].buffer_type = MYSQL_TYPE_LONG; update_bind[1].buffer = &user_id; mysql_stmt_bind_param(update_stmt, update_bind); if (mysql_stmt_execute(update_stmt)) { fprintf(stderr, "업데이트 실패: %s\n", mysql_stmt_error(update_stmt)); mysql_rollback(config->conn); mysql_stmt_close(update_stmt); mysql_autocommit(config->conn, 1); return -1; } // 커밋 if (mysql_commit(config->conn)) { fprintf(stderr, "커밋 실패: %s\n", mysql_error(config->conn)); mysql_rollback(config->conn); mysql_stmt_close(update_stmt); mysql_autocommit(config->conn, 1); return -1; } printf("사용자 %d의 이메일이 '%s'로 업데이트되었습니다.\n", user_id, new_email); mysql_stmt_close(update_stmt); mysql_autocommit(config->conn, 1); return 0; } // 정리 함수 void db_cleanup(db_config_t *config) { if (config->conn) { mysql_close(config->conn); config->conn = NULL; } } int main() { // MySQL 라이브러리 초기화 if (mysql_library_init(0, NULL, NULL)) { fprintf(stderr, "MySQL 라이브러리 초기화 실패\n"); exit(EXIT_FAILURE); } // 데이터베이스 설정 db_config_t db_config = { .host = "localhost", .user = "testuser", .password = "testpass", .database = "testdb" }; // 연결 if (db_connect(&db_config) != 0) { mysql_library_end(); exit(EXIT_FAILURE); } // 테이블 생성 (존재하지 않는 경우) const char *create_table = "CREATE TABLE IF NOT EXISTS users (" "id INT AUTO_INCREMENT PRIMARY KEY," "name VARCHAR(100) NOT NULL," "email VARCHAR(255) UNIQUE NOT NULL," "age INT," "created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP" ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"; if (mysql_query(db_config.conn, create_table)) { fprintf(stderr, "테이블 생성 실패: %s\n", mysql_error(db_config.conn)); } else { printf("사용자 테이블 준비 완료\n"); } // 실제 작업 수행 add_user(&db_config, "김철수", "[email protected]", 30); add_user(&db_config, "이영희", "[email protected]", 25); add_user(&db_config, "박민수", "[email protected]", 35); list_users(&db_config); update_user(&db_config, 2, "[email protected]"); list_users(&db_config); // 정리 db_cleanup(&db_config); mysql_library_end(); printf("프로그램 종료\n"); return 0; } ``` 이 예제는 다음 모범 사례들을 보여준다: 1. **구조화된 설정 관리**: `db_config_t` 구조체로 연결 정보 관리 2. **Prepared Statement 사용**: SQL 인젝션 방지 3. **트랜잭션 활용**: 데이터 일관성 보장 4. **포괄적 오류 처리**: 모든 API 호출 후 오류 확인 5. **적절한 리소스 관리**: 메모리 및 연결 정리 6. **문자 집합 설정**: UTF-8 완전 지원 ## X. 결론 MySQL C API는 데이터베이스 프로그래밍에서 최고의 성능과 제어력을 제공하는 강력한 도구다. 하지만 이런 강력함에는 책임이 따른다. ### 핵심 포인트 요약 1. **보안이 최우선**: Prepared Statement 사용으로 SQL 인젝션 완전 차단 2. **철저한 오류 처리**: 모든 API 호출 후 반환값 확인 3. **메모리 관리 주의**: 할당된 모든 리소스 명시적 해제 4. **문자 집합 일관성**: UTF-8 설정으로 국제화 지원 5. **트랜잭션 활용**: 데이터 일관성 보장 ### 선택 기준 **MySQL C API를 선택해야 하는 경우**: - 최고 성능이 필요한 고빈도 거래 시스템 - MySQL 특화 기능 활용이 필요한 경우 - 기존 C 프로젝트와의 긴밀한 통합 - 임베디드 시스템의 메모리 제약 - 커스텀 데이터베이스 도구 개발 **다른 옵션을 고려할 경우**: - 개발 편의성이 중요하다면 → Connector/C++ - 여러 데이터베이스 지원이 필요하다면 → ODBC - 빠른 프로토타이핑이 목표라면 → 고급 언어 바인딩 MySQL C API는 올바르게 사용하면 견고하고 효율적인 데이터베이스 애플리케이션을 만들 수 있는 훌륭한 도구다. 이 가이드에서 다룬 모범 사례들을 따라 안전하고 성능 좋은 애플리케이션을 개발할 수 있다.