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. rollbacksql = "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;
}