Linux与数据库进阶

事物

事务就是一组原子性的SQL查询,或者说一个独立的工作单元。如果数据库引擎能够成功地对数据库应用该组查询的全部语句,那么就执行该组查询。如果其中有任何一条语句因为崩溃或其他原因无法执行,那么所有的语句都不会执行。也就是说,事务内的语句,要么全部执行成功,要么全部执行失败。

为什么需要事物

比如一个银行业务如有借有贷,每有一次借贷业务,需要保证账目上的借方和贷方至少都记上相等的一笔帐,这两笔账要么同时成功,要么同时失败,如果只有一方帐,就会出现记错账的情况.

事物的四大特性

MySQL中的事务(Transaction)是一组操作的集合,这些操作要么全部成功,要么全部失败,是数据库操作的一个基本单位。事务具有四个关键特性,通常被称为ACID特性:

以银行借贷为例:

1.要查看支票账户的余额是否高于转账金额

2.从支票账户余额减去转账金额

3.在储蓄账户余额加上转账金额

原子性

一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性。

一致性

当事物完成时,数据必须处于一致状态,如在事物开始前数据存储数据处于一致状态,当事物执行的途中数据可能处于不同的状态,但是当事物结束后数据依旧要处于同一状态,比如,当第三条语句执行失败以后,因为事物并没有提交,所以并不会保存到数据库中

隔离性

通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。在前面的例子中,当执行完第三条语句、第四条语句还未开始时,此时有另外一个账户汇总程序开始运行,则其看到的支票账户的余额并没有被减去200美元。后面我们讨论隔离级别(Isolationlevel)的时候,会发现为什么我们要说"通常来说"是不可见的。

持久性

一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。持久性是个有点模糊的概念,因为实际上持久性也分很多不同的级别。有些持久性策略能够提供非常强的安全保障,而有些则未必。而且不可能有能做到100%的持久性保证的策略(如果数据库本身就能做到真正的持久性,那么备份又怎么能增加持久性呢?)。在后面的一些章节中,我们会继续讨论MySQL中持久性的真正含义。

隔离级别

在SQL标准中定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发,系统的开销也更低。

读未提交

读未提交是最低的隔离级别,允许事务读取其他未提交事务的修改(脏读)。这种级别牺牲了数据一致性以换取高并发性能,适用于对数据准确性要求极低但需要快速响应的场景(如实时统计近似值),但在多数业务系统中容易导致数据不一致问题。举个栗子:还是以银行借贷为例,

事物1:A要从账户中拿出1000元给B(未提交)

事物2:查看A的账户余额(直接读到A的账户少了1000)

如果事物1突然回滚,资产没有变化,但是事物B已经读到数据并使用了,这就是脏读.

读已提交

读已提交要求事务只能读取其他已提交事务的数据,避免了脏读,但仍可能发生不可重复读(同一事务多次读取结果不同)。这是多数数据库的默认隔离级别(如Oracle),适合允许短期数据变化的场景(如账户余额查询),平衡了性能与一致性需求。

可重复读

可重复读确保同一事务中多次读取同一数据的结果一致,避免了不可重复读,但仍可能发生幻读(范围查询结果变化)。MySQL默认采用此级别并通过"间隙锁"减少幻读。适用于需要事务内数据稳定的场景(如对账操作),但可能增加锁竞争。

串行化(Serializable)

串行化是最严格的隔离级别,通过强制事务串行执行来彻底避免脏读、不可重复读和幻读。其代价是并发性能显著下降,通常仅用于对数据一致性要求极高的场景(如金融交易核验)。实际应用中需谨慎权衡,避免系统吞吐量瓶颈。

查看当前会话隔离级别

sql 复制代码
mysql> SELECT @@SESSION.transaction_isolation;
 +---------------------------------+
 | @@SESSION.transaction_isolation |
 +---------------------------------+
 | REPEATABLE-READ                 |
 +---------------------------------+
 1 row in set (0.00 sec)
 mysql>

查看系统隔离级别

sql 复制代码
mysql> SELECT @@GLOBAL.transaction_isolation;
 +--------------------------------+
 | @@GLOBAL.transaction_isolation |
 +--------------------------------+
 | REPEATABLE-READ                |
 +--------------------------------+
 1 row in set (0.00 sec)
 mysql>

设置隔离级别

sql 复制代码
# 设置全局隔离级别
set global transaction isolation level REPEATABLE READ;
 set global transaction isolation level  READ COMMITTED;
 set global transaction isolation level READ UNCOMMITTED;
 set global transaction isolation level SERIALIZABLE;
 #设置会话隔离级别
set session transaction isolation level REPEATABLE READ;
 set session transaction isolation level READ COMMITTED;
 set session transaction isolation level READ UNCOMMITTED;
 set session transaction isolation level SERIALIZABLE;

C语言链接数据库---一种更安全的方式

在之前的学习中我们学习的注入语句mysql_query(),一般来说我们写成这个样子:

sql 复制代码
// 传统拼接 SQL 的写法(不安全)
char sql[200];
sprintf(sql, "insert into empolyees(name, em_id, dp) values('%s', '%s', '%s')", name, em_id, dp);
// 执行 SQL
mysql_query(conn, sql);

但是倘若有人向我们的系统中恶意输入一些构造后的sql语句如:

sql 复制代码
张三'); delete from empolyees where 1=1; --

那么我们的语句就会变成这样

sql 复制代码
insert into empolyees(name, em_id, dp) values('张三'); delete from empolyees where 1=1; -- ', '123', '技术部');

这样执行结果就会变成先向数据库中插入一条张三,然后删除所有的员工信息

这时我们就可以使用预处理语句来使用我们的sql语句

预处理语句

预处理语句(也译作 "预编译语句")是数据库编程中一种安全、高效的 SQL 执行方式,核心思想是将 SQL 语句的 "结构解析" 和 "参数赋值" 分两步进行,而非传统的一次性拼接执行,其流程如下图

初始化预处理指令

cpp 复制代码
MYSQL_STMT * stmt=mysql_stmt_init(conn);

发送 SQL 模板并解析

cpp 复制代码
const char *sql = "insert into empolyees(name, em_id, dp) values(?,?,?)";
mysql_stmt_prepare(stmt, sql, strlen(sql)); // 发送模板到数据库解析

绑定并发送参数

cpp 复制代码
mysql_stmt_bind_param(stmt, bind); // 将用户输入的参数绑定到占位符

执行语句

cpp 复制代码
mysql_stmt_execute(stmt); // 执行填充了参数的SQL

预处理语句的核心优势

1**. 彻底杜绝 SQL 注入(最核心优势)**

这是预处理语句最关键的价值:

传统拼接 SQL:用户输入的 张三'); delete from empolyees; -- 会被当作 SQL 语法执行;

预处理语句:无论参数中包含什么字符,数据库都只会将其视为 "字符串数据",而非 SQL 指令,从根源上阻断注入。

  1. 执行效率更高(批量操作时

如果需要多次执行结构相同、参数不同的 SQL(比如批量插入 1000 条员工数据):

传统方式:每条 SQL 都要重新解析语法、生成执行计划,耗时久;

预处理语句:仅解析 1 次模板,后续只需传参数执行,节省大量解析时间。

3.简化参数处理

无需手动转义特殊字符(比如把 ' 转成 ''),数据库会自动处理参数中的特殊字符,避免手动转义遗漏导致的安全问题。
预处理语句的适用场景

所有需要接收用户输入的 SQL 操作(增 / 删 / 改 / 查),尤其是插入、更新、查询;

批量执行相同结构的 SQL(比如批量导入数据);

对安全性要求高的场景(如用户登录、数据写入、敏感信息查询)。

例子

下面以实现往员工信息表中插入语句为例:

cpp 复制代码
int add_empolyees()
{
   //声明函数变量
   MYSQL *conn;          //数据库连接对象
   MYSQL_STMT * stmt;    //预处理语句对象
   MYSQL_BIND bind[3];   //参数绑定数组

   //用户输入缓冲区
   char name[50];
   char em_id[50];
   char dp[50];

   //声明字符串长度变量
   unsigned long name_len;
   unsigned long em_id_len;
   unsigned long dp_len;

   printf("----添加员工界面----\n");
   
   //获取员工姓名
   printf("请输入员工姓名:\n");
   gets();
   fgets(name, sizeof(name), stdin);
   name[strcspn(name ,"\n")] = 0;  //移除末尾的换行符

   //获取员工姓名
   printf("请输入员工工号:\n");
   fgets(em_id, sizeof(em_id), stdin);
   em_id[strcspn(em_id ,"\n")] = 0;  //移除末尾的换行符

   //获取员工姓名
   printf("请输入员工部门:\n");
   fgets(dp, sizeof(dp), stdin);
   dp[strcspn(dp ,"\n")] = 0;  //移除末尾的换行符

   //建立数据库的连接
   conn = connect_databse();
   if(conn == NULL)
    return 1;

   //定义sql语句,使用?作为参数占位符
   const char *sql = "insert into empolyees(name, em_id, dp) values(?,?,?)";
   //初始化预处理语句
   stmt = mysql_stmt_init(conn);
   //准备预处理语句,发送给数据库进行解析
   if(mysql_stmt_prepare(stmt, sql, strlen(sql)) !=0 )
     {
        printf("预处理语句准备失败\n");
        //close_database();
        return 1;
     }
    
    //将参数绑定数组清空
    memset(bind ,0 ,sizeof(bind));
    
    //绑定姓名信息
    name_len = strlen(name);
    bind[0].buffer_type = MYSQL_TYPE_STRING; //指定参数类型为字符串
    bind[0].buffer = name;  //指定数据缓冲区
    bind[0].buffer_length = sizeof(name);   //指定数据缓冲区大小
    bind[0].length = &name_len; //指定字符串长度指针

     //绑定工号信息
    em_id_len = strlen(em_id);
    bind[1].buffer_type = MYSQL_TYPE_STRING; //指定参数类型为字符串
    bind[1].buffer = em_id;  //指定数据缓冲区
    bind[1].buffer_length = sizeof(em_id);   //指定数据缓冲区大小
    bind[1].length = &em_id_len; //指定字符串长度指针

     //绑定部门信息
    dp_len = strlen(dp);
    bind[2].buffer_type = MYSQL_TYPE_STRING; //指定参数类型为字符串
    bind[2].buffer = dp;  //指定数据缓冲区
    bind[2].buffer_length = sizeof(dp);   //指定数据缓冲区大小
    bind[2].length = &dp_len; //指定字符串长度指针


    //将参数设置到预处理语句中
    if(mysql_stmt_bind_param(stmt, bind) !=0)
    {
        printf("参数绑定失败\n");
        mysql_stmt_close(stmt);//关闭预处理语句
        //close_database();
        return 1;
    }

    //将准备好的预处理语句发送给数据库实现功能
    if(mysql_stmt_execute(stmt) !=0)
    {
        printf("预处理语句插入失败\n");
        mysql_stmt_close(stmt);//关闭预处理语句
        //close_database();
        return 1;
    }

    printf("员工信息添加成功!\n");
    //释放资源
    mysql_stmt_close(stmt);//关闭预处理语句
    //close_database();
    return 0;
}
相关推荐
与衫1 小时前
Gudu SQL Omni 技术深度解析
数据库·sql
咖啡の猫2 小时前
Redis桌面客户端
数据库·redis·缓存
oradh2 小时前
Oracle 11g数据库软件和数据库静默安装
数据库·oracle
what丶k2 小时前
如何保证 Redis 与 MySQL 数据一致性?后端必备实践指南
数据库·redis·mysql
_半夏曲2 小时前
PostgreSQL 13、14、15 区别
数据库·postgresql
把你毕设抢过来2 小时前
基于Spring Boot的社区智慧养老监护管理平台(源码+文档)
数据库·spring boot·后端
未来之窗软件服务2 小时前
数据库(九)SQL 模式操作 Excel——东方仙盟练气
数据库·sql·excel·仙盟创梦ide·东方仙盟·数据库修复
点点滴滴的记录3 小时前
Redis部署在Linux上性能高于Windows
linux·数据库·redis
lhj_loveFang_11053 小时前
Redis如何与数据库保持双写一致性
数据库·redis