赞
踩
安装mysql-8.0.13-winx64.msi
cmd进入C:\Program Files\MySQL\MySQL Server 8.0\bin
mysqld --initialize --console
mysqld install mysql8.0
net start mysql8.0
mysql -u root -p
show databases;
ALTER USER “root”@“localhost” IDENTIFIED BY “123456”;
show databases;
±-------------------+
| Database |
±-------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
±-------------------+
4 rows in set (0.00 sec)
另外一台电脑上(虚拟机)安装mysql8.0,并输入下面命令连接我们本机的数据库:
mysql -u root -p123456 -h 192.168.0.201
这种连接方式不安全,因为密码会明文显示出来,日志中的密码也是明文。
提示连接超时(连接不上),是因为我们没有权限,回到本机。
(如果提示语法错误,请quit退出,重新登录后再试试)
使用4个数据库中的mysql,因为我们用户的数据库都会建在这个数据库里面:
use mysql
select * from user;
select user,host from user;
±-----------------±----------+
| user | host |
±-----------------±----------+
| mysql.infoschema | localhost |
| mysql.session | localhost |
| mysql.sys | localhost |
| root | localhost |
±-----------------±----------+
4 rows in set (0.00 sec)
我们可以看到这个root只能本地访问,我们试着把它的权限从localhost改成所有ip地址都可以访问:
update user set host=“%” where user=“root”;
%这个百分号表示所有的ip地址都可以访问(本地也能访问、远程也能访问),当然这是很不安全的,我们这里是内部测试,只是为了方便。
由于不是立即生效的,所以要么重启数据库,要么输入命令刷新权限:
flush privileges;
如果出现错误
ERROR 1062 (23000): Duplicate entry ‘%-root’ for key ‘PRIMARY’
不用理会,执行flush privileges;
执行quit命令退出数据库后重新连接,执行select user,host from user;
可以发现,已经修改成功了。
mysql -u root -p123456 -h 192.168.0.1
提示我们远程连接上了本机这台数据库了。
如果使用阿里云,记得还要把mysql的3306端口号和33060端口号都开放出来,这样在外网才可以连接。
Workbench的依赖环境是vs2015的运行环境Visual C++ Redistributable for Visual Studio 2015和.net4.5;
如果你安装好了vs2015、vs2017的话,依赖环境就已经安装好了。
由于Workbench 8.0的官方安装程序,在有的机器上会安装失败,所以最简洁的方法就是使用免安装版本就可以直接使用了。
创建数据库的时候,字符集Charset我们一般使用utf8,排列顺序Collation我们使用utf8_bin即可。
执行“USE 数据库名;”命令来切换到指定数据库;
执行“show tables;”或“SHOW FULL TABLES;”命令,会以表格形式列出mysql数据库中的所有表
测试vs项目发现的错误如下所示(上述截图代码是在win32平台,所用mysql也是win32版本)。
解决:
添加头文件#include <winsock.h>。
注意:这个winsock.h头文件要放在mysql.h的前面,毕竟是mysql.h里面要以弄SOCKET的。
解决:
链接器->输入->忽略特定库默认库:libcmt.lib
vs2012连接MySQL,出错“Unable to connect to any of the specified MySQL hosts”的话,是因为添加连接那里的设置有问题,服务器名字不能用已经启动的MySQL服务名(例如MySQL5.5),而要用服务器IP地址或者本地的测试IP(localhost或者127.0.0.1)方能成功!
这是由于dll是在当前目录下找(即vcxproj项目文件所在目录),然后是系统目录下;
为了将dll和exe统一放在外层的一个目录下,我们修改工作目录:
我们把它改成外层的bin目录下面,这样就可以编译通过了;
但是我们会发现这个bin目录下面没有exe生成,所以还得修改输出目录:
将上图中的输出目录修改为:
我们把release和Debug的输出目录都设置成…\bin,但是为了避免两个版本的输出exe文件名一样,可以把Debug版本的输出exe改个名字:
在上图输出文件的$(TargetName)后面添加_d即可:
修改输出目录后,如果调试运行后提示:无法找到调试信息,或者调试信息不匹配。无法查找或打开PDB文件。
解决:请打开工具、选项、调试,修改如下:
启用源服务器支持,并打开符号,勾选上Microsoft符号服务器选项:
点击本地 Windows调试器的时候,第一次会比较慢,因为它要下载几个模块的调试信息,之后清理解决方案,重新生成解决方案即可。
解决:将mysql安装目录下bin目录里面的libeay32.dll和ssleay32.dll拷贝到前面我们设置的工作目录中:
提示:不想看下面出错过程的,只需要修改下图目标文件名为$(ProjectName)_d即可,并把输出文件恢复原样为$(OutDir)$(TargetName)_d$(TargetExt)即可。
解决:打开项目属性,如下图所示复制输出目录的全部内容:
将上图中的$(OutDir)修改为下图所示的输出目录的内容:
按照网上这种方法修改后,我们发现仍然出错,系统找不到指定的文件,vs还是找testmysql.exe这个文件,但是我们命名在上图修改了输出文件名为$(TargetName)_d$(TargetExt)了,应该去找testmysql_d.exe文件,而这个文件我们F7编译后已经生成在bin目录里面了,但是vs就是不找已经生成好的testmysql_d.exe这个文件;
而且我们看到bin目录下,生成的exe是testmysql_d.exe,但是调试符号文件却还是testmysql这个项目名称。
(这个调试服务文件的问题可以通过链接器、调试,修改生成程序数据库文件为$(OutDir)$(TargetName)_d.pdb即可解决)
经过分析配置属性,我们发现上图所选部分的目标文件名,仍然是项目testmysql这个名称,所以我们修改它为$(TargetName)试试:
还是报错,在给这个名字后面添加个_d试试:
调试运行仍然失败,折腾了一圈,尝试了各种名称,最后才发现,如果为了不和Release输出的文件名称相同,想修改Debug输出的文件名的话,只修改一处地方即可:
还原链接器、常规中的输出文件为$(OutDir)$(TargetName)$(TargetExt),如下图所示:
如上图所示,只需要修改这一处地方就可以了!
参考链接:
https://blog.csdn.net/dengguawei0519/article/details/101315765
https://blog.csdn.net/daijingxin/article/details/108944780
https://blog.csdn.net/newworldis/article/details/118603965
https://www.cnblogs.com/steve-jokes/p/3883801.html
初次使用VS2010基于C++开发项目碰到的问题及解决方法
https://dandelioncloud.cn/article/details/1507754882374377473
我们要设置附加库目录:
还要记住附加依赖项中的文件名要和lib目录中的文件名要相同:
mysql的默认端口号是3306,而有些系统可能要mysql5.7的,所以这个时候就可以把mysql8.0的端口号给它改成3307,
// testmysql.cpp : 此代码为x64平台,mysql为64位版本。 #include <iostream> #include "mysql.h" #include <thread> int main() { MYSQL mysql; int ret = -1; mysql_library_init(0, 0, 0); // 初始化mysql上下文 if (!mysql_init(&mysql)) { std::cout << "mysql init failed " << mysql_error(&mysql) << std::endl; return ret; } // 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中 const char *host = "127.0.0.1"; //const char* host = "192.168.0.222"; const char *user = "root"; const char *passwd = "123456"; const char* db = "mysql"; // 数据库名称 // 设定超时3秒 const u_int timeout = 3; if (mysql_options(&mysql, MYSQL_OPT_CONNECT_TIMEOUT, &timeout)) { std::cout << "mysql_options CONNECT_TIMEOUT failed! " << mysql_error(&mysql) << std::endl; mysql_close(&mysql); return ret; } // 自动重连 const u_int reconect = 1; if (mysql_options(&mysql, MYSQL_OPT_RECONNECT, &reconect)) { std::cout << "mysql_options RECONNECT failed! " << mysql_error(&mysql) << std::endl; mysql_close(&mysql); return ret; } // 连接登录数据库 if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)) { std::cout << "mysql connect failed! " << mysql_error(&mysql) << std::endl; mysql_close(&mysql); return ret; } std::cout << "mysql connect " << host << " success!" << std::endl; for (int i = 0; i < 1000; i++) { if (mysql_ping(&mysql)) { std::cout << "mysql_ping " << host << " failed! " << mysql_error(&mysql) << std::endl; } else { std::cout << "mysql_ping " << host << " success!" << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1)); } } mysql_close(&mysql); mysql_library_end(); ret = 0; std::cout << "Hello World!" << std::endl; getchar(); }
错误 C2672 “std::this_thread::sleep_for”: 未找到匹配的重载函数
虽然在代码开头我们包含了头文件:
#include
但是这种错误是说我们调用该函数传的参数不对,没有匹配的重载函数:
应修改为:
std::this_thread::sleep_for(std::chrono::seconds(1));
参考:
https://blog.csdn.net/Dontla/article/details/125132538
https://blog.csdn.net/xiaojinger_123/article/details/124047634
// testmysql.cpp : 此代码为x64平台,mysql为64位版本。 // #include <iostream> #include "mysql.h" #include <thread> int main() { int ret = -1; // 初始化mysql上下文 MYSQL mysql; mysql_library_init(0, 0, 0); if (!mysql_init(&mysql)) { std::cout << "mysql init failed " << mysql_error(&mysql) << std::endl; return ret; } // 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中 const char *host = "127.0.0.1"; //const char* host = "192.168.0.222"; const char *user = "root"; const char *passwd = "123456"; const char* db = "mysql"; // 数据库名称 // 连接登录数据库 if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)) { std::cout << "mysql connect failed! " << mysql_error(&mysql) << std::endl; mysql_close(&mysql); return ret; } else { std::cout << "mysql connect " << host << " success!" << std::endl; } // 1、执行SQL语句 const char* sql = "select * from user"; //mysql_query(&mysql, sql); if (mysql_real_query(&mysql, sql, strlen(sql))) { std::cout << "mysql_real_query failed! " << sql << " " << mysql_error(&mysql) << std::endl; } else { std::cout << "mysql_real_query success! " << sql << std::endl; } mysql_close(&mysql); mysql_library_end(); ret = 0; std::cout << "Hello World!" << std::endl; getchar(); }
如果在用MYSQL API的mysql_real_query做查询的时候,如果出现错误“Commands out of sync;you can’t run this command now”,的话,也就是说我们只要执行了mysql_real_query,你就必须调用获取结果集,然后清理结果集,之后才能再次执行SQL命令;
你可以不遍历,你可以直接对结果集进行清理。
假如我们执行两遍SQL查询命令的话,就会出现错误“Commands out of sync;you can’t run this command now”:
因为你没有把上一次的命令缓存给它做一个清理;所以,执行SQL语句后,必须获取结果集并且清理结果集。
这两个接口的区别:
mysql_use_result,仅仅是告诉服务器我要取这个结果了,但是实际上并没有接收这个结果,所以执行速度非常快;那么它的结果集数据在什么时候取呢,在第3步遍历结果集的时候,才去取这个结果集。
mysql_store_result,不仅告诉服务器我要取这个结果,而且把这个结果集按照设定的包大小给取回来,所以执行速度没上一个命令快。
我们可以看到,mysql_use_result接口返回的结果中row_count的值为0,也就是说这时候是没有数据的,但是实际上是有数据的,这个接口就像之前说的,只是通知服务器一声我要取结果集了;
我们再来看看mysql_store_result接口:
也就是说这一步读取了数据。
mysql_use_result 不实际读取数据,在后面遍历的时候再去读取;
mysql_store_result 读取所有数据,注意缓存大小 默认64M 最大可以设置为MYSQL_OPT_MAX_ALLOWED_PACKET。
这就是这两种的读取方法。
// testmysql.cpp : 此代码为x64平台,mysql为64位版本。 // #include <iostream> #include "mysql.h" #include <thread> int main() { int ret = -1; // 初始化mysql上下文 MYSQL mysql; mysql_library_init(0, 0, 0); if (!mysql_init(&mysql)) { std::cout << "mysql init failed " << mysql_error(&mysql) << std::endl; return ret; } // 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中 const char* host = "127.0.0.1"; //const char* host = "192.168.0.222"; const char* user = "root"; const char* passwd = "123456"; const char* db = "mysql"; // 数据库名称 // 连接登录数据库 if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)) { std::cout << "mysql connect failed! " << mysql_error(&mysql) << std::endl; mysql_close(&mysql); return ret; } else { std::cout << "mysql connect " << host << " success!" << std::endl; } // 1、执行SQL语句 const char* sql = "select * from user"; //mysql_query(&mysql, sql); if (mysql_real_query(&mysql, sql, strlen(sql))) { std::cout << "mysql_real_query failed! " << sql << " " << mysql_error(&mysql) << std::endl; } else { std::cout << "mysql_real_query success! " << sql << std::endl; } // 2、获取结果集 //MYSQL_RES* result = mysql_use_result(&mysql); MYSQL_RES* result = mysql_store_result(&mysql); if (!result) { std::cout << "mysql_store_result failed! " << sql << " " << mysql_error(&mysql) << std::endl; } mysql_close(&mysql); mysql_library_end(); ret = 0; std::cout << "Hello World!" << std::endl; getchar(); }
// testmysql.cpp : 此代码为为win32平台,mysql为32位版本。 #include <iostream> #include <winsock.h> #include "mysql.h" #include <thread> using std::cout; using std::endl; int main() { int ret = -1; // 初始化mysql上下文 MYSQL mysql; mysql_library_init(0, 0, 0); if (!mysql_init(&mysql)) { cout << "mysql init failed " << mysql_error(&mysql) << endl; return ret; } // 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中 const char* host = "127.0.0.1"; //const char* host = "192.168.0.222"; const char* user = "root"; const char* passwd = "123456"; const char* db = "mysql"; // 数据库名称 // 连接登录数据库 if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)) { cout << "mysql connect failed! " << mysql_error(&mysql) << endl; mysql_close(&mysql); return ret; } else { cout << "mysql connect " << host << " success!" << endl; } // 1、执行SQL语句 const char* sql = "select * from user"; //mysql_query(&mysql, sql); if (mysql_real_query(&mysql, sql, strlen(sql))) { cout << "mysql_real_query failed! " << sql << " " << mysql_error(&mysql) << endl; } else { cout << "mysql_real_query success! " << sql << endl; } // 2、获取结果集 //MYSQL_RES* result = mysql_use_result(&mysql); MYSQL_RES* result = mysql_store_result(&mysql); if (!result) { cout << "mysql_store_result failed! " << sql << " " << mysql_error(&mysql) << endl; } // 3、遍历结果集 MYSQL_ROW row; int i = 0; while(row = mysql_fetch_row(result)) { unsigned long* len = mysql_fetch_lengths(result); cout << "len[" << i << "] = " << len[i] << " ------ " << "[" << row[i] << "," << row[i + 1] << "]" << endl; i++; } // 清理结果集 mysql_free_result(result); mysql_close(&mysql); mysql_library_end(); ret = 0; cout << "Hello World!" << endl; getchar(); }
// testmysql.cpp : 此代码为为win32平台,mysql为32位版本。 #include <iostream> #include <winsock.h> #include "mysql.h" #include <thread> using std::cout; using std::endl; int main() { int ret = -1; // 初始化mysql上下文 MYSQL mysql; mysql_library_init(0, 0, 0); if (!mysql_init(&mysql)) { cout << "mysql init failed " << mysql_error(&mysql) << endl; return ret; } // 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中 const char* host = "127.0.0.1"; //const char* host = "192.168.0.222"; const char* user = "root"; const char* passwd = "123456"; const char* db = "mysql"; // 数据库名称 // 连接登录数据库 if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)) { cout << "mysql connect failed! " << mysql_error(&mysql) << endl; mysql_close(&mysql); return ret; } else { cout << "mysql connect " << host << " success!" << endl; } // 1、执行SQL语句 const char* sql = "select * from user"; //mysql_query(&mysql, sql); if (mysql_real_query(&mysql, sql, strlen(sql))) { cout << "mysql_real_query failed! " << sql << " " << mysql_error(&mysql) << endl; } else { cout << "mysql_real_query success! " << sql << endl; } // 2、获取结果集 //MYSQL_RES* result = mysql_use_result(&mysql); MYSQL_RES* result = mysql_store_result(&mysql); if (!result) { cout << "mysql_store_result failed! " << sql << " " << mysql_error(&mysql) << endl; } // 获取表字段 MYSQL_FIELD* field = nullptr; while(field = mysql_fetch_field(result)) { cout << "key: " << field->name << endl; } // 获取表字段数量 int fnum = mysql_num_fields(result); cout << "\nfields's num = " << fnum << endl; cout << "\n===================================================\n" << endl; // 3、遍历结果集 MYSQL_ROW row; while(row = mysql_fetch_row(result)) { /*unsigned long* len = mysql_fetch_lengths(result); cout << "len[" << i << "] = " << len[i] << " ------ " << "[" << row[i] << "," << row[i + 1] << "]" << endl; i++;*/ for(int i = 0; i < fnum; i++) { cout << mysql_fetch_field_direct(result, i)->name << ":"; if(row[i]) cout << row[i]; else cout << "NULL"; cout << ", "; } cout << "\n===================================================\n" << endl; } // 清理结果集 mysql_free_result(result); mysql_close(&mysql); mysql_library_end(); ret = 0; cout << "Hello World!" << endl; getchar(); }
上图的 CREATE TABLE IF NOT EXISTS ‘t_image’ 的意思是说,如果这个表不存在我们才创建,这个比较方便的,这样的话我们的代码就不用手动去判断这个表是否存在了,我们每次都这样执行一遍:CREATE TABLE IF NOT EXISTS ‘t_image’ ,表不存在我们就创建,然后开始插入数据。
注意,上图中的表名、字段名两边的不是单引号,是ESC键下边的那个键,也是数字1前面那个符号`;我们不管什么样的情况都给字段名两边加上这个符号,这样就能保证不会跟MYSQL系统中的关键字出现冲突,导致程序出现问题。
// testmysql.cpp : 此代码为为win32平台,mysql为32位版本。 #include <iostream> #include <winsock.h> #include "mysql.h" #include <thread> #include <string> using std::cout; using std::endl; using std::string; int main() { int ret = -1; // 初始化mysql上下文 MYSQL mysql; mysql_library_init(0, 0, 0); if (!mysql_init(&mysql)) { cout << "mysql init failed " << mysql_error(&mysql) << endl; return ret; } // 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中 const char* host = "127.0.0.1"; //const char* host = "192.168.0.222"; const char* user = "root"; const char* passwd = "123456"; const char* db = "mysql"; // 数据库名称 // 连接登录数据库 if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)) { cout << "mysql connect failed! " << mysql_error(&mysql) << endl; mysql_close(&mysql); return ret; } else { cout << "mysql connect " << host << " success!" << endl; } string sql = ""; // 创建数据库 sql = "CREATE DATABASE `zpdatabase` DEFAULT CHARACTER SET utf8 COLLATE utf8_bin"; //sql = "CREATE DATABASE `zpdatabase11` charset utf8"; //mysql_query(&mysql, sql.c_str()); if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))) { cout << "mysql_real_query failed! " << sql << " " << mysql_error(&mysql) << endl; } else { cout << "mysql_real_query success! " << sql << endl; } // 1 创建表 sql = "CREATE TABLE IF NOT EXISTS `zpdatabase`.`t_image` ( \ `id` int AUTO_INCREMENT, \ `name` varchar(1024), \ `path` varchar(2046), \ `size` int, \ PRIMARY KEY(`id`) \ )"; //mysql_query(&mysql, sql.c_str()); if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))) { cout << "mysql_real_query failed! " << sql << " " << mysql_error(&mysql) << endl; } else { cout << "mysql_real_query success! " << sql << endl; } // 2 插入数据 // 3 修改数据 // 4 删除数据 mysql_close(&mysql); mysql_library_end(); ret = 0; cout << "MYSQL 5.5.40" << endl; getchar(); }
创建表、插入数据、删除数据的时候不需要清理结果集,因为它没有结果集返回,我们只需要判断它成功还是失败。
注意:代码中的数据库名称 const char* db = “mysql”; 记得改为所创建的数据库名称zpdatabase,这样创建表、插入数据的sql语句中就不需要在表名前添加数据库名称了。
mysql> desc t_image;
±------±--------------±-----±----±--------±---------------+
| Field | Type | Null | Key | Default | Extra |
±------±--------------±-----±----±--------±---------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(1024) | YES | | NULL | |
| path | varchar(2046) | YES | | NULL | |
| size | int(11) | YES | | NULL | |
±------±--------------±-----±----±--------±---------------+
4 rows in set (0.01 sec)
// 2 插入数据 sql = "insert into `t_image`(`name`, `path`, size) values('test1.jpg','d:/img/test1.jpg', 10240)"; //mysql_query(&mysql, sql.c_str()); if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))) { cout << "insert failed! " << mysql_error(&mysql) << endl; } else { cout << "insert success! " << endl; // mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据 my_ulonglong count = mysql_affected_rows(&mysql); cout << "mysql_affected_rows " << count << endl; }
select查找的话你还要获取结果集,而insert插入之操作我们不需要获取结果集,我们只需要通过mysql_affected_rows(&mysql);知道插入语句影响的行数就可以了。
mysql> select * from t_image;
±—±----------±-----------------±------+
| id | name | path | size |
±—±----------±-----------------±------+
| 1 | test1.jpg | d:img est1.jpg | 10240 |
| 2 | test1.jpg | d:img est1.jpg | 10240 |
| 3 | test1.jpg | d:img est1.jpg | 10240 |
| 4 | test1.jpg | d:/img/test1.jpg | 10240 |
| 5 | test1.jpg | d:/img/test1.jpg | 10240 |
±—±----------±-----------------±------+
5 rows in set (0.00 sec)
单独一个反斜杠在C++字符串中会被当成转义,如果我们多加几个反斜杠的话,在sql语句里仍然被当成了转义,所以我们统一用斜杠来作为路径分隔符吧,这样就不会造成转义。
为了改变所插入的名字,我们使用sstream库里面的拼接字符串功能,因为C++本身它不能支持把整型数字给加进字符串里面,所以我们用这个字符串流来处理。
为了使用stringstream ,记得在开头添加头文件,以及 using std::stringstream;
// sstream版本的插入1000条数据 for(int i = 0; i < 1000; i++) { //sql = "insert into `t_image`(`id`, `name`, `path`, size) values('test1.jpg','d:/img/test1.jpg', 10240)"; stringstream ss; ss << "insert into `t_image` (`name`, `path`, size) values('test"; ss << i << ".jpg','d:/img/test"; ss << i << ".jpg', 10240)"; sql = ss.str(); if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))) { cout << "insert into failed! " << mysql_error(&mysql) << endl; } else { cout << "insert into success! " << endl; // mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据 my_ulonglong count = mysql_affected_rows(&mysql); // mysql_insert_id(&mysql); 这个接口可以把id号取出来 cout << "mysql_affected_rows " << count << " id = " << mysql_insert_id(&mysql) << endl; } }
// sprintf_s版本的插入数据 char csql[1024] = { 0 }; for(int i = 0; i < 50; i++) { //sql = "insert into `t_image`(`id`, `name`, `path`, size) values('test1.jpg','d:/img/test1.jpg', 10240)"; sprintf_s(csql, "insert into `t_image` (`name`, `path`, `size`) values('my_image_%d.jpg', 'd:/img/my_image%d.jpg', '5000%d')", i, i, i); if (mysql_real_query(&mysql, csql, strlen(csql))) { cout << "sprintf_s insert into failed! " << mysql_error(&mysql) << endl; } else { cout << "sprintf_s insert into success! " << endl; // mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据 my_ulonglong count = mysql_affected_rows(&mysql); // mysql_insert_id(&mysql); 这个接口可以把id号取出来 cout << "insert mysql_affected_rows " << count << " id = " << mysql_insert_id(&mysql) << endl; } }
mysql> select count(*) from zpdatabase.t_image;
±---------+
| count(*) |
±---------+
| 319 |
±---------+
1 row in set (0.00 sec)
上面这条sql语句可以用来统计表中有多少行数据。
// 3 修改数据 // update t_image set `name`="image.png", size=2000 where id=1 sql = "update `t_image` set `name` = 'image11.png', `path` = 'd:/img/image11.png', `size` = 50000 where `id` = 2" ; //mysql_query(&mysql, sql.c_str()); if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))) { cout << "update failed! " << mysql_error(&mysql) << endl; } else { cout << "update success! " << endl; // mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据 my_ulonglong count = mysql_affected_rows(&mysql); cout << "update mysql_affected_rows " << count << endl; }
注意:内容要添加单引号,不加引号会被sql当做字段。
我们经过测试发现可以给size这个整型值传入字符串,这样sql语句就可以统一值的形式,方便用户传一个map进来,map里面是map<string, string>。
为了使用map和maike_pair,记得在开头添加头文件
// 通过传入的map<string, string>来实现sql语句 // update t_image set `name`="image.png", size=2000 where id=1 map<string, string> kv; kv.insert(make_pair("name", "image_update001.png")); kv.insert(make_pair("path", "d:/img/image_update001.png")); kv.insert(make_pair("size", "5000")); string where = "where id = 3"; // 遍历map // map<string, string>::iterator string tmp; for(auto ptr = kv.begin(); ptr != kv.end(); ptr++) { tmp.clear(); tmp += "`"; tmp += ptr->first; tmp += "`='"; tmp += ptr->second; tmp += "', "; //为了解决上面tmp最后结尾处多的一个逗号,比如说我们在结尾处加一个固定的不变的值: tmp += " id=id " ; sql = "update `t_image` set "; sql += tmp; sql += where; if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))) { cout << "map<string, string> update failed! " << mysql_error(&mysql) << endl; } else { cout << "map<string, string> update success! " << endl; // mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据 my_ulonglong count = mysql_affected_rows(&mysql); cout << "update mysql_affected_rows " << count << endl; } }
由于这里拼接的字符串里面没有整型数据,所以我们就用一个临时的string来拼接字符串就行。
这样我们就可以根据map自动生成update的sql语句了。
mysql> select * from t_image where id < 10;
±—±--------------------±---------------------------±------+
| id | name | path | size |
±—±--------------------±---------------------------±------+
| 1 | test1.jpg | d:img est1.jpg | 10240 |
| 2 | image11.png | d:/img/image11.png | 50000 |
| 3 | image_update001.png | d:/img/image_update001.png | 5000 |
| 4 | test1.jpg | d:/img/test1.jpg | 10240 |
| 5 | test1.jpg | d:/img/test1.jpg | 10240 |
| 6 | test1.jpg | d:/img/test1.jpg | 10240 |
| 7 | test1.jpg | d:/img/test1.jpg | 10240 |
| 8 | test1.jpg | d:/img/test1.jpg | 10240 |
| 9 | test1.jpg | d:/img/test1.jpg | 10240 |
±—±--------------------±---------------------------±------+
9 rows in set (0.00 sec)
delete from t_image + 条件,例如delete from t_image where id >0,会把整个表中的数据都删掉,这种用的比较多;
它并不是真正把数据给删掉,如果你把它放在事务里面去做的话,事务是可以回滚的,这条语句只是给它做个标识,说它被删掉了,删除一条数据的话比较慢;你的自增索引并没有清除,你再插入数据的话这个索引会接续增加;
如果delete from t_image要真正的清理掉空间的话,要用OPTIMIZE TABLE t_image去优化下这张表,才会把t_image对应的空间真正清理掉,这种工作可以定期来做(因为优化的时候会锁住这张表,所以一般在夜里没什么操作这张表的时候来做);
truncate t_image,会把整个表都删掉,并且使自增索引重新从0开始;它会真正的清理掉空间;
drop table t_image,把整个表都删掉,包括数据以及表结构,这种不怎么用。
下图是我们要操作的数据库的存储位置:
我们待会可以从这个存储位置这里看到数据删除之后的变化,可以看到数据删除的开销和文件的大小变化。
// testmysql.cpp : 此代码为为win32平台,mysql为32位版本。 #include <iostream> #include <winsock.h> #include "mysql.h" #include <thread> #include <string> #include <sstream> #include <stdio.h> #include <map> using std::cout; using std::endl; using std::string; using std::stringstream; using std::map; using std::make_pair; int main() { int ret = -1; // 初始化mysql上下文 MYSQL mysql; mysql_library_init(0, 0, 0); if (!mysql_init(&mysql)) { cout << "mysql init failed " << mysql_error(&mysql) << endl; return ret; } // 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中 const char* host = "127.0.0.1"; //const char* host = "192.168.0.222"; const char* user = "root"; const char* passwd = "123456"; const char* db = "zpdatabase"; // 数据库名称 // 连接登录数据库 if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)) { cout << "mysql connect failed! " << mysql_error(&mysql) << endl; mysql_close(&mysql); return ret; } else { cout << "mysql connect " << host << " success!" << endl; } string sql = ""; 创建数据库 //sql = "CREATE DATABASE `zpdatabase` DEFAULT CHARACTER SET utf8 COLLATE utf8_bin"; sql = "CREATE DATABASE `zpdatabase11` charset utf8"; mysql_query(&mysql, sql.c_str()); //if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))) //{ // cout << "mysql_real_query failed! " << mysql_error(&mysql) << endl; //} //else //{ // cout << "mysql_real_query success! " << endl; //} // 1 创建表 sql = "CREATE TABLE IF NOT EXISTS `t_image`( \ `id` int auto_increment, \ `name` varchar(1024), \ `path` varchar(2046), \ `size` int, \ primary KEY(`id`) \ )"; //mysql_query(&mysql, sql.c_str()); if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))) { cout << "CREATE TABLE failed! " << mysql_error(&mysql) << endl; } else { cout << "CREATE TABLE success! " << endl; } // 清空数据,并恢复自增id从1开始 sql = "truncate t_image"; if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))) { cout << "delete failed! " << mysql_error(&mysql) << endl; } else { cout << "delete success! " << endl; // mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据 my_ulonglong count = mysql_affected_rows(&mysql); cout << "delete mysql_affected_rows " << count << endl; } // 2 插入数据 // 插入100条数据 for(int i = 0; i < 100; i++) { //sql = "insert into `t_image`(`id`, `name`, `path`, size) values('test1.jpg','d:/img/test1.jpg', 10240)"; stringstream ss; ss << "insert into `t_image` (`name`, `path`, size) values('test"; ss << i << ".jpg','d:/img/test"; ss << i << ".jpg', 10240)"; sql = ss.str(); if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))) { cout << "insert into failed! " << mysql_error(&mysql) << endl; } else { cout << "insert into success! " << endl; // mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据 my_ulonglong count = mysql_affected_rows(&mysql); // mysql_insert_id(&mysql); 这个接口可以把id号取出来 cout << "insert mysql_affected_rows " << count << " id = " << mysql_insert_id(&mysql) << endl; } } // 3 修改数据 // update t_image set `name`="image.png", size=2000 where id=1 sql = "update `t_image` set `name` = 'image11.png', `path` = 'd:/img/image11.png', `size` = 50000 where `id` = 2" ; //mysql_query(&mysql, sql.c_str()); if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))) { cout << "update failed! " << mysql_error(&mysql) << endl; } else { cout << "update success! " << endl; // mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据 my_ulonglong count = mysql_affected_rows(&mysql); cout << "update mysql_affected_rows " << count << endl; } // 通过传入的map<string, string>来实现sql语句 // update t_image set `name`="image.png", size=2000 where id=1 //sql = "insert into `t_image`(`id`, `name`, `path`, size) values('test1.jpg','d:/img/test1.jpg', 10240)"; map<string, string> kv; kv.insert(make_pair("name", "image_update001.png")); kv.insert(make_pair("path", "d:/img/image_update001.png")); kv.insert(make_pair("size", "5000")); string where = "where id = 3"; // 遍历map // map<string, string>::iterator string tmp; for(auto ptr = kv.begin(); ptr != kv.end(); ptr++) { tmp.clear(); tmp += "`"; tmp += ptr->first; tmp += "`='"; tmp += ptr->second; tmp += "', "; //为了解决上面tmp最后结尾处多的一个逗号,比如说我们在结尾处加一个固定的不变的值: tmp += " id=id " ; sql = "update `t_image` set "; sql += tmp; sql += where; if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))) { cout << "map<string, string> update failed! " << mysql_error(&mysql) << endl; } else { cout << "map<string, string> update success! " << endl; // mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据 my_ulonglong count = mysql_affected_rows(&mysql); cout << "update mysql_affected_rows " << count << endl; } } // 4 删除数据 // delete from t_image where id=1 // truncate t_image // drop table t_image // sql = "truncate `t_image`" ; // delete 不会实际删除空间,只是做了标识 sql = "delete from t_image where id = 1"; //sql = "truncate t_image"; if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))) { cout << "delete failed! " << mysql_error(&mysql) << endl; } else { cout << "delete success! " << endl; // mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据 my_ulonglong count = mysql_affected_rows(&mysql); cout << "delete mysql_affected_rows " << count << endl; } // 要想真正删除空间,还有优化一下 // 实际清理了空间 sql = "OPTIMIZE TABLE t_image"; if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))) { cout << "delete failed! " << mysql_error(&mysql) << endl; } else { cout << "delete success! " << endl; // mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据 my_ulonglong count = mysql_affected_rows(&mysql); cout << "delete mysql_affected_rows " << count << endl; } mysql_close(&mysql); mysql_library_end(); ret = 0; cout << "MYSQL 5.5.40" << endl; getchar(); }
我们是一次执行发过去的,这种语句里面有不同的操作,比方说有插入、有删除、也有update,也有select查找等,有多种语句的时候那你怎么返回结果呢,要通过mysql_next_result来遍历结果;
这个mysql_next_result,并不是返回结果集,它只是确认有没有下一个结果,如果有下一个结果它就移到这个结果的位置,然后你再通过store_result或者query_result来获取这个结果集,有的情况下是由结果集的,有的情况下是没有结果集的,有的是有结果但是没有结果集(比方说insert),所以说你在处理多条sql语句的时候,你要做判断。
我们接下来要在一条sql语句中完成这几个动作:建表、清表、插入100条数据、显示(即查询)。
我们先不用CLIENT_MULTI_STATEMENTS这个参数,用以前的语句执行看看:
可以看到执行第2条sql语句的时候就出错了。
添加CLIENT_MULTI_STATEMENTS参数的话,让它支持一次执行多条sql语句:
// testmysql.cpp : 此代码为为win32平台,mysql为32位版本。 #include <iostream> #include <winsock.h> #include "mysql.h" #include <thread> #include <string> #include <sstream> #include <stdio.h> #include <map> using std::cout; using std::endl; using std::string; using std::stringstream; using std::map; using std::make_pair; int main() { int ret = -1; // 初始化mysql上下文 MYSQL mysql; mysql_library_init(0, 0, 0); if (!mysql_init(&mysql)) { cout << "mysql init failed " << mysql_error(&mysql) << endl; return ret; } // 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中 const char* host = "127.0.0.1"; //const char* host = "192.168.0.222"; const char* user = "root"; const char* passwd = "123456"; const char* db = "zpdatabase"; // 数据库名称 // 支持多条sql语句 要用到CLIENT_MULTI_STATEMENTS 这个参数 // 连接登录数据库 if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, CLIENT_MULTI_STATEMENTS)) { cout << "mysql connect failed! " << mysql_error(&mysql) << endl; mysql_close(&mysql); return ret; } else { cout << "mysql connect " << host << " success!" << endl; } string sql = ""; // 1 创建表 sql = "CREATE TABLE IF NOT EXISTS `t_image`( \ `id` int auto_increment, \ `name` varchar(1024), \ `path` varchar(2046), \ `size` int, \ primary KEY(`id`) \ );"; // 清空数据,并恢复自增id从1开始 sql += "truncate t_image;"; // 2 插入数据 // 插入100条数据 for(int i = 0; i < 100; i++) { //sql = "insert into `t_image`(`id`, `name`, `path`, size) values('test1.jpg','d:/img/test1.jpg', 10240)"; stringstream ss; ss << "insert into `t_image` (`name`, `path`, size) values('test"; ss << i << ".jpg','d:/img/test"; ss << i << ".jpg', 10240);"; sql += ss.str(); } // 3 修改数据 // update t_image set `name`="image.png", size=2000 where id=1 sql += "update `t_image` set `name` = 'image11.png', `path` = 'd:/img/image11.png', \ `size` = 50000 where `id` = 2;"; // 通过传入的map<string, string>来实现sql语句 // update t_image set `name`="image.png", size=2000 where id=1 //sql = "insert into `t_image`(`id`, `name`, `path`, size) values('test1.jpg','d:/img/test1.jpg', 10240)"; map<string, string> kv; kv.insert(make_pair("name", "image_update001.png")); kv.insert(make_pair("path", "d:/img/image_update001.png")); kv.insert(make_pair("size", "5000")); string where = "where id = 3;"; // 遍历map // map<string, string>::iterator string tmp; for(auto ptr = kv.begin(); ptr != kv.end(); ptr++) { tmp.clear(); tmp += "`"; tmp += ptr->first; tmp += "`='"; tmp += ptr->second; tmp += "', "; //为了解决上面tmp最后结尾处多的一个逗号,比如说我们在结尾处加一个固定的不变的值: tmp += " id=id " ; sql += "update `t_image` set "; sql += tmp; sql += where; } // 4 删除数据 // delete from t_image where id=1 // truncate t_image // drop table t_image // sql = "truncate `t_image`" ; // delete 不会实际删除空间,只是做了标识 sql += "delete from t_image where id = 1;"; // 在进行查找 sql += "select * from t_image;"; // 执行sql语句会立刻返回,但是语句并没有全部执行好,需要获取结果,不然会出现问题 // 它是把sql语句整个发送给mysql server,mysql server一条条执行,一条条返回结果 // 一次执行完返回一个统一的结果,这种可以通过存储过程或者用事务来做 // 以前那种单条语句并不会出现这种问题 if (mysql_real_query(&mysql, sql.c_str(), strlen(sql.c_str()))) { cout << "excute mutilsql failed! " << mysql_error(&mysql) << endl; } else { cout << "excute multisql success! " << endl; // 有多个返回结果,所以要用mysql_next_result(&mysql); // 取下一条结果,而当前已经有一条返回结果了,所以我们用do...while()来做 do{ MYSQL_RES* result = mysql_store_result(&mysql); if(result) // 如果有结果集,有可能是SELECT语句执行的结果 { // 因为有多种sql语句的执行结果,所以这里我们只把SELECT语句执行的结果的总行号打印出来 // 如果有结果集,我们就可以把它释放 mysql_free_result(result); } else // 如果没有结果集,那有可能是INSERT、UPDATE、DELETE、CREATE、TRUNCATE、DROP { // 也有可能mysql_store_result执行失败,所以要先判断下 // 如果有字段、但没有结果集,那就是SELECT出错 if(mysql_field_count(&mysql) > 0) { cout << "Not retrieve result!" << mysql_error(&mysql) << endl; } else // INSERT、UPDATE、DELETE、CREATE、TRUNCATE、DROP { // 打印一下影响的行数 // 等待服务器的处理结果 cout << mysql_affected_rows(&mysql) << " rows affected!" << endl; } } }while(mysql_next_result(&mysql) == 0); // 0表示有结果 // mysql_affected_rows(&mysql); 这个接口的意思是说,上一句的sql语句影响了多少行的数据 int count = mysql_affected_rows(&mysql); cout << "excute multisql mysql_affected_rows " << count << endl; } mysql_close(&mysql); mysql_library_end(); ret = 0; cout << "MYSQL 5.5.40" << endl; getchar(); }
如果mysql_affected_rows(&mysql); 的返回值是18446744073709551615,而该值是2^64-1 (18446744073709551615),这个数字超过了 my_ulonglong 的最大范围;
我们看 MySQL 源码,mysql_affected_rows() 返回类型是my_ulonglong,源码中其实是这么定义的:
typedef unsigned long long my_ulonglong;
也就是说,在 C 代码中,这个数字最大就是2^64-1 (18446744073709551615),由于接口mysql_affected_rows的返回值是无符号类型的,所以-1就变成18446744073709551615了。
所以,将count的类型改为int即可:
事务可以进行回滚;
autocommit自动提交设成0,就是不自动提交,我们手动提交。
MYSQL有两种存储引擎:MyISAM和InnoDB,涉及到事务的话,我们一般用InnoDB。
我们接下来做一下事务功能的演示,我们先插3条数据,然后回滚,再插入1000条数据;并比较不用事务和用事务插入数据的性能。
// test_transaction.cpp : 此代码为为win32平台,mysql为32位版本。 #include <iostream> #include <winsock.h> #include "mysql.h" #include <thread> #include <string> #include <sstream> #include <stdio.h> #include <map> using std::cout; using std::endl; using std::string; using std::stringstream; using std::map; using std::make_pair; int main() { int ret = -1; // 初始化mysql上下文 MYSQL mysql; mysql_library_init(0, 0, 0); if (!mysql_init(&mysql)) { cout << "mysql init failed " << mysql_error(&mysql) << endl; return ret; } // 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中 const char* host = "127.0.0.1"; //const char* host = "192.168.0.222"; const char* user = "root"; const char* passwd = "123456"; const char* db = "zpdatabase"; // 数据库名称 // 连接登录数据库 if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)) { cout << "mysql connect failed! " << mysql_error(&mysql) << endl; mysql_close(&mysql); return ret; } else { cout << "mysql connect " << host << " success!" << endl; } string sql = ""; // 1 创建表 sql = "CREATE TABLE IF NOT EXISTS `t_video`( \ `id` int auto_increment, \ `name` varchar(1024), \ `path` varchar(2046), \ `size` int, \ primary KEY(`id`) \ ) ENGINE = InnoDB"; // 设置支持事务回滚的InnoDB引擎 int result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "CREATE TABLE failed" << mysql_error(&mysql) << endl; } // 清理表中的数据 sql = "truncate `t_video`"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "truncate failed" << mysql_error(&mysql) << endl; } // 事务 // 1 开始事务 // START TRANSACTION sql = "START TRANSACTION"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "START TRANSACTION failed" << mysql_error(&mysql) << endl; } // 2 设置为手动提交事务 // set autocommit = 0 sql = "set autocommit = 0"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "set autocommit failed" << mysql_error(&mysql) << endl; } // 3 sql语句 // 插入3条数据,然后回滚 for(int i = 0; i < 3; i++) { sql = "insert into t_video (`name`) values('值:"; sql += std::to_string(i); sql += "')"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "insert failed " << mysql_error(&mysql) << endl; } } // 4 回滚 ROLLBACK sql = "ROLLBACK"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "ROLLBACK failed " << mysql_error(&mysql) << endl; } // 如果把表改成MyISAM引擎的话,就不支持回滚了 //sql = "ALTER TABLE `t_video` ENGINE = MyISAM"; for(int i = 0; i < 100; i++) { sql = "insert into t_video (`name`) values('值:"; sql += std::to_string(i); sql += "')"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "insert failed " << mysql_error(&mysql) << endl; } } // 5 提交 COMMIT sql = "COMMIT"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "COMMIT failed " << mysql_error(&mysql) << endl; } // 6 恢复自动提交 // set autocommit = 1 sql = "set autocommit = 1"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "set autocommit failed " << mysql_error(&mysql) << endl; } // 为了避免以后插入1万条数据后,通过*来查询开销太大,所以这里就用count看一下有多少行数据 sql = "select count(*) from t_video"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "mysql_query failed" << mysql_error(&mysql) << endl; } // 把查询的结果集存下来 MYSQL_RES* res = mysql_store_result(&mysql); if(res) { // 这里只取第一行数据 MYSQL_ROW row = mysql_fetch_row(res); if(row) { // 查看第一个和第二个字段 cout << "t_video count(*) = " << row[0] << endl; } } mysql_close(&mysql); mysql_library_end(); ret = 0; cout << "MYSQL 5.5.40" << endl; getchar(); }
接下来我们用单条sql语句、多条sql语句,以及用事务这个功能来测插入十万条数据的性能差异。
为了测时,需要用到C++自带的时间库chrono来计时:
#include <chrono>
using namespace std::chrono;
// test_transaction.cpp : 此代码为win32平台,mysql为32位版本。 #include <iostream> #include <winsock.h> #include "mysql.h" #include <thread> #include <string> #include <sstream> #include <stdio.h> #include <map> #include <chrono> using std::cout; using std::endl; using std::string; using std::stringstream; using std::map; using std::make_pair; using namespace std::chrono; int main() { int ret = -1; // 初始化mysql上下文 MYSQL mysql; mysql_library_init(0, 0, 0); if (!mysql_init(&mysql)) { cout << "mysql init failed " << mysql_error(&mysql) << endl; return ret; } // 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中 const char* host = "127.0.0.1"; //const char* host = "192.168.0.222"; const char* user = "root"; const char* passwd = "123456"; const char* db = "zpdatabase"; // 数据库名称 // 连接登录数据库 if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)) { cout << "mysql connect failed! " << mysql_error(&mysql) << endl; mysql_close(&mysql); return ret; } else { cout << "mysql connect " << host << " success!" << endl; } string sql = ""; // 1 创建表 sql = "CREATE TABLE IF NOT EXISTS `t_video`( \ `id` int auto_increment, \ `name` varchar(1024), \ `path` varchar(2046), \ `size` int, \ primary KEY(`id`) \ ) ENGINE = InnoDB"; // 设置支持事务回滚的InnoDB引擎 int result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "CREATE TABLE failed" << mysql_error(&mysql) << endl; } // 清理表中的数据 sql = "truncate `t_video`"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "truncate failed" << mysql_error(&mysql) << endl; } // 事务 // 1 开始事务 // START TRANSACTION sql = "START TRANSACTION"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "START TRANSACTION failed" << mysql_error(&mysql) << endl; } // 2 设置为手动提交事务 // set autocommit = 0 sql = "set autocommit = 0"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "set autocommit failed" << mysql_error(&mysql) << endl; } // 3 sql语句 // 插入3条数据,然后回滚 for(int i = 0; i < 3; i++) { sql = "insert into t_video (`name`) values('值:"; sql += std::to_string(i); sql += "')"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "insert failed " << mysql_error(&mysql) << endl; } } // 4 回滚 ROLLBACK sql = "ROLLBACK"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "ROLLBACK failed " << mysql_error(&mysql) << endl; } // 如果把表改成MyISAM引擎的话,就不支持回滚了 //sql = "ALTER TABLE `t_video` ENGINE = MyISAM"; // 5 提交 COMMIT sql = "COMMIT"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "COMMIT failed " << mysql_error(&mysql) << endl; } // 6 恢复自动提交 // set autocommit = 1 sql = "set autocommit = 1"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "set autocommit failed " << mysql_error(&mysql) << endl; } // 为了避免以后插入1万条数据后,通过*来查询开销太大,所以这里就用count看一下有多少行数据 sql = "select count(*) from t_video"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "mysql_query failed" << mysql_error(&mysql) << endl; } // 把查询的结果集存下来 MYSQL_RES* res = mysql_store_result(&mysql); if(res) { // 这里只取第一行数据 MYSQL_ROW row = mysql_fetch_row(res); if(row) { // 查看第一个和第二个字段 cout << "t_video count(*) = " << row[0] << endl; } } auto start = system_clock::now(); // 单条sql语句插入1千条数据 for(int i = 0; i < 1000; i++) { sql = "insert into t_video (`name`, `path`) values('single', '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789')"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "insert failed " << mysql_error(&mysql) << endl; } } auto end = system_clock::now(); // 转换为毫秒 1000毫秒是1秒 auto dur = duration_cast<milliseconds>(end - start); //duration<double> diff = end - start; // 单位为s,可精确到小数点后七位 cout << "单条sql语句插入1千条数据 " << dur.count() / 1000. << "秒" << endl; // milliseconds timeInterval = duration_cast<milliseconds>(endTime - beginTime); // 单位为ms // 多条sql语句插入1千条数据 // 事务插入1千条数据 mysql_close(&mysql); mysql_library_end(); ret = 0; cout << "MYSQL 5.5.40" << endl; getchar(); }
// 连接登录数据库
if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, CLIENT_MULTI_STATEMENTS))
{
cout << "mysql connect failed! " << mysql_error(&mysql) << endl;
mysql_close(&mysql);
return ret;
}
else
{
cout << "mysql connect " << host << " success!" << endl;
}
// 多条sql语句插入1千条数据 { sql = ""; auto start = system_clock::now(); for(int i = 0; i < 100; i++) { sql += "insert into t_video (`name`, `path`) values('single', '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789');"; } result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "insert failed " << mysql_error(&mysql) << endl; } auto end = system_clock::now(); // 转换为毫秒 1000毫秒是1秒 auto dur = duration_cast<milliseconds>(end - start); cout << "多条sql语句插入1千条数据 " << dur.count() / 1000. << "秒" << endl; }
我们插入了1千条数据,然后我们要获取结果集,不然这个测出来的时间是没有意义的,你这时候测是非常快的,是因为我们没有获取它的结果集,说白了只是把sql指令发给server,server并没有执行完;
因为我们知道sql语句全部都是insert语句,所以在获取结果集这边我们直接处理:
// 多条sql语句插入1千条数据 { sql = ""; auto start = system_clock::now(); for(int i = 0; i < 100; i++) { sql += "insert into t_video (`name`, `path`) values('single', '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789');"; } result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "insert failed " << mysql_error(&mysql) << endl; } do { // 在这里可以直接获取到它的结果 cout << mysql_affected_rows(&mysql) << std::flush; } while(mysql_next_result(&mysql) == 0); auto end = system_clock::now(); // 转换为毫秒 1000毫秒是1秒 auto dur = duration_cast<milliseconds>(end - start); cout << "多条sql语句插入1千条数据 " << dur.count() / 1000. << "秒" << endl; }
经测试我们发现,单条sql插入1千条数据和多条sql语句一次执行插入1千条数据,两者的执行速度相差不大。
// test_transaction.cpp : 此代码为win32平台,mysql为32位版本。 #include <iostream> #include <winsock.h> #include "mysql.h" #include <thread> #include <string> #include <sstream> #include <stdio.h> #include <map> #include <chrono> using std::cout; using std::endl; using std::string; using std::stringstream; using std::map; using std::make_pair; using namespace std::chrono; int main() { int ret = -1; // 初始化mysql上下文 MYSQL mysql; mysql_library_init(0, 0, 0); if (!mysql_init(&mysql)) { cout << "mysql init failed " << mysql_error(&mysql) << endl; return ret; } // 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中 const char* host = "127.0.0.1"; //const char* host = "192.168.0.222"; const char* user = "root"; const char* passwd = "123456"; const char* db = "zpdatabase"; // 数据库名称 // 连接登录数据库 if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, CLIENT_MULTI_STATEMENTS)) { cout << "mysql connect failed! " << mysql_error(&mysql) << endl; mysql_close(&mysql); return ret; } else { cout << "mysql connect " << host << " success!" << endl; } string sql = ""; // 1 创建表 sql = "CREATE TABLE IF NOT EXISTS `t_video`( \ `id` int auto_increment, \ `name` varchar(1024), \ `path` varchar(2046), \ `size` int, \ primary KEY(`id`) \ ) ENGINE = InnoDB"; // 设置支持事务回滚的InnoDB引擎 int result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "CREATE TABLE failed" << mysql_error(&mysql) << endl; } // 清理表中的数据 sql = "truncate `t_video`"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "truncate failed" << mysql_error(&mysql) << endl; } // 三种方式测试插入1000条数据的性能差异 auto start = system_clock::now(); // 单条sql语句插入1千条数据 for(int i = 0; i < 1000; i++) { sql = "insert into t_video (`name`, `path`) values('single', '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789')"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "insert failed " << mysql_error(&mysql) << endl; } } auto end = system_clock::now(); // 转换为毫秒 1000毫秒是1秒 auto dur = duration_cast<milliseconds>(end - start); //duration<double> diff = end - start; // 单位为s,可精确到小数点后七位 cout << "\n1 单条sql语句插入1千条数据 " << dur.count() / 1000. << "秒" << endl; // milliseconds timeInterval = duration_cast<milliseconds>(endTime - beginTime); // 单位为ms // 多条sql语句插入1千条数据 { sql = ""; auto start = system_clock::now(); for(int i = 0; i < 1000; i++) { sql += "insert into t_video (`name`, `path`) values('single', '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789');"; } result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "insert failed " << mysql_error(&mysql) << endl; } do { // 在这里可以直接获取到它的结果 cout << mysql_affected_rows(&mysql) << std::flush; } while(mysql_next_result(&mysql) == 0); auto end = system_clock::now(); // 转换为毫秒 1000毫秒是1秒 auto dur = duration_cast<milliseconds>(end - start); cout << "\n2 多条sql语句插入1千条数据 " << dur.count() / 1000. << "秒" << endl; } // 事务插入1千条数据 { // 1 开始事务 // START TRANSACTION sql = "START TRANSACTION"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "START TRANSACTION failed" << mysql_error(&mysql) << endl; } // 2 设置为手动提交事务 // set autocommit = 0 sql = "set autocommit = 0"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "set autocommit failed" << mysql_error(&mysql) << endl; } auto start = system_clock::now(); // 单条sql语句插入1千条数据 for(int i = 0; i < 1000; i++) { sql = "insert into t_video (`name`, `path`) values('single', '0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789')"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "insert failed " << mysql_error(&mysql) << endl; } else cout << mysql_affected_rows(&mysql) << std::flush; } auto end = system_clock::now(); // 转换为毫秒 1000毫秒是1秒 auto dur = duration_cast<milliseconds>(end - start); //duration<double> diff = end - start; // 单位为s,可精确到小数点后七位 cout << "\n3 事务插入1千条数据 " << dur.count() / 1000. << "秒" << endl; sql = "COMMIT"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "set autocommit failed" << mysql_error(&mysql) << endl; } // 在结尾处还要把提交设置回来 sql = "set autocommit = 1"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "set autocommit failed" << mysql_error(&mysql) << endl; } } mysql_close(&mysql); mysql_library_end(); ret = 0; cout << "MYSQL 5.5.40" << endl; getchar(); }
因为事务减少了反复编译sql语句来确认的环节,因为这个的开销是相当大的,事务在插入这块的效率是非常高的,所以在后面我们做导入这种批量工作的时候就可以用事务来做。
我们回到代码当中实现这样的功能:
准备好一个图片,我们把这个图片插入到数据库当中,插入完之后我们再把这个图片读出来,再保存到本地另外一个文件,看一下这个图片是否跟原来的一致。
我们要做的是,往MYSQL数据库当中插入图片文件这类二进制的内容,并且我们读取并把它存下来。
我们之前讲过,二进制的内容我们不好放在sql语句当中,虽然sql语句里面也支持二进制,支持\0这类,但是二进制文件里面肯定会包含sql里面所有的语句,这肯定是不安全的,在这个角度上我们就是通过MYSQL提供的一整套预处理、预编译sql语句的接口,所有的操作都基于stmt这类接口来进行操作;
如上图中的insert into 语句中values内容中的问号相当于占位符,你在后面的sql语句中再把这些问号所代表的字段内容给填进去,这样做的最大好处就是注入攻击是不可行了,因为是会认为这是一个字段,而不再会认为它是一条语句,所以说用这种方式基本上杜绝了注入攻击。
MYSQL_STMT这种类型相当于存的是一条sql语句,对于mysql来说就相当于我们给它做了一条语法,让它去编译,在这条sql语句中除了我们字符串内容之外,还要加入我们二进制的内容。
首先我们初始化好、准备好MYSQL_STMT这样一个用来存储sql语句的空间,因为这里面涉及到内存空间的申请了,在C语言的API它的空间是不会自动释放的,所以我们在结束的时候,初始化的stmt_init对应的就是stmt_close,我们通过stmt_close接口把它关掉并且释放stmt指针对应的空间。
下面通过stmt_prepare接口把sql语句加进来,这时候就相当于我们的sql语句预处理好了,但是这时候VALUES里面只是问号啊,它对应的值并没有加进去;
我们再通过stmt_bind_param接口把参数给绑进去;MYSQL_BIND是一个结构体数组,因为我们绑定的话不止一个字段,我们要绑定多个字段,这个结构体里面有多个成员,我们目前关注的是MYSQL_TYPE_*,就是选择这个字段的类型;
执行就通过stmt_execute接口,执行完我们就可以获取结果;
select查找的话你还要获取结果集,而insert插入的话我们不需要获取结果集,我们只需要知道插入语句影响的行数就可以了。
// test_transaction.cpp : 此代码为win32平台,mysql为32位版本。 #include <iostream> #include <winsock.h> #include "mysql.h" #include <thread> #include <string> #include <sstream> #include <stdio.h> #include <map> #include <chrono> #include <fstream> using std::cout; using std::endl; using std::string; using std::cerr; using std::fstream; using std::ios; int main() { int ret = -1; // 初始化mysql上下文 MYSQL mysql; mysql_library_init(0, 0, 0); if (!mysql_init(&mysql)) { cout << "mysql init failed " << mysql_error(&mysql) << endl; return ret; } // 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中 const char* host = "127.0.0.1"; //const char* host = "192.168.0.222"; const char* user = "root"; const char* passwd = "123456"; const char* db = "zpdatabase"; // 数据库名称 // 连接登录数据库 if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)) { cout << "mysql connect failed! " << mysql_error(&mysql) << endl; mysql_close(&mysql); return ret; } else { cout << "mysql connect " << host << " success!" << endl; } string sql = ""; // 1 创建好存放二进制数据的表 t_data sql = "CREATE TABLE IF NOT EXISTS `t_data`( \ `id` int auto_increment, \ `name` varchar(1024), \ `data` blob, \ `size` int, \ primary KEY(`id`) \ )"; int result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "CREATE TABLE failed" << mysql_error(&mysql) << endl; } // 2 清空表 truncate t_data sql = "truncate t_data"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cerr << "truncate failed" << mysql_error(&mysql) << endl; } // 3 初始化stmt mysql_stmt_init MYSQL_STMT* stmt = mysql_stmt_init(&mysql); if(!stmt) { cerr << "mysql_stmt_init failed!" << mysql_stmt_error(stmt) << endl; } // 4 预处理sql语句 sql = "insert into `t_data` (`name`, `data`, `size`) values(?, ?, ?)"; if(mysql_stmt_prepare(stmt, sql.c_str(), sql.size())) { cerr << "mysql_stmt_prepare failed!" << mysql_stmt_error(stmt) << endl; } // 5 打开并读取文件 string filename = "20200816104741.jpg"; // 读取文件有多种方式,我们写代码原则都是要跨平台的,我们要用C++的方式来做这个文件的读取,我们要用到fstream这个库 // 得到文件大小和文件二进制地址(这个二进制地址涉及到空间的动态的分配和释放) // 读取二进制文件 fstream in(filename, ios::in | ios::binary); if(!in.is_open()) { cerr << "file " << filename << "open failed!" << endl; } // 为了得到文件大小,可以移动文件指针到文件结尾处,通过位置你就知道文件大小了 // 也就是说从结尾的位置往前移,移多少呢,移动到开始位置, in.seekg(0, ios::end); int filesize = in.tellg(); // 因为一会还要读文件,所以需要把文件指针移到文件开头 // 回到开头 in.seekg(0, ios::beg); // 分配空间大小 char* data = new char[filesize]; // 因为你不能保证read读文件能一次读完,所以保险起见我们通过循环来读 int readedSize = 0; // 已经读了多少 while(!in.eof()) { // 因为我们一直在往data里面读,所以指针得移位,读了多少就移多少;每次读的字节数量也应减少已读取的字节数量 in.read(data + readedSize, filesize - readedSize); if((filesize - readedSize) <= 0) { cout << "读取文件完毕" << endl; break; } // 如果第一次没读完,我们得知道上一次读了多少 // 读取了多少字节 if(in.gcount() <= 0) { cerr << "file " << filename << " in.read failed!" << endl; break; // 假如出错我们就直接跳出while循环 } readedSize += in.gcount(); } in.close(); //delete data; // 6 绑定字段 要绑定3个字段:name data size // 第一个字段是我们的文件名 MYSQL_BIND bind[3] = {0}; bind[0].buffer_type = MYSQL_TYPE_STRING; // name 文件名 这是我们的第一个字段 bind[0].buffer = (char*)filename.c_str(); bind[0].buffer_length = filename.size(); // 第二个字段是我们的二进制文件 bind[1].buffer_type = MYSQL_TYPE_BLOB; // data 文件的二进制内容 bind[1].buffer = data; // 二进制文件 bind[1].buffer_length = filesize; // 有时候我们不需要从数据库里把二进制文件读出来,我们只需要显示一下文件列表:文件名和文件大小,所以需要第三个字段来存文件大小 // 文件大小 整型的话它的长度是固定的4字节大小,所以不需要写buffer_length bind[2].buffer_type = MYSQL_TYPE_LONG; bind[2].buffer = &filesize; // 绑定 该函数返回0是成功 if(mysql_stmt_bind_param(stmt, bind) != 0) { cerr << "mysql_stmt_bind_param failed!" << mysql_stmt_error(stmt) << endl; } // 7 执行stmt sql if(mysql_stmt_execute(stmt) != 0) { cerr << "mysql_stmt_execute failed!" << mysql_stmt_error(stmt) << endl; } // 原则:空间是尽晚申请,尽早释放,尽量减少对资源的占用时间 delete data; mysql_stmt_close(stmt); // 释放stmt 清理掉预处理的sql语句 mysql_close(&mysql); mysql_library_end(); ret = 0; cout << "MYSQL 5.5.40" << endl; getchar(); }
如上图所示,经测试,二进制数据图片插入成功。
下面我们来读取文件,并把它写到本地。
// test_transaction.cpp : 此代码为win32平台,mysql为32位版本。 #include <iostream> #include <winsock.h> #include "mysql.h" #include <thread> #include <string> #include <sstream> #include <stdio.h> #include <map> #include <chrono> #include <fstream> using std::cout; using std::endl; using std::string; using std::cerr; using std::fstream; using std::ios; int main() { int ret = -1; // 初始化mysql上下文 MYSQL mysql; mysql_library_init(0, 0, 0); if (!mysql_init(&mysql)) { cout << "mysql init failed " << mysql_error(&mysql) << endl; return ret; } // 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中 const char* host = "127.0.0.1"; //const char* host = "192.168.0.222"; const char* user = "root"; const char* passwd = "123456"; const char* db = "zpdatabase"; // 数据库名称 // 连接登录数据库 if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, 0)) { cout << "mysql connect failed! " << mysql_error(&mysql) << endl; mysql_close(&mysql); return ret; } else { cout << "mysql connect " << host << " success!" << endl; } string sql = ""; // 1 创建好存放二进制数据的表 t_data sql = "CREATE TABLE IF NOT EXISTS `t_data`( \ `id` int auto_increment, \ `name` varchar(1024), \ `data` blob, \ `size` int, \ primary KEY(`id`) \ )"; int result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "CREATE TABLE failed" << mysql_error(&mysql) << endl; } // 2 清空表 truncate t_data sql = "truncate t_data"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cerr << "truncate failed" << mysql_error(&mysql) << endl; } // 3 初始化stmt mysql_stmt_init MYSQL_STMT* stmt = mysql_stmt_init(&mysql); if(!stmt) { cerr << "mysql_stmt_init failed!" << mysql_stmt_error(stmt) << endl; } // 4 预处理sql语句 sql = "insert into `t_data` (`name`, `data`, `size`) values(?, ?, ?)"; if(mysql_stmt_prepare(stmt, sql.c_str(), sql.size())) { cerr << "mysql_stmt_prepare failed!" << mysql_stmt_error(stmt) << endl; } // 5 打开并读取文件 string filename = "20200816104741.jpg"; // 读取文件有多种方式,我们写代码原则都是要跨平台的,我们要用C++的方式来做这个文件的读取,我们要用到fstream这个库 // 得到文件大小和文件二进制地址(这个二进制地址涉及到空间的动态的分配和释放) // 读取二进制文件 fstream in(filename, ios::in | ios::binary); if(!in.is_open()) { cerr << "file " << filename << "open failed!" << endl; } // 为了得到文件大小,可以移动文件指针到文件结尾处,通过位置你就知道文件大小了 // 也就是说从结尾的位置往前移,移多少呢,移动到开始位置, in.seekg(0, ios::end); int filesize = in.tellg(); // 因为一会还要读文件,所以需要把文件指针移到文件开头 // 回到开头 in.seekg(0, ios::beg); // 分配空间大小 char* data = new char[filesize]; // 因为你不能保证read读文件能一次读完,所以保险起见我们通过循环来读 int readedSize = 0; // 已经读了多少 while(!in.eof()) { // 因为我们一直在往data里面读,所以指针得移位,读了多少就移多少;每次读的字节数量也应减少已读取的字节数量 in.read(data + readedSize, filesize - readedSize); if((filesize - readedSize) <= 0) { cout << "读取文件完毕" << endl; break; } // 如果第一次没读完,我们得知道上一次读了多少 // 读取了多少字节 if(in.gcount() <= 0) { cerr << "file " << filename << " in.read failed!" << endl; break; // 假如出错我们就直接跳出while循环 } readedSize += in.gcount(); } in.close(); //delete data; // 6 绑定字段 要绑定3个字段:name data size // 第一个字段是我们的文件名 MYSQL_BIND bind[3] = {0}; bind[0].buffer_type = MYSQL_TYPE_STRING; // name 文件名 这是我们的第一个字段 bind[0].buffer = (char*)filename.c_str(); bind[0].buffer_length = filename.size(); // 第二个字段是我们的二进制文件 bind[1].buffer_type = MYSQL_TYPE_BLOB; // data 文件的二进制内容 bind[1].buffer = data; // 二进制文件 bind[1].buffer_length = filesize; // 有时候我们不需要从数据库里把二进制文件读出来,我们只需要显示一下文件列表:文件名和文件大小,所以需要第三个字段来存文件大小 // 文件大小 整型的话它的长度是固定的4字节大小,所以不需要写buffer_length bind[2].buffer_type = MYSQL_TYPE_LONG; bind[2].buffer = &filesize; // 绑定 该函数返回0是成功 if(mysql_stmt_bind_param(stmt, bind) != 0) { cerr << "mysql_stmt_bind_param failed!" << mysql_stmt_error(stmt) << endl; } // 7 执行stmt sql if(mysql_stmt_execute(stmt) != 0) { cerr << "mysql_stmt_execute failed!" << mysql_stmt_error(stmt) << endl; } // 原则:空间是尽晚申请,尽早释放,尽量减少对资源的占用时间 delete data; mysql_stmt_close(stmt); // 释放stmt 清理掉预处理的sql语句 // 8 查询数据库中的二进制数据,并保存为本地文件 // 题外话:通过sql语句中的limit可以进行分页 例如:sql = "select * fromt t_data limit 1"; sql = "select * from t_data"; // 先简单来做,尽量不要关联太多东西 result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cerr << "mysql_query failed!" << mysql_error(&mysql) << endl; } // 取到sql命令执行的结果集 MYSQL_RES* res = mysql_store_result(&mysql); if(!res) { cerr << "mysql_store_result failed!" << mysql_error(&mysql) << endl; } // 取一行数据 实际上数据库中也就一行数据 MYSQL_ROW是一个数组 MYSQL_ROW row = mysql_fetch_row(res); if(!row) { cerr << "mysql_fetch_row failed!" << mysql_error(&mysql) << endl; } // 我们可以通过第三个字段取得文件的大小 cout << "id:" << row[0] << " name:" << row[1] << " size:" << row[3] << endl; // 如果我们在不知道size的情况下,也可以通过一个方式来获取数据大小 // 获取每列数据的大小,该函数返回的是一个数组 unsigned long *length = mysql_fetch_lengths(res); // 获取上一个函数返回的数组的元素个数,即获取有几列数据 //mysql_field_count(&mysql); // 这个是取所操作表中的字段数 int columnNum = mysql_num_fields(res); // 这个是根据结果集来做的 for(int i = 0; i < columnNum; i++) { cout << "[" << length[i] << "]" << endl; } // 写入本地文件 filename = "out_"; filename += row[1]; fstream out(filename, ios::out | ios::binary); if(!out.is_open()) { cerr << "open file " << filename << " failed!" << endl; } out.write(row[2], length[2]); out.close(); mysql_close(&mysql); mysql_library_end(); ret = 0; cout << "MYSQL 5.5.40" << endl; getchar(); return ret; }
我们写入了数据库,并且从数据库里面把它读了出来,并且显示出来,这样的话,我们插入二进制,并且读取二进制的整个操作就完成了。
存储过程其实就是一系列sql语句的整合,是数据库内部全部已经编译好的,它的执行效率是最高的;存储过程使得业务和界面真正的完全隔离。
对于存储过程我们要解决3个问题:
string sql = "";
// 1 创建存储过程
// (把输入参数打印出来;改变输入参数的值;把改变的值再打印一遍)
sql = "CREATE PROCEDURE `p_test` (IN p_in INT, OUT p_out INT, INOUT p_inout INT) \
BEGIN \
SELECT p_in, p_out, p_inout; \
SET p_in = 100, p_out = 200, p_inout = 300; \
SELECT p_in, p_out, p_inout; \
END";
// test_transaction.cpp : 此代码为win32平台,mysql为32位版本。 #include <iostream> #include <winsock.h> #include "mysql.h" #include <thread> #include <string> #include <sstream> #include <stdio.h> #include <map> #include <chrono> #include <fstream> using std::cout; using std::endl; using std::string; using std::cerr; using std::fstream; using std::ios; int main() { int ret = -1; // 初始化mysql上下文 MYSQL mysql; mysql_library_init(0, 0, 0); if (!mysql_init(&mysql)) { cout << "mysql init failed " << mysql_error(&mysql) << endl; return ret; } // 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中 const char* host = "127.0.0.1"; //const char* host = "192.168.0.222"; const char* user = "root"; const char* passwd = "123456"; const char* db = "zpdatabase"; // 数据库名称 // 连接登录数据库 // 支持多条sql语句:最后一个参数为CLIENT_MULTI_STATEMENTS if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, CLIENT_MULTI_STATEMENTS)) { cout << "mysql connect failed! " << mysql_error(&mysql) << endl; mysql_close(&mysql); return ret; } else { cout << "mysql connect " << host << " success!" << endl; } string sql = ""; // 1 创建存储过程 // (把输入参数打印出来;改变输入参数的值;把改变的值再打印一遍) sql = "CREATE PROCEDURE `p_test` (IN p_in INT, OUT p_out INT, INOUT p_inout INT) \ BEGIN \ SELECT p_in, p_out, p_inout; \ SET p_in = 100, p_out = 200, p_inout = 300; \ SELECT p_in, p_out, p_inout; \ END"; int result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "CREATE PROCEDURE failed! " << mysql_error(&mysql) << endl; } // sql = "call p_test(1, 2, 3);" //这样调用是不行的,传入的实参必须是变量 // 2 定义变量并赋值 sql = "SET @A = 1; SET @B = 2; SET @C = 3"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "SET PARAMETER failed! " << mysql_error(&mysql) << endl; } // 3 调用存储过程 call sql = "CALL p_test(@A, @B, @C)"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "CALL PROCEDURE failed! " << mysql_error(&mysql) << endl; } // 4 获取存储过程的结果 sql = "SELECT @A, @B, @C"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "mysql_query failed! " << mysql_error(&mysql) << endl; } mysql_close(&mysql); mysql_library_end(); ret = 0; cout << "MYSQL 5.5.40" << endl; getchar(); return ret; }
我们先执行一次上面的程序看看有没有问题:
看上图我们运行后出现右图的错误,CREATE PROCEDURE failed!这是因为我们运行了两遍该程序,p_test这个存储过程已经存在了,所以报创建存储过程失败,这个错误不用管它;
CALL PROCEDURE failed! 这是因为我们执行第2步的sql语句,没有去获取它的结果集,虽然SET语句是对变量进行赋值,它是没有结果集的,所以说我们可以不获取它的结果。
但是由于第2步执行了多条sql语句,所以在第3步调用存储过程的时候,它显示你的结果集没有释放,所以存储过程没法调用。
对于第2步中的多条sql语句的多条结果集我们要给它遍历一下:
// 2 定义变量并赋值 sql = "SET @A = 1; SET @B = 2; SET @C = 3"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "SET PARAMETER failed! " << mysql_error(&mysql) << endl; } do { cout << "SET affected" << mysql_affected_rows(&mysql) << endl; }while (mysql_next_result(&mysql) == 0); // 0表示还有结果 -1没有结果 >1错误 // 3 调用存储过程 call sql = "CALL p_test(@A, @B, @C)"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "CALL PROCEDURE failed! " << mysql_error(&mysql) << endl; }
我们运行一下程序看看:
可以看到它其实影响的行数是0条,但是你必须要把结果集给它遍历一下,因为你有多条sql语句,主要是调用mysql_next_result这个API;
如果你不是执行多条的话,那么遍历这步就不用做了;
因为它没有结果集(SET语句没有结果集返回),所以也不用释放结果集。
目前通过上图我们知道,调用存储过程这步我们没有错误了,最后一个错误是第4步获取存储过程的结果的时候,这里的话我们要把存储过程的结果给它返回,返回结果的话同样也是要遍历结果集。
这个p_test存储过程中的sql语句,有的有结果集(例如SELECT),有的没有结果集(例如SET语句),只有有结果集的我们才处理,没有结果集我们暂时就不处理了(比方说它里面做了SET,这个SET其实我们先不用管它)。
// test_transaction.cpp : 此代码为win32平台,mysql为32位版本。 #include <iostream> #include <winsock.h> #include "mysql.h" #include <thread> #include <string> #include <sstream> #include <stdio.h> #include <map> #include <chrono> #include <fstream> using std::cout; using std::endl; using std::string; using std::cerr; using std::fstream; using std::ios; int main() { int ret = -1; // 初始化mysql上下文 MYSQL mysql; mysql_library_init(0, 0, 0); if (!mysql_init(&mysql)) { cout << "mysql init failed " << mysql_error(&mysql) << endl; return ret; } // 这里只是为了演示所以用了明文,实际开发中应写入加密的ini文件中 const char* host = "127.0.0.1"; //const char* host = "192.168.0.222"; const char* user = "root"; const char* passwd = "123456"; const char* db = "zpdatabase"; // 数据库名称 // 连接登录数据库 // 支持多条sql语句:最后一个参数为CLIENT_MULTI_STATEMENTS if (!mysql_real_connect(&mysql, host, user, passwd, db, 3306, nullptr, CLIENT_MULTI_STATEMENTS)) { cout << "mysql connect failed! " << mysql_error(&mysql) << endl; mysql_close(&mysql); return ret; } else { cout << "mysql connect " << host << " success!" << endl; } string sql = ""; // 1 创建存储过程 // (把输入参数打印出来;改变输入参数的值;把改变的值再打印一遍) sql = "CREATE PROCEDURE `p_test` (IN p_in INT, OUT p_out INT, INOUT p_inout INT) \ BEGIN \ SELECT p_in, p_out, p_inout; \ SET p_in = 100, p_out = 200, p_inout = 300; \ SELECT p_in, p_out, p_inout; \ END"; int result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "CREATE PROCEDURE failed! " << mysql_error(&mysql) << endl; } // sql = "call p_test(1, 2, 3);" //这样调用是不行的,传入的实参必须是变量 // 2 定义变量并赋值 cout << "IN A = 1, B = 2, C = 3" << endl; sql = "SET @A = 1; SET @B = 2; SET @C = 3"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "SET PARAMETER failed! " << mysql_error(&mysql) << endl; } do { cout << "SET affected" << mysql_affected_rows(&mysql) << endl; }while (mysql_next_result(&mysql) == 0); // 0表示还有结果 -1没有结果 >1错误 // 3 调用存储过程 call sql = "CALL p_test(@A, @B, @C)"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "CALL PROCEDURE failed! " << mysql_error(&mysql) << endl; } cout << "In Proc: "; do { MYSQL_RES* res = mysql_store_result(&mysql); if(!res) continue; // 首先要知道字段的数量 int fcount = mysql_num_fields(res); // 打印结果集 for(;;) { // 提取一行记录 MYSQL_ROW row = mysql_fetch_row(res); if(!row) break; for(int i = 0; i < fcount; i++) { // 打印前做下判断,因为mysql里面是有NULL数据的,你打印NULL的话是会出错的 if(row[i] != NULL) { cout << "row[" << i << "]:" << row[i] << " ; "; } } cout << endl; } }while (mysql_next_result(&mysql) == 0); // 4 获取存储过程的结果 sql = "SELECT @A, @B, @C"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "mysql_query failed! " << mysql_error(&mysql) << endl; } mysql_close(&mysql); mysql_library_end(); ret = 0; cout << "MYSQL 5.5.40" << endl; getchar(); return ret; }
通过上图的运行结果,我们可以看到只有1和3,没有2,为什么没有2,那是因为我们2是NULL,我们修改下代码把NULL打印出来:
// 3 调用存储过程 call sql = "CALL p_test(@A, @B, @C)"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "CALL PROCEDURE failed! " << mysql_error(&mysql) << endl; } cout << "In Proc: "; do { MYSQL_RES* res = mysql_store_result(&mysql); if(!res) continue; // 首先要知道字段的数量 int fcount = mysql_num_fields(res); // 打印结果集 for(;;) { // 提取一行记录 MYSQL_ROW row = mysql_fetch_row(res); if(!row) break; for(int i = 0; i < fcount; i++) { // 打印前做下判断,因为mysql里面是有NULL数据的,你打印NULL的话是会出错的 if(row[i] != NULL) { cout << "row[" << i << "]:" << row[i] << " ; "; } else cout << "row[" << i << "]:" << row[i] << " ; "; } cout << endl; } }while (mysql_next_result(&mysql) == 0);
我们第2个参数的类型是OUT,传进来的OUT类型参数的值是没有用的,所以它在存储过程中的值是NULL,而传进去的INOUT类型的参数是有用的;所以说传进存储过程中的OUT类型的参数@B的值2是没有用的;
所以说inout传进去了,in也传进去了,而这个out没有传进去。
接着我们来看下获取存储过程返回的变量:
因为我们知道最后这一条sql语句只返回了一行结果集,有3个字段,所以可以直接打印这3个字段:
// 4 获取存储过程的结果 sql = "SELECT @A, @B, @C"; result = mysql_query(&mysql, sql.c_str()); if(result != 0) { cout << "mysql_query failed! " << mysql_error(&mysql) << endl; } MYSQL_RES* res = mysql_store_result(&mysql); cout << "out:"; MYSQL_ROW row = mysql_fetch_row(res); cout << "in=" << row[0] << " "; cout << "out=" << row[1] << " "; cout << "inout=" << row[2] << " "; // 最后别忘了释放结果集,不释放的话会有内存泄漏的 mysql_free_result(res); cout << endl; mysql_close(&mysql); mysql_library_end(); ret = 0; cout << "MYSQL 5.5.40" << endl; getchar(); return ret; }
我们看一下程序执行的结果,输入的in是1,经过我们的存储过程,给它设成了100,但返回的还是1,因为它只是输入,是IN类型的;
传入的第2个变量值是2,在存储过程中被修改成200,所以出来就是200,因为第2个参数的类型是OUT,但是在存储过程内部并没有取得传入的值;
第3个参数的类型是INOUT,即输入又输出,它是3进来的,被改成了300,所以出来就是300。
这样的话我们就完成了存储过程的3种参数的传递,具体内部是什么业务逻辑我们都可以做,我们只要清楚它的参数是怎么传进去,怎么取出来就可以了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。