1、事务的概念
-
定义:事务是构成单一逻辑工作单元的操作集合,要么完整的执行,要么完全不执行。无论发生何种情况,DBS必须保证事务能正确、完整的执行。
-
性质:事务的四大ACID性质。
- 原子性(Atomicity):一个事务对数据库的所有操作,是一个不可分割的工作单元。这些操作要么全部执行,要么全都不执行。既要么执行成功要么执行失败
- 一致性(Consistency):一个事务独立执行的结果,应该保持数据库的一致性,既数据不会因事务的执行而遭受破坏
- 隔离性(Isolation):在多个事务并发执行时,系统应该保证与这些事务先后单独执行时的结果一样,此时称事务打到了隔离性的要求。也就是在多个事务并发执行时,保证执行结果是正确的,如同单用户环境一样。早些年的MyISAM数据库引擎是采用表锁的形式,当执行SQL语句时锁定一张表,而当今的InnoDB引擎是采用行锁定的,在操纵数据库的的时候只锁定操作的行不锁定这张表,这就是隔离性
- 持久性(Durability):一个事务一旦完成全部操作后,它对数据库的所有更新应永久的反映在数据库中,不会丢失、即以后系统发生故障也是如此。
2、事务的并发读和隔离级别问题
2.1、事务的并发读问题
- 脏读:一个事务读取到了另一个事务未成功提交的数据;例如:事务A对数据库中的数据进行了修改但是还未提交、此时事务B进行数据读取;但是事务A因为某些原因回滚了,此时B拿到的数据是一个无效数据,也叫脏数据!
-
不可重复读:同一个事务内,先后两次读取数据返回结果不一致;例如:事务A第一次读取到数据、事务B
修改
数据并且成功提交、事务A第二次读取数据;两次读取数据结果不一致。由于事务没有形成隔离导致 -
幻读:一个事务读取到另一个事务已经提交的添加或者删除的数据;例如:A事务读取数据、B事务
删除(增加)
数据,A事务再次读取数据,发现多出一些新的数据,像出现了幻觉一样。 -
幻读与不可重复度的区别:不可重复度强调的是修改数据;幻读强调的是添加、删除数据。
-
总结:脏读是致命的操作,因为拿到的数据是无效数据;而不可重复度与幻读是一种现象,只是先后读取不一致的问题,但是数据是有效的(其他事物成功提交)!
2.2、事务的隔离级别
-
TRANSACTION_NONE:无事务。
-
TRANSACTION_READ_UNCOMMITTED:未解决任何问题,可能出现一系列并发问题
-
TRANSACTION_READ_COMMITTED:解决了脏读问题
-
TRANSACTION_REPEATABLE_READ:解决了脏读和不可重复读问题
TRANSACTION_SERIALIZABLE:解决了脏读、不可重复读、幻读问题,采用串行化访问。
3、C++使用MySQL事务操作
3.1、事务的测试
- 事务操作主要分4-5个步骤
- 开启事务:“start transaction”
- 关闭自动提交:“set autocommit = 0;”
- 执行SQL语句:主要是一些增删改操作
- 回滚或提交:当出现问题时可以rollback回滚,如果没有问题直接commit提交
- 恢复自动提交:“set autocommit = 1;”
void transaction_test(MYSQL &mysql)
{
/*
* 事务的操作:
* 1. 开启事务
* 2. 关闭自动提交,开启手动提交
* 3. 执行sql
* 4. 成功 commit 或者 rollback
* 5. 开启自动提交
*/
// 1. 开启事务
string sql = "start transaction;";
mysql_real_query(&mysql, sql.c_str(), sql.length());
// 2. 开启手动提交
sql = "set autocommit = 0;";
mysql_real_query(&mysql, sql.c_str(), sql.length());
// 3. 执行sql语句
for(int i = 1;i <= 5;i++){
stringstream ss;
sql = "insert into `test`(`username`, `password`) values('Admin', '123456');";
int insert_result = mysql_real_query(&mysql, sql.c_str(), sql.length());
if(insert_result != 0){
cout << "insert data failed! sql = " << sql << ", error msg = " << mysql_error(&mysql) << endl;
}
}
// 4. rollback
sql = "rollback";
mysql_real_query(&mysql, sql.c_str(), sql.length());
// 5. 再次执行插入操作
sql = "insert into `test`(`username`, `password`) values('Splay', '123456');";
mysql_real_query(&mysql, sql.c_str(), sql.length());
// 6. commit提交
sql = "commit";
mysql_real_query(&mysql, sql.c_str(), sql.length());
// 7. 回复自动提交
sql = "set autocommit = 1;";
mysql_real_query(&mysql, sql.c_str(), sql.length());
// 8. 查询数据库数据的数量
sql = "select count(*) from `test`;";
mysql_real_query(&mysql, sql.c_str(), sql.length());
// 释放mysql结构体
MYSQL_RES* result = mysql_store_result(&mysql);
cout << "当前数据库的数据行数:" << mysql_fetch_row(result)[0] << endl;
}
3.2、事务与不同批量插入数据的性能对比
对于不同的类型的操作每次都插入1000条数据,在插入之前将表清空。
- 单条插入:每次插入一条数据,这样就会导致一个问题执行一条SQL语句,频繁的开启关闭事务会造成性能浪费
- 批量插入:1000条数据一次性组合,通过CLIENT_MULTI_STATEMENT设置进行多SQL的同时执行
- 事务插入:将1000条数据打包成一个事务里,最后一次性提交(一个事务内)
void truncate_table(MYSQL &mysql)
{
string sql = "truncate table `test`;";
mysql_real_query(&mysql, sql.c_str(), sql.length());
}
void test_single_insert(MYSQL &mysql)
{
truncate_table(mysql);
auto start = std::chrono::system_clock::now();
for(int i = 1;i <= 1000;i++){
string sql = "insert into `test`(`username`, `password`) values('Splay', '123456');";
mysql_real_query(&mysql, sql.c_str(), sql.length());
}
auto end = std::chrono::system_clock::now();
auto duration = duration_cast<chrono::milliseconds> (end - start);
cout << "single_insert插入1万条数据所需的时间: " << duration.count()/1000.0 << "秒" << endl;
}
void test_multi_insert(MYSQL &mysql)
{
truncate_table(mysql);
auto start = std::chrono::system_clock::now();
string sql = "";
for(int i = 1;i <= 1000;i++){
sql += "insert into `test`(`username`, `password`) values('Splay', '123456');";
}
mysql_real_query(&mysql, sql.c_str(), sql.length());
do{
mysql_affected_rows(&mysql);
} while(mysql_next_result(&mysql) == 0);
auto end = std::chrono::system_clock::now();
auto duration = duration_cast<chrono::milliseconds> (end - start);
cout << "multi_insert插入1万条数据所需的时间: " << duration.count()/1000.0 << "秒" << endl;
}
void test_transaction_insert(MYSQL &mysql)
{
truncate_table(mysql);
auto start = std::chrono::system_clock::now();
// 1. 开启事务
string sql = "start transaction;";
mysql_real_query(&mysql, sql.c_str(), sql.length());
// 2. 开启手动提交
sql = "set autocommit = 0;";
mysql_real_query(&mysql, sql.c_str(), sql.length());
// 3. 执行sql语句
for(int i = 1;i <= 1000;i++){
sql = "insert into `test`(`username`, `password`) values('Admin', '123456');";
mysql_real_query(&mysql, sql.c_str(), sql.length());
}
// 4. commit提交
sql = "commit";
mysql_real_query(&mysql, sql.c_str(), sql.length());
// 5. 恢复自动提交
sql = "set autocommit = 1;";
mysql_real_query(&mysql, sql.c_str(), sql.length());
auto end = std::chrono::system_clock::now();
auto duration = duration_cast<chrono::milliseconds> (end - start);
cout << "transaction_insert插入1万条数据所需的时间: " << duration.count()/1000.0 << "秒" << endl;
}
// mysql connect 127.0.0.1 success!
// single_insert插入1万条数据所需的时间: 5.379秒
// multi_insert插入1万条数据所需的时间: 5.516秒
// transaction_insert插入1万条数据所需的时间: 0.068秒
3.3、连接等其他代码
#include <iostream>
#include <mysql/mysql.h>
#include <cstring>
#include <sstream>
#include <string>
#include <chrono>
#include <unordered_map>
using namespace std;
using namespace chrono;
void create_table(MYSQL &mysql)
{
string sql = "CREATE TABLE IF NOT EXISTS `test` (\
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,\
`username` varchar(255) NOT NULL,\
`password` varchar(255) NOT NULL,\
PRIMARY KEY (`id`)\
) ENGINE=InnoDB DEFAULT CHARSET=utf8;";
if(mysql_real_query(&mysql, sql.c_str(), sql.length()) != 0){
cout << "mysql_query failed!" << endl;
}
}
int main(int argc, char *argv[])
{
MYSQL mysql;
// 初始化mysql结构体并且初始化服务连接环境
mysql_init(&mysql);
const char *host = "127.0.0.1";
const char *user = "root";
const char *password = "123456";
const char *db = "cpp";
int timeout = 3;
// 连接超时时长设置
mysql_options(&mysql, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
// 断开重连设置
int reconnect = 1;
mysql_options(&mysql, MYSQL_OPT_RECONNECT, &reconnect);
// MySQL连接建立
if(!mysql_real_connect(&mysql, host, user, password, db, 3306, 0, CLIENT_MULTI_STATEMENTS)){
std::cout << "mysql connect failed!" << mysql_error(&mysql) << std::endl;
}
else{
std::cout << "mysql connect " << host << " success!" << std::endl;
}
// create_table(mysql);
// transaction_test(mysql);
test_single_insert(mysql);
test_multi_insert(mysql);
test_transaction_insert(mysql);
mysql_close(&mysql);
mysql_library_end();
return 0;
}