C++:MySQL的事务概念与使用(四)

1、事务的概念
  • 定义:事务是构成单一逻辑工作单元的操作集合,要么完整的执行,要么完全不执行。无论发生何种情况,DBS必须保证事务能正确、完整的执行。

  • 性质:事务的四大ACID性质。

    • 原子性(Atomicity):一个事务对数据库的所有操作,是一个不可分割的工作单元。这些操作要么全部执行,要么全都不执行。既要么执行成功要么执行失败
    • 一致性(Consistency):一个事务独立执行的结果,应该保持数据库的一致性,既数据不会因事务的执行而遭受破坏
    • 隔离性(Isolation):在多个事务并发执行时,系统应该保证与这些事务先后单独执行时的结果一样,此时称事务打到了隔离性的要求。也就是在多个事务并发执行时,保证执行结果是正确的,如同单用户环境一样。早些年的MyISAM数据库引擎是采用表锁的形式,当执行SQL语句时锁定一张表,而当今的InnoDB引擎是采用行锁定的,在操纵数据库的的时候只锁定操作的行不锁定这张表,这就是隔离性
    • 持久性(Durability):一个事务一旦完成全部操作后,它对数据库的所有更新应永久的反映在数据库中,不会丢失、即以后系统发生故障也是如此。
2、事务的并发读和隔离级别问题
2.1、事务的并发读问题
  • 脏读:一个事务读取到了另一个事务未成功提交的数据;例如:事务A对数据库中的数据进行了修改但是还未提交、此时事务B进行数据读取;但是事务A因为某些原因回滚了,此时B拿到的数据是一个无效数据,也叫脏数据!
  1. 不可重复读:同一个事务内,先后两次读取数据返回结果不一致;例如:事务A第一次读取到数据、事务B修改数据并且成功提交、事务A第二次读取数据;两次读取数据结果不一致。由于事务没有形成隔离导致

  2. 幻读:一个事务读取到另一个事务已经提交的添加或者删除的数据;例如:A事务读取数据、B事务删除(增加)数据,A事务再次读取数据,发现多出一些新的数据,像出现了幻觉一样。

  3. 幻读与不可重复度的区别:不可重复度强调的是修改数据;幻读强调的是添加、删除数据。

  4. 总结:脏读是致命的操作,因为拿到的数据是无效数据;而不可重复度与幻读是一种现象,只是先后读取不一致的问题,但是数据是有效的(其他事物成功提交)!

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;
}

相关推荐

  1. Mysql - 事务概念

    2024-04-08 06:36:03       14 阅读
  2. 【LinuxC语言】信号基本概念基本使用

    2024-04-08 06:36:03       15 阅读

最近更新

  1. stm32使用单通道规则组ADC

    2024-04-08 06:36:03       0 阅读
  2. HTML 学习指南:从入门到精通

    2024-04-08 06:36:03       0 阅读
  3. 经导管二尖瓣修复系统市场全面解析与未来展望

    2024-04-08 06:36:03       0 阅读
  4. 大众点评商家电话采集软件分享 解析爬虫工具

    2024-04-08 06:36:03       1 阅读
  5. Python 使用正则提取字符串

    2024-04-08 06:36:03       1 阅读
  6. 专业课笔记——(第十二章:文件的读写)

    2024-04-08 06:36:03       1 阅读
  7. lvs集群

    2024-04-08 06:36:03       1 阅读

热门阅读

  1. 李沐16神经网络基础——自学笔记

    2024-04-08 06:36:03       13 阅读
  2. 文件上传到阿里云

    2024-04-08 06:36:03       16 阅读
  3. terraform创建多台阿里云ecs 命名问题

    2024-04-08 06:36:03       16 阅读
  4. 应用运维文档1

    2024-04-08 06:36:03       13 阅读
  5. 汇编基础-----常见命令基本使用

    2024-04-08 06:36:03       15 阅读
  6. python 标准库之 xml.etree.ElementTree

    2024-04-08 06:36:03       13 阅读
  7. 机器学习---集成学习(未完成)

    2024-04-08 06:36:03       18 阅读