## 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는 올바르게 사용하면 견고하고 효율적인 데이터베이스 애플리케이션을 만들 수 있는 훌륭한 도구다. 이 가이드에서 다룬 모범 사례들을 따라 안전하고 성능 좋은 애플리케이션을 개발할 수 있다.