这是一篇 Linux 下使用 C 语言访问 MySQL 数据库的指导文章,它涵盖了 MySQL 的编程与 C API 的基础知识。你也可以考虑阅读在 ZetCode 上的 MySQL教程

MySQL数据库和API的安装

MySQL官方提供不同操作系统的安装包,你可以在官方网站上找到它。你也可以使用下列命令安装:

sudo apt-get install mysql-server mysql-client

然后,要使用C语言编程访问数据库,需要另外安装一个开发包:

sudo apt-get install libmysqlclient15-dev  
#或
sudo apt-get install libmysqlclient-dev

执行一个SQL语句

接下来的代码示例将创建一个数据库。代码示例可分为以下部分:

  • 初始化一个连接句柄
  • 创建一个连接
  • 执行SQL语句
  • 关闭连接
#include <my_global.h>
#include <mysql.h>

int main(int argc, char **argv)
{  
  printf("MySQL client version: %s\n", mysql_get_client_info());

  MYSQL *con = mysql_init(NULL);

  if (con == NULL) 
  {
      fprintf(stderr, "%s\n", mysql_error(con));
      exit(1);
  }

  if (mysql_real_connect(con, "localhost", "root", "root_pswd", 
          NULL, 0, NULL, 0) == NULL) 
  {
      printf("Error connecting to MySQL database: %s\n", mysql_error(con));
      mysql_close(con);
      exit(1);
  }  

  if (mysql_query(con, "CREATE DATABASE testdb")) 
  {
      printf("Error when exec MySQL statement: %s\n", mysql_error(con));
      mysql_close(con);
      exit(1);
  }

  mysql_close(con);
  exit(0);
}

mysql_get_client_info() 函数能够获取 MySQL 客户端的版本。

你可以使用下面的代码初始化一个 MYSQL 连接句柄:

MYSQL *con = mysql_init(NULL);
if (con != NULL) 
{
    mysql_close(con);
}

也可以使用一个临时变量来存储 MYSQL 句柄:

MYSQL con;
if (mysql_init(&con) != NULL) 
{
    mysql_close(&con);
}

mysql_real_connect() 函数建立一个与数据库的连接,我们提供在上面初始化的连接句柄,主机名,用户名、密码、数据库名,端口号、unix socket 和客户端标志这几个参数即可。我们创建一个新的数据库需要超级用户权限。

mysql_query() 函数执行一个 SQL 语句,最后,使用 mysql_close() 函数关闭数据库连接。

执行一个查询语句

这个例子演示如何从一个表中检索数据,我们需要按照下面的步骤来做:

  • 创建一个数据库连接
  • 执行查询语句
  • 获取结果集
  • 获取结果集中的数据
  • 释放结果集
#include <my_global.h>
#include <mysql.h>

void finish_with_error(MYSQL *con)
{
  fprintf(stderr, "%s\n", mysql_error(con));
  mysql_close(con);
  exit(1);        
}

int main(int argc, char **argv)
{
  MYSQL *con = mysql_init(NULL);
  
  if (con == NULL)
  {
      fprintf(stderr, "mysql_init() failed\n");
      exit(1);
  }  
  
  if (mysql_real_connect(con, "localhost", "user12", "34klq*", 
          "testdb", 0, NULL, 0) == NULL) 
  {
      finish_with_error(con);
  } 
  
  if (mysql_query(con, "SELECT * FROM Cars LIMIT 3"))
  {  
      finish_with_error(con);
  }
  
  MYSQL_RES *result = mysql_store_result(con);

  if (result == NULL) 
  {
      finish_with_error(con);
  }  

  int num_fields = mysql_num_fields(result);

  MYSQL_ROW row;
  MYSQL_FIELD *field;
  
  while ((row = mysql_fetch_row(result))) 
  { 
      for(int i = 0; i < num_fields; i++) 
      { 
          if (i == 0) 
          {              
             while(field = mysql_fetch_field(result)) 
             {
                printf("%s ", field->name);
             }
             
             printf("\n");           
          }
          
          printf("%s  ", row[i] ? row[i] : "NULL"); 
      } 
  }
  
  printf("\n");
  
  mysql_free_result(result);
  mysql_close(con);
  
  exit(0);
}

我们可以使用 mysql_store_result()mysql_use_result() 函数获取结果集,结果集保存在 MYSQL_RES 结构体中,检索结束后使用 mysql_free_result() 释放结果集。

使用 mysql_num_fields() 函数获取字段(列)的个数。MYSQL_FIELD 结构体中包含字段名,字段类型等字段信息,但是字段值不存储在该机构体中,字段值存储在MYSQL_ROW结构体。使用下面的代码检索表中数据:

MYSQL_ROW row;

while ((row = mysql_fetch_row(result))) 
{ 
    for(int i = 0; i < num_fields; i++) 
    { 
        printf("%s ", row[i] ? row[i] : "NULL"); 
    } 
    printf("\n"); 
}

获取最后插入的一行数据的行id

有时,我们需要获取最后插入的一行数据的行id,这个需求可以使用 mysql_insert_id() 函数办到,当然,这个函数生效的前提是表中有一个自动增长(AUTO_INCREMENT)列。

#include <my_global.h>
#include <mysql.h>

void finish_with_error(MYSQL *con)
{
  fprintf(stderr, "%s\n", mysql_error(con));
  mysql_close(con);
  exit(1);        
}

int main(int argc, char **argv)
{
  
  MYSQL *con = mysql_init(NULL);
  
  if (con == NULL)
  {
      fprintf(stderr, "mysql_init() failed\n");
      exit(1);
  }
  
  if (mysql_real_connect(con, "localhost", "user12", "34klq*", 
          "testdb", 0, NULL, 0) == NULL) 
  {
      finish_with_error(con);
  }   
  
  if (mysql_query(con, "DROP TABLE IF EXISTS Writers"))
  {    
      finish_with_error(con);    
  }
  
  char *sql = "CREATE TABLE Writers(Id INT PRIMARY KEY AUTO_INCREMENT, Name TEXT)";
  
  if (mysql_query(con, sql))
  {    
      finish_with_error(con);    
  }
  
  if (mysql_query(con, "INSERT INTO Writers(Name) VALUES('Leo Tolstoy')"))
  {    
      finish_with_error(con);    
  }
  
  if (mysql_query(con, "INSERT INTO Writers(Name) VALUES('Jack London')"))
  {    
      finish_with_error(con);
  }
  
  if (mysql_query(con, "INSERT INTO Writers(Name) VALUES('Honore de Balzac')"))
  {    
      finish_with_error(con);
  }
  
  int id = mysql_insert_id(con);
  
  printf("The last inserted row id is: %d\n", id);

  mysql_close(con);
  exit(0);
}

mysql_insert_id() 函数可以返回被INSERT或UPDATE语句影响的自动增长(AUTO_INCREMENT )列生成的值。

执行多个SQL语句

我们可以在一个查询函数里执行多条 SQL 语句,但是必须在连接的时候设置 CLIENT_MULTI_STATEMENTS 标志。

#include <my_global.h>
#include <mysql.h>

void finish_with_error(MYSQL *con)
{
  fprintf(stderr, "%s\n", mysql_error(con));
  mysql_close(con);
  exit(1);        
}

int main(int argc, char **argv)
{ 
  int status = 0;  
    
  MYSQL *con = mysql_init(NULL);  
  
  if (con == NULL)
  {
      fprintf(stderr, "mysql_init() failed\n");
      exit(1);
  }  
  
  if (mysql_real_connect(con, "localhost", "user12", "34klq*", 
          "testdb", 0, NULL, CLIENT_MULTI_STATEMENTS) == NULL) 
  {
      finish_with_error(con);
  }    
  
  if (mysql_query(con, "SELECT Name FROM Cars WHERE Id=2;\
      SELECT Name FROM Cars WHERE Id=3;SELECT Name FROM Cars WHERE Id=6")) 
  {
      finish_with_error(con);
  }
  
  do {  
      MYSQL_RES *result = mysql_store_result(con);
        
      if (result == NULL) 
      {
          finish_with_error(con);
      }
            
      MYSQL_ROW row = mysql_fetch_row(result);
      
      printf("%s\n", row[0]);
      
      mysql_free_result(result);
                 
      status = mysql_next_result(con); 
     
      if (status > 0) {
          finish_with_error(con);
      }
      
  } while(status == 0);
    
  mysql_close(con);  
  exit(0);
}

插入二进制数据

有时,我们想插入图片等二进制数据到数据库中,这是一件很麻烦的事。

mysql> CREATE TABLE Images(Id INT PRIMARY KEY, Data MEDIUMBLOB);

我们先创建一个新的图片表,图片大小最大能存储16MB,这由 MEDIUMBLOB 类型决定。

#include <my_global.h>
#include <mysql.h>
#include <string.h>

void finish_with_error(MYSQL *con)
{
  fprintf(stderr, "%s\n", mysql_error(con));
  mysql_close(con);
  exit(1);        
}

int main(int argc, char **argv)
{

  FILE *fp = fopen("woman.jpg", "rb");
  
  if (fp == NULL) 
  {
      fprintf(stderr, "cannot open image file\n");    
      exit(1);
  }
      
  fseek(fp, 0, SEEK_END);
  
  if (ferror(fp)) {
      
      fprintf(stderr, "fseek() failed\n");
      int r = fclose(fp);

      if (r == EOF) {
          fprintf(stderr, "cannot close file handler\n");          
      }    
      
      exit(1);
  }  
  
  int flen = ftell(fp);
  
  if (flen == -1) {
      
      perror("error occurred");
      int r = fclose(fp);

      if (r == EOF) {
          fprintf(stderr, "cannot close file handler\n");
      }
      
      exit(1);      
  }
  
  fseek(fp, 0, SEEK_SET);
  
  if (ferror(fp)) {
      
      fprintf(stderr, "fseek() failed\n");
      int r = fclose(fp);

      if (r == EOF) {
          fprintf(stderr, "cannot close file handler\n");
      }    
      
      exit(1);
  }

  char data[flen+1];

  int size = fread(data, 1, flen, fp);
  
  if (ferror(fp)) {
      
      fprintf(stderr, "fread() failed\n");
      int r = fclose(fp);

      if (r == EOF) {
          fprintf(stderr, "cannot close file handler\n");
      }
      
      exit(1);      
  }
  
  int r = fclose(fp);

  if (r == EOF) {
      fprintf(stderr, "cannot close file handler\n");
  }          
  
  MYSQL *con = mysql_init(NULL);
  
  if (con == NULL)
  {
      fprintf(stderr, "mysql_init() failed\n");
      exit(1);
  }  
    
  if (mysql_real_connect(con, "localhost", "user12", "34klq*", 
          "testdb", 0, NULL, 0) == NULL) 
  {
      finish_with_error(con);
  }   
    
  char chunk[2*size+1];
  mysql_real_escape_string(con, chunk, data, size);

  char *st = "INSERT INTO Images(Id, Data) VALUES(1, '%s')";
  size_t st_len = strlen(st);

  char query[st_len + 2*size+1]; 
  int len = snprintf(query, st_len + 2*size+1, st, chunk);

  if (mysql_real_query(con, query, len))
  {
      finish_with_error(con);
  }
  
  mysql_close(con);
  exit(0);
}

mysql_real_escape_string() 函数添加一个转义字符(反斜杠,),这样可以避免传递给函数的字符串潜在一定的危险,这有助于防止 SQL 注入攻击。新缓冲区的长度必须至少为 2*size+1。

mysql_query() 函数不能用于二进制数据,必须是 mysql_real_query() 函数替代。

查询二进制数据

上一个例子演示如果将一个图片插入到数据库中,这个例子演示如何将插入的图片从数据库中查询出来。

#include <my_global.h>
#include <mysql.h>

void finish_with_error(MYSQL *con)
{
  fprintf(stderr, "%s\n", mysql_error(con));
  mysql_close(con);
  exit(1);        
}

int main(int argc, char **argv)
{
  FILE *fp = fopen("woman2.jpg", "wb");
  
  if (fp == NULL) 
  {
      fprintf(stderr, "cannot open image file\n");    
      exit(1);
  }

  MYSQL *con = mysql_init(NULL);
  
  if (con == NULL)
  {
      fprintf(stderr, "mysql_init() failed\n");
      exit(1);
  }  
  
  if (mysql_real_connect(con, "localhost", "user12", "34klq*", 
          "testdb", 0, NULL, 0) == NULL) 
  {
      finish_with_error(con);
  }
  
  if (mysql_query(con, "SELECT Data FROM Images WHERE Id=1"))
  {
      finish_with_error(con);
  }
  
  MYSQL_RES *result = mysql_store_result(con);
  
  if (result == NULL) 
  {
      finish_with_error(con);
  }  

  MYSQL_ROW row = mysql_fetch_row(result);
  unsigned long *lengths = mysql_fetch_lengths(result);
  
  if (lengths == NULL) {
      finish_with_error(con);
  }
  
  fwrite(row[0], lengths[0], 1, fp);

  if (ferror(fp)) 
  {            
      fprintf(stderr, "fwrite() failed\n");
      mysql_free_result(result);
      mysql_close(con);

      exit(1);      
  }  
  
  int r = fclose(fp);

  if (r == EOF) {
      fprintf(stderr, "cannot close file handler\n");
  }
  
  mysql_free_result(result);
  mysql_close(con);

  exit(0);
}

其中最关键的函数就是mysql_fetch_lengths(),它的返回值是一个数组类型,可以获取当前行中所有数据的长度。

解决中文乱码问题

中文乱码问题一般由字符集引起,可使用 mysql_set_character_set() 函数修改字符集。

MYSQL mysql;

mysql_init(&mysql_conn);
if (!mysql_real_connect(&mysql_conn,"host","user","passwd","database",0,NULL,0))
{
    fprintf(stderr, "Failed to connect to database: Error: %s\n",
          mysql_error(&mysql_conn));
}

if (!mysql_set_character_set(&mysql_conn, "utf8"))
{
    printf("New client character set: %s\n",
           mysql_character_set_name(&mysql_conn));
}

或者使用在连接 MySQL 后使用 SQL 语句设计字符集:

mysql_query (&mysql_conn, "set names utf8");

批量插入数据

使用 mysql_stmt 既可以批量插入数据又可以预处理 SQL 语句。

  1. 使用 mysql_stmt_init 初始化 MYSQL_STMT 结构体;
  2. 使用 mysql_stmt_prepare 预处理 SQL 语句,可以使用“?”作为占位符;
  3. 使用 mysql_stmt_bind_param 绑定数据;
  4. 使用 mysql_stmt_execute 完成 SQL 的执行;
  5. 如果想要批量插入数据可以重复第3、4步;
  6. 执行结束后使用 mysql_stmt_close 释放 MYSQL_STMT 结构体。
MYSQL_STMT* mysql_stmt = mysql_stmt_init(&mysql_conn);
if (mysql_stmt == nullptr)
{
   qDebug () << "Unable to create new session: Could not init statement handle" <<  mysql_error(&mysql_conn);
   result = E_MYSQL_ERROR;
}
else
{
    char sz_sql_stmt[] = "INSERT INTO `event_comments` (`et_id`, `u_id`, `c_commit_time`, `c_content`) VALUES (?, ?, ?, ?)";
    if (mysql_stmt_prepare(mysql_stmt, sz_sql_stmt, strlen(sz_sql_stmt)))
    {
        qDebug () << "Could not exec mysql_stmt_prepare" <<  mysql_stmt_error(mysql_stmt);
        result = E_MYSQL_ERROR;
    }
    else
    {
        MYSQL_BIND  bind[4];
        memset(bind, 0, sizeof(bind));

        /* set up input buffers for all 4 parameters */
        bind[0].buffer_type = MYSQL_TYPE_LONGLONG;
        bind[0].buffer = (char*)&post_id;

        bind[1].buffer_type = MYSQL_TYPE_LONGLONG;
        bind[1].buffer = (char*)&user_id;

        long long currentTime = QDateTime::currentMSecsSinceEpoch();
        bind[2].buffer_type = MYSQL_TYPE_LONGLONG;
        bind[2].buffer = (char*)&currentTime;

        bind[3].buffer_type = MYSQL_TYPE_STRING;
        bind[3].buffer = (char*)text;
        bind[3].buffer_length = strlen(text);

        if (mysql_stmt_bind_param(mysql_stmt, bind) != 0)
        {
            qDebug () << "Could not exec mysql_stmt_bind_param" <<  mysql_stmt_error(mysql_stmt);
            result = E_MYSQL_ERROR;
        }
        else
        {
            if (mysql_stmt_execute(mysql_stmt) != 0)
            {
                qDebug () << "Could not exec mysql_stmt_execute" <<  mysql_stmt_error(mysql_stmt);
                result = E_MYSQL_ERROR;
            }
            else
            {
               // succeed
            }
        }
    }
    mysql_stmt_close(mysql_stmt);
}

标签: Linux, MySQL, API, 数据库

添加新评论