MySQL 图片 BLOB 与连接池
学习代码:mysql/mysql.c、mysql/connection_pool.c、mysql/example.c

MySQL 这一组代码让我把数据库学习从"会写 SQL"推进到"会在 C 程序里稳定操作数据库"。项目主要包含两部分:一是把本地图片写入 MySQL 的 BLOB 字段,再从数据库读出还原成文件;二是实现一个线程安全的 MySQL 连接池,减少高并发场景下频繁建连的开销。
表结构可以这样设计:
sql
CREATE TABLE tbl_students (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(64) NOT NULL,
gender VARCHAR(16) NOT NULL,
image LONGBLOB NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_students_name_gender (name, gender)
);
写入图片时,代码没有把整张图片一次性读进内存,而是用预处理语句和 mysql_stmt_send_long_data() 分块发送:
c
#define SQL_INSERT_STUDENTS_IMAGE_QUERY \
"INSERT INTO tbl_students (name, gender, image) VALUES (?, ?, ?)"
while ((bytes_read = fread(chunk, 1, IMAGE_CHUNK_SIZE, fp)) > 0)
{
mysql_stmt_send_long_data(stmt, 2, chunk, bytes_read);
}
mysql_stmt_execute(stmt);
这里的 2 表示第三个占位符,也就是 image 字段。使用预处理语句的好处不只是防 SQL 注入,也让二进制数据绑定变得更清晰。分块上传还能避免应用侧为大文件一次性申请很大的内存。
读取图片时,思路也类似:先查询 BLOB 字段长度,再按 offset 分块取出,边取边写入本地文件。这样做比一次性缓存整张图片更稳,也更接近实际业务里处理大字段的方式。
连接池部分解决的是另一个性能问题。每次数据库操作都新建连接,会经历 TCP 连接、认证、初始化等步骤,高并发时成本很高。连接池在启动时预先创建一批连接,线程使用时借出,用完归还:
c
MYSQL *conn = connection_pool_get_connection(pool);
mysql_query(conn, "SELECT 1");
connection_pool_release_connection(pool, conn);
为了保证线程安全,连接池用 pthread_mutex_t 保护空闲链表,用 pthread_cond_t 处理等待和通知,还在借出连接时做 mysql_ping() 健康检查。我的理解是,数据库代码不能只追求"能插入一条数据",还要考虑内存、连接复用、异常恢复和并发访问。这个项目把这些工程问题集中放在一起,非常适合作为 C 语言连接数据库的进阶练习。