【MySQL】Linux下如何用语言连接数据库?

在这里插入图片描述
本篇博客由 CSDN@先搞面包再谈爱 原创,转载请标注清楚,请勿抄袭。

前言

在我讲第一篇MySQL的博客中我就说过,MySQL是一个网络服务,学过网络的同学应该是懂的,我们想要获取到MySQL的服务,必须要连接mysql的服务器,我们在下载MySQL的时候有两个程序很重要,一个是mysqld,就是MySQL的服务器,一个是mysql,就是MySQL的客户端。

当我们启动MySQL服务器后,用netstat可以查看到我们主机中的mysql服务器:
在这里插入图片描述
我这里就不把我的mysqld的端口暴露出来了,这里也不建议你将你自己mysql服务器的端口暴露到网络中。

刚下载好的默认端口是3306,我这里修改了。如果你也想修改你的端口,只需要修改一下/etc/my.cnf配置文件就行:
在这里插入图片描述

这里改好之后记得用systemctl restart mysqld来重启你的服务器:
在这里插入图片描述

前面博客中一直是用mysql客户端连接数据库,并以命令行的方式来获取MySQL的服务,我前面也演示过远端连接其他主机的MySQL服务器。

那么这一篇就用语言来连接mysql服务器,本篇是用C语言演示的,不过学会这一个其他语言也就会了。

正式开始

创建一个等会专门演示语言连接的库和用户

我这里先来创建一个专门用来演示进行连接的用户:
在这里插入图片描述

关于用户管理的内容我前一篇已经讲过了,本篇不再细说,不懂的同学可以看看我前一篇。

再专门建一个库,给这个用户赋权:
在这里插入图片描述

用这个用户登录并查看权限:
在这里插入图片描述

正常。

连接mysql要用到的动静态库以及头文件

手动下载mysql官方提供的库(可以跳过不看)

我这里先介绍一下mysql官方所提供的库,其实我们在下载MySQL的时候已经将改装的库都装好了,不过这里只是介绍一下这些库都在什么地方。

MySQL的官方:MySQL

上面这个是中文官方页面,也有英文的:MySQL

进去之后点击下载(或者英文页面中的download):
在这里插入图片描述

进去之后往下翻,有个MySQL Community(GPL) Downloads:
在这里插入图片描述
点进去之后选择Download Archives:
在这里插入图片描述

本篇用C语言连接,所以用的是C API,点开之后是这样:
在这里插入图片描述

点中间的MySQL Connector/C,进去之后可以选择你对应mysql的版本:
在这里插入图片描述

不过可以看到下面提供的mysql版本是6.几的,你可以先查看一下你的mysql版本:
在这里插入图片描述

我的是5.几的,其实高版本是兼容我的版本的,所以这里很多的历史版本都没有,直接用新的就行:

在这里插入图片描述

再选择你os的版本,我等会是用Linux来演示,所以os版本选的是Linux的:
在这里插入图片描述

我这里下载好后并发送到我Linux上就是这样的:
在这里插入图片描述

用tar -xvzf进行解压:
在这里插入图片描述

然后会形成一个同名目录:
在这里插入图片描述

如果你嫌目录名太长了可以用mv命令来重命名:
在这里插入图片描述

里面文件有:
在这里插入图片描述

就两个比较重要,一个是include,表示包含头文件的目录:
在这里插入图片描述
其中mysql.h最重要,等会引的时候只需要引这个文件就行了。

一个lib,表示包含库文件的目录:
在这里插入图片描述
.a的为静态库,.so的为动态库。

这个手动下载库就到这,下面来说下载好MySQL就自带的库。

下载MySQL时自动安装的库

其实我们用yum install -y mysql-devel就可以把所有该下载的库都下好:
在这里插入图片描述

然后就会在/user/include下产生一个mysql目录:
在这里插入图片描述
这个目录下中保存的就是要用的头文件:
在这里插入图片描述

还有/lib64下也会生成一个mysql目录:
在这里插入图片描述
这个目录下保存的是需要用到的库文件:
在这里插入图片描述

这些东西搞好了之后就可以测试一下能不能连接上数据库了。

用C连接数据库

官方文档

mysql官方为我们提供的这些库,里面有很多的接口,不同的语言不一样,这些接口需要我们自己摸索着用,所以用语言连接数据库并发送相关的命令是需要我们再学习的,官网中也给我们提供了相关的文档介绍,这里我来简单介绍点接口。

进入官网之后先点击documentation:
在这里插入图片描述

然后往下稍微翻一点,有一个connector & APIS:
在这里插入图片描述

选择你想查看的语言对应的接口介绍,这里我用的是C,那我就选的是C API,后面带加号的表示还要选择不同版本:
在这里插入图片描述

我的mysql是5.7的,所以我选的是这个:
在这里插入图片描述

进去之后选择:

在这里插入图片描述

就可以查看所有函数的介绍了:
在这里插入图片描述

mysql官方提供了很多函数,我这里只截取了一部分。

虽然有很多函数,但是常用的还是那么些。

里面有个mysql_get_client_info函数,先用这个函数测试一下:
在这里插入图片描述

mysql官方里面是有这些函数的介绍的:
在这里插入图片描述

这个函数返回客户端对应的库版本。

对于编译链接的解释

上面的代码如果我直接进行简单的g++会报错的:
在这里插入图片描述

这里说mysql_get_client_info没有定义,并不是没有定义。

mysql为我们提供的库是第三方库,g++在进行编译的时候只会去找C++的库,其中会找一个路径,就是刚刚提到的/lib64下面,虽然刚刚看到了库的目录mysql放到了这个/lib64目录下,但是编译的时候是不会自动去/lib64/mysql中去找的。所以我们在编译的时候要手动加上连接的库以及库对应的目录:
在这里插入图片描述

关于这一点我在前面文件IO的博客讲过,不懂的同学可以看看:看动静态库的制作和使用

如果你编译出来后运行失败,可能是因为你动态库没有连接上,可以ldd一下:

在这里插入图片描述
如果你是动态库没连上,那箭头右边就是not found,我这里给两种方法:

  1. 在/etc/ld.so.conf.d/目录下将动态库的路径添加到系统配置文件中:
    在这里插入图片描述
    在这个路径下面加上一个.conf文件,文件中放的是库文件所在的路径就行:
    在这里插入图片描述

  2. 将库路径放在环境变量中,这里就不说了,在我那篇博客中详细说了

还有一个-I(这里不是l(L),而是I(i)),-I是指明头文件位置的,不过这里不用加,因为我们在引用头文件的时候是#include <mysql/mysql.h>的,刚刚也看到了/usr/include下是有mysql头文件目录的,搜索的时候会到include目录下面搜索,在搜索头文件的时候是指明了头文件所在目录的,也就是mysql目录,所以会直接进入该目录中找mysql目录下的mysql.h。如果这段话你听不懂,可以去看刚刚给的博客。

如果我是直接#include<mysql.h>,那么就要指明mysql.h所在的路径:
在这里插入图片描述
这里vscode自动报错了。

编译的时候加上-I选项:
在这里插入图片描述

编译:
在这里插入图片描述

成功。

所以小总结一下:

#include指明引用哪个头文件
-I(i)指明头文件位置
-l指明链接哪个库文件
-L指明库文件的位置

对库的操作

C/C++对库的操作,要在官网中查看有哪些函数接口。

库中会有很多的数据结构:
在这里插入图片描述这里有很多数据结构,不过最常用的就前几个。

函数接口介绍

在进行mysql的操作前一定要先连接上mysql服务器,而连接前还要创建一下mysql提供的一些基础数据结构。

mysql_init()

在这里插入图片描述
这个函数会帮我们创建一个MYSQL类型的对象,MYSQL就是一个结构体:

typedef struct st_mysql
{
  NET		net;			/* Communication parameters */
  unsigned char	*connector_fd;		/* ConnectorFd for SSL */
  char		*host,*user,*passwd,*unix_socket,*server_version,*host_info;
  char          *info, *db;
  struct charset_info_st *charset;
  MYSQL_FIELD	*fields;
  MEM_ROOT	field_alloc;
  my_ulonglong affected_rows;
  my_ulonglong insert_id;		/* id if insert on table with NEXTNR */
  my_ulonglong extra_info;		/* Not used */
  unsigned long thread_id;		/* Id for connection in server */
  unsigned long packet_length;
  unsigned int	port;
  unsigned long client_flag,server_capabilities;
  unsigned int	protocol_version;
  unsigned int	field_count;
  unsigned int 	server_status;
  unsigned int  server_language;
  unsigned int	warning_count;
  struct st_mysql_options options;
  enum mysql_status status;
  my_bool	free_me;		/* If free in mysql_close */
  my_bool	reconnect;		/* set to 1 if automatic reconnect */

  /* session-wide random string */
  char	        scramble[SCRAMBLE_LENGTH+1];
  my_bool unused1;
  void *unused2, *unused3, *unused4, *unused5;

  LIST  *stmts;                     /* list of all statements */
  const struct st_mysql_methods *methods;
  void *thd;
  /*
    Points to boolean flag in MYSQL_RES  or MYSQL_STMT. We set this flag 
    from mysql_stmt_close if close had to cancel result set of this object.
  */
  my_bool *unbuffered_fetch_owner;
  /* needed for embedded server - no net buffer to store the 'info' */
  char *info_buffer;
  void *extension;
} MYSQL;

这个MYSQL结构体有很多保存连接的字段,用来标识MySQL客户端相关的资源,就像C中的FILE、系统中的fd一样,都是用来唯一标识某些资源的,都可以被称为句柄。

mysql_init()调用成功会返回一个MYSQL对象,失败了会返回空。

传参的时候给一个nullptr就会直接创建新对象。

mysql_close()

这里的MYSQL就像C中的FILE一样,创建好了之后在最后还要关闭,关闭的函数就是mysql_close:
在这里插入图片描述

传入的就是mysql_init()创建出来的MYSQL对象指针,用来关闭连接,释放资源等。

来写写代码:
在这里插入图片描述

mysql_real_connect()

在这里插入图片描述

第一个参数就是刚刚创建出来的my。

host就是主机是哪台,本篇就测试本地中的mysql服务器,所以等会传localhost或者127.0.0.1就行。

user为连接服务器所用的用户,就是root或普通用户啥的。

paswd就是用户对应的密码。

db就是你在以user身份登录的时候要选择哪个数据库。等会就是用最开始创建的那个conn库来演示,不过这个库是空的,等会演示的时候再创建表。

port即mysql服务器的端口号(这里根据自己设定的端口来定,你可以用netstat -nltp来查看你的端口是啥)。

mysql在本地服务时采用域间套接的方式,类似于管道那样直接和mysql服务端通信,不走网络的那一套。这里不管是域间套接还是网络,后面两个参数设置为nullptr和0就行。

mysql_real_connect调用成功就返回传入的my,失败就返回空,有点像strcpy,成功了就返回原串。

写写代码:
在这里插入图片描述

这样就可以连接数据库了。

这里我加上sleep来测试一下:
在这里插入图片描述

运行,用processlist查看连接的用户信息:
在这里插入图片描述

10s后:
在这里插入图片描述

可以看到很成功。

有一个mysql_query:
在这里插入图片描述
这个函数会将我们传入的stmt_str作为一条sql语句去执行。

执行成功返回0,失败返回非零值。

来写一个创建表的语句:
在这里插入图片描述

这里是可以执行的:
在这里插入图片描述

desc:
在这里插入图片描述

mysql_query的时候传入的sql不需要加分号或者\G。

其实我们完全可以根据mysql_query来写一个我们自己的mysql客户端,不过这样做意义不大,但还是演示一下吧。

我这里先用mysql客户端插入几条记录:
在这里插入图片描述

然后再来改改代码:
在这里插入图片描述

更新:
在这里插入图片描述

删除:
在这里插入图片描述

插入:
在这里插入图片描述

前面我都是可以的在插入英文,如果我现在插入一个中文:
在这里插入图片描述
可以看到乱码了。

这里写的这个相当于是我自己的客户端,就和mysql客户端是一个性质的东西,我这里设置了mysql客户端的默认编码就是UTF8,和服务端一样:
在这里插入图片描述

但是我手写的这个简易客户端并没有设置,可以用mysql_get_character_set_info这个接口来查看当我们手写的这个客户端当前的编码:
在这里插入图片描述

运行:
在这里插入图片描述

或者直接用mysql_character_set_name:
在这里插入图片描述
在这里插入图片描述

可以看到是拉丁的编码,肯定和我们服务端UTF8的编码不匹配,所以我们要手动设置一下我们这个客户端的编码,用mysql_set_charcter_set函数:
在这里插入图片描述
运行:
在这里插入图片描述

这样将编码修改后,就可以插入中文了:
在这里插入图片描述
成功。

再来演示一下退出:

执行ctrl + d试试:
在这里插入图片描述

正常退出。

输入quit:
在这里插入图片描述
也是正常退出。

select 查找

前面的这三个增删改都能成功,但是查找还不行:
在这里插入图片描述
因为查找的结果需要我们自己提取。这里mysql_query只是执行以下sql而已,结果并没有返回,不过单次执行的结果会放到刚刚最开始创建出来的my对象中,我们需要将其中的返回结果手动提取出来。

用mysql_store_result函数:
在这里插入图片描述

可以看到这个函数参数就是刚刚的my,返回一个MYSQL_RES结构体的指针,来看看这个结构体中有什么:
在这里插入图片描述
很多字段。

从这里开始的代码,就和前面的创建的my对象没有什么关系了,后面只用这个MYSQL_RES的对象。

函数返回nullptr的时候有两种情况,一种是执行了非select语句的(比如update、delete、insert),这种不需要什么返回结果,只需要mysql_query的返回值就够了。一种是直接出错了,所以如果是返回空的时候要用其他函数来检查一下,具体我就不说了,看官方的文档介绍:
在这里插入图片描述

我们用select查找出来的结果中可能会有多条记录,而且多条记录中还会有很多不同列的字段,比如说用mysql客户端查找出来的表格:
在这里插入图片描述

把其中的格子去掉:
在这里插入图片描述

当我用我自己写的这个简易客户端执行select语句后,上面的这些数据就会全部被保存在MYSQL_RES这个结构体中。

不管库中的数据是以什么类型存放的,读取出来的时候都是当做字符串来看的。也就是说上面去掉框框的这些数据都是拿出来之后都是字符串,以字符串的形式保存在MYSQL_RES中的。

来搞一个全是元素为char*的二位数组:
在这里插入图片描述

对应的,将这个数组中的char*指针指向上面的所有数据:
在这里插入图片描述
中间的没有画,我怕一下子给画乱了,反正你懂我意思就行,就是一个char*指向一个字符串,一一对应。

所以其实可以把MYSQL_RES这个结构体当做二位数组来看。

我们可以通过mysql_num_rows来获取表的行数,用mysql_num_fields来获取表的列数:
在这里插入图片描述
但是如果不是select函数就会返回空,那么就不会有这些结果:
在这里插入图片描述
在这里插入图片描述

但是我们不仅要知道有多少列,重要的是获取出来其中的结果,所以要想办法遍历结果集,就像遍历上面的那个元素为char*的二维数组一样,把每一个元素所指向的字符串搞出来,mysql为了支持我们进行遍历,内部提供了MYSQL_ROW类型,可以获取到二维数组中的每一行,

其实这个类型就是char**:
在这里插入图片描述

其实也很好理解,假如说上面的二维数组为char* arr[x][y],第一个元素类型为char*,如果想要找到第一个元素的地址,那么就是char** ,这样就等于是一整行的地址就找到了。但是有很多行,底层就会类似这样的方式来找对应行:
在这里插入图片描述

我们通过mysql_fetch_row函数来获取每一行:
在这里插入图片描述

这个传入res,返回一个char**,而且调用这个函数会自动帮我们提取到下一行,就像范围for一样,自动迭代。

拿到一个MYSQL_ROW对象之后就可直接向数组一样用就行。

代码如下:
在这里插入图片描述

运行:
在这里插入图片描述

这样就能查到所有的数据了。

不过没有拿到每一列的列类型,要用另一种数据结构MYSQL_FIELD,也是个结构体:
在这里插入图片描述
里面字段很多,倒数第二个字段type,类型为enum_filed_types,这个类型要说一下:
在这里插入图片描述
其中就是所有的数据类型,当我们拿到了全是字符串的数据后,想要转回原来的数据类型,就可以根据type字段来提取。

调用mysql_fetch_fields:
在这里插入图片描述

返回值为MYSQL_FIELD类型的指针。用的时候也可以像刚刚的MYSQL_ROW的对象一样:
在这里插入图片描述

运行:
在这里插入图片描述

就和mysql客户端打印出来的结果是一样的。
MYSQL_FIELD中还有很多字段,比如说db表示当前选中了哪个数据库,table表示当前是哪一张表:
在这里插入图片描述
所以其实我们就可以控制一下打印格式,加上那些框框啥的,就和mysql客户端一样了,不过这样做没啥意义。

记得用完这个MYSQL_RES的结构体的对象后要释放掉对应的空间,这个结构体字段很多,占用空间还是不小的,用mysql_free_result就可以:
在这里插入图片描述

其实前面的res可以直接写到if的判断里面:
在这里插入图片描述

这样就可以根据res来分成一个模块,正好。

正常的使用方式

仅查找

我们平时用语言连接上数据库之后不是这样写一个死循环,然后再输入得到结果,要是这样的话不如直接用mysql客户端,一般都是针对于某一个功能只执行一条语句,比如说我只想要查询,那就只执行查询的sql,不加循环:
在这里插入图片描述

直接运行就是结果:
在这里插入图片描述

去除掉杂七杂八的打印:
在这里插入图片描述
这样就能打印成功。

仅插入

光是一个插入的,那就更简单了,都不需要MYSQL_RES了。

代码:
在这里插入图片描述

运行:
在这里插入图片描述

mysql图形化界面

mysql的图形化界面有不少,像Navicat、SQLyog都是比较好用的,不过二者要收费,你可以用破解版的,不过我这里就不说怎么搞了,感兴趣的老铁自行百度。

介绍一下MYSQL Workbench,这是个MYSQL官方提供的图形化界面,不过用起来没有上面的Navicat和SQLyog爽。

下载MYSQL Workbench,直接进MYSQL官网就行,跟着我步骤来:
在这里插入图片描述

往下翻:
在这里插入图片描述

进去之后:
在这里插入图片描述

进去后:
在这里插入图片描述

win下就一个版本,直接下就行。

不过下载时,你要有一个Oracle的账户,登陆之后选择一下你的身份信息啥的就能直接下载安装包了。

安装的时候一路next就行(你可以换一下安装路径)。

安装好之后长这样:
在这里插入图片描述

图形化界面本质上也是一个客户端,但我这里没有加上我Linux下的远端MYSQL服务端,我先专门新建一个用户:
在这里插入图片描述

然后再将conn库的权限给这个用户:
在这里插入图片描述
下面给我的workbench添加一个远端登录的用户:
在这里插入图片描述

然后就会有对应的连接:
在这里插入图片描述

登录:
在这里插入图片描述

左侧能选择表:
在这里插入图片描述

里面可以输入sql:
在这里插入图片描述

选中输入的sql然后按那个闪电就可以运行,还会有语法提示。

一些按键,比如说删除:
在这里插入图片描述
点一下就删了,很方便:
在这里插入图片描述

也可以手动插入:
在这里插入图片描述

点击红色框住的Apply就可以查看对应的sql:
在这里插入图片描述

再按一下Apply就执行了:
在这里插入图片描述

对应的远端也能看到:
在这里插入图片描述

就介绍到这,想要深入了解的同学可以自行学学。

Mysql连接池原理

像刚刚那样,连接一个客户端后发送一个sql就直接关闭连接,其实这样做挺浪费资源的,不如搞一个线程池,池中每个线程都连接数据库服务器,并维护一个任务队列,当有任务时就让线程拿任务,没有任务就让线程等待。

具体做法就不细说了,不懂的同学可以看看我线程池的那篇博客。

前面完整代码:

#include<iostream>
#include<mysql/mysql.h>
#include<unistd.h>

#include<string>

const std::string host = "localhost";
const std::string user = "connector";
const std::string passwd = "123456";
const std::string db = "conn";
const unsigned int port = xxxx; // 端口就不给了

int main()
{
    MYSQL* my = mysql_init(nullptr);

    if(mysql_real_connect(my, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, nullptr, 0) == nullptr)
    {
        std::cerr << "mysql_real_connect err" << std::endl;
        exit(1);
    }

    // std::string tmp = "select * from user";
    
    std::string tmp = "insert into user (name,age,telphone) values ('fangzhang', 20, 5235134113)";
    int ret = mysql_query(my, tmp.c_str());

    if(ret == 0)
    {
        std::cout << "success" << std::endl;
    }
    else
    {
        std::cout << "err" << std::endl; 
    }
    
    mysql_close(my);
        
    // if(MYSQL_RES* res = mysql_store_result(my))
    // {
    //     int rows = mysql_num_rows(res);
    //     int fields = mysql_num_fields(res);

    //     MYSQL_FIELD* fieldArr = mysql_fetch_fields(res);
    //     for(int j = 0; j < fields; ++j)
    //     {
    //         std::cout << fieldArr[j].name << '\t';
    //     }

    //     std::cout << std::endl;

    //     for(int i = 0; i < rows; ++i)
    //     {
    //         MYSQL_ROW rowArr = mysql_fetch_row(res);
    //         for(int j = 0; j < fields; ++j)
    //         {
    //             std::cout << rowArr[j] << '\t';
    //         }
    //         std::cout << std::endl;
    //     }

    //     mysql_free_result(res);
    // }

   

        // 建个表
    // const std::string tmp = "\
    // create table user(\
    //     id bigint primary key auto_increment,\
    //     name varchar(32) not null,\
    //     age int not null,\
    //     telphone varchar(32) unique\
    // );";


        // 编码
    // MY_CHARSET_INFO charset_info;
    // mysql_get_character_set_info(my, &charset_info);
    // printf("Character set name: %s\n", charset_info.name);
    // printf("Character set name: %s\n", mysql_character_set_name(my));
    // 
    // mysql_set_character_set(my, "utf8");
    // 
    // mysql_get_character_set_info(my, &charset_info);
    // printf("Character set name: %s\n", charset_info.name);
    // printf("Character set name: %s\n", mysql_character_set_name(my));

        // 循环的客户端
    // while (1)
    // {
    //     std::cout << "MYSQL > ";
    //     std::string tmp;
    //     if(!std::getline(std::cin, tmp) || tmp == "quit")
    //     {
    //         std::cout << "bye" << std::endl;
    //         break;    
    //     }

    //     int ret = mysql_query(my, tmp.c_str());
        
    //     if(MYSQL_RES* res = mysql_store_result(my))
    //     {
    //         int rows = mysql_num_rows(res);
    //         int fields = mysql_num_fields(res);
    //         std::cout << "row::" << rows << std::endl;
    //         std::cout << "fields::" << fields << std::endl;

    //         MYSQL_FIELD* fieldArr = mysql_fetch_fields(res);
    //         for(int j = 0; j < fields; ++j)
    //         {
    //             std::cout << fieldArr[j].name << '\t';
    //         }

    //         std::cout << std::endl;

    //         for(int i = 0; i < rows; ++i)
    //         {
    //             MYSQL_ROW rowArr = mysql_fetch_row(res);
    //             for(int j = 0; j < fields; ++j)
    //             {
    //                 std::cout << rowArr[j] << '\t';
    //             }
    //             std::cout << std::endl;
    //         }

    //         std::cout << "db:" << fieldArr[0].db << std::endl;
    //         std::cout << "table:" << fieldArr[0].table << std::endl;
    //         mysql_free_result(res);
    //     }

    //     if(ret == 0)
    //     {
    //         std::cout << "success" << std::endl;
    //     }
    //     else
    //     {
    //         std::cout << "err" << std::endl; 
    //     }
    // }
    

    return 0;
}

到此结束。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

先搞面包再谈爱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值