当前位置: 首页 > news >正文

在MySQL中处理同时进行的SELECT和UPDATE操作20240729

在MySQL中处理同时进行的SELECT和UPDATE操作

在MySQL中同时对同一张表执行SELECT和UPDATE操作可能会引发脏读、不可重复读和幻读等问题。本文将详细介绍这些问题及其解决方法,并提供具体的示例代码。

问题描述

在多事务环境下,可能会遇到以下问题:

  1. 脏读 (Dirty Read): 一个事务在读取数据的同时,另一个事务修改了同一行的数据并提交,第一个事务读取的数据可能是不一致的。
  2. 不可重复读 (Non-repeatable Read): 一个事务在读取数据的同时,另一个事务修改了同一行的数据并提交,第一个事务再次读取同一行的数据时,可能会得到不同的结果。
  3. 幻读 (Phantom Read): 一个事务在读取数据的同时,另一个事务插入了符合第一个事务查询条件的新数据并提交,第一个事务再次执行相同的查询时,会发现存在新的数据。

解决方法

为了解决上述问题,可以使用MySQL提供的事务隔离级别和锁机制来确保数据的一致性。

事务隔离级别

MySQL支持四种事务隔离级别:

  1. READ UNCOMMITTED: 允许脏读、不可重复读和幻读。
  2. READ COMMITTED: 防止脏读,但允许不可重复读和幻读。
  3. REPEATABLE READ: 防止脏读和不可重复读,但允许幻读。
  4. SERIALIZABLE: 防止脏读、不可重复读和幻读。

可以通过设置事务隔离级别来控制并发事务的行为。下面是设置事务隔离级别的示例:

-- 设置事务隔离级别为 REPEATABLE READ
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;START TRANSACTION;-- 你的查询和更新操作
SELECT * FROM employees WHERE id = 1 FOR UPDATE;
UPDATE employees SET name = 'new_name' WHERE id = 1;COMMIT;
锁机制

MySQL的InnoDB存储引擎提供了行级锁,可以在查询时使用锁来确保数据的一致性。

-- 设置事务隔离级别为 SERIALIZABLE
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;START TRANSACTION;-- 你的查询和更新操作
SELECT * FROM employees WHERE id = 1 FOR UPDATE;
UPDATE employees SET name = 'new_name' WHERE id = 1;COMMIT;

在上述示例中,使用FOR UPDATE语句对查询的行加锁,以防止其他事务在当前事务完成之前修改这些行。

实际案例分析

假设我们在两个不同的数据库实例中进行实验,以验证不同配置下MySQL的行为。

表结构

假设我们有一个名为employees的表,表结构如下:

CREATE TABLE employees (id INT PRIMARY KEY,name VARCHAR(255),department VARCHAR(255)
);
实验步骤和SQL语句
  1. 环境准备
    • 创建数据库和表。
    • 插入初始数据。
-- 创建数据库
CREATE DATABASE company_db;-- 选择数据库
USE company_db;-- 创建表
CREATE TABLE employees (id INT PRIMARY KEY,name VARCHAR(255),department VARCHAR(255)
);-- 插入初始数据
INSERT INTO employees (id, name, department) VALUES (1, 'Alice', 'HR');
  1. Session 1: 开始事务,查询employees表。
-- Session 1
START TRANSACTION;
SELECT * FROM employees;
  1. Session 2: 插入一行数据到employees表并提交事务。
-- Session 2
START TRANSACTION;
INSERT INTO employees (id, name, department) VALUES (2, 'Bob', 'Engineering');
COMMIT;
  1. Session 1: 再次查询employees表。
-- Session 1
SELECT * FROM employees;

实验现象

在两个环境中进行上述操作,并观察结果。

  • 环境A: Session 1无法查询到Session 2提交的数据。
  • 环境B: Session 1可以查询到Session 2提交的数据。

实际案例分析

通过不同的环境验证,我们发现:

环境A
  • MySQL版本: 5.7.13
  • autocommit: OFF
  • 事务隔离级别: REPEATABLE READ

在此环境中,Session 1在第二次查询时无法看到Session 2提交的数据,因为事务在开始时就固定了读取的数据版本。

环境B
  • MySQL版本: 5.7.13
  • autocommit: ON
  • 事务隔离级别: REPEATABLE READ

在此环境中,Session 1在第二次查询时可以看到Session 2提交的数据,因为每个查询都是一个新的事务。

验证问题

通过不同组合的autocommit和隔离级别参数,验证查询结果是否符合预期。

验证组合
  1. autocommit=ON,隔离级别=REPEATABLE-READ: Session 1能否在T3时刻看到Session 2提交的数据?
    • 答:能。
  2. autocommit=OFF,隔离级别=REPEATABLE-READ: Session 1能否在T3时刻看到Session 2提交的数据?
    • 答:不能。
  3. autocommit=ON,隔离级别=READ-COMMITTED: Session 1能否在T3时刻看到Session 2提交的数据?
    • 答:能。
  4. autocommit=OFF,隔离级别=READ-COMMITTED: Session 1能否在T3时刻看到Session 2提交的数据?
    • 答:能。

代码示例

以下是Python、C语言和Go语言的示例代码,演示如何在MySQL中使用事务和锁机制来解决SELECT和UPDATE操作的并发问题。

Python代码示例
import pymysql# 连接数据库
conn1 = pymysql.connect(host='localhost', user='root', password='password', db='company_db', autocommit=False)
conn2 = pymysql.connect(host='localhost', user='root', password='password', db='company_db', autocommit=True)try:with conn1.cursor() as cursor1, conn2.cursor() as cursor2:# Session 1: 开始事务并查询数据cursor1.execute("START TRANSACTION;")cursor1.execute("SELECT * FROM employees;")result1 = cursor1.fetchall()print("Session 1 - First Query:", result1)# Session 2: 插入数据并提交事务cursor2.execute("INSERT INTO employees (id, name, department) VALUES (2, 'Bob', 'Engineering');")cursor2.execute("COMMIT;")# Session 1: 再次查询数据cursor1.execute("SELECT * FROM employees;")result2 = cursor1.fetchall()print("Session 1 - Second Query:", result2)
finally:conn1.close()conn2.close()
C语言代码示例
#include <mysql/mysql.h>
#include <stdio.h>void finish_with_error(MYSQL *con) {fprintf(stderr, "%s\n", mysql_error(con));mysql_close(con);exit(1);
}int main() {MYSQL *con1 = mysql_init(NULL);MYSQL *con2 = mysql_init(NULL);if (con1 == NULL || con2 == NULL) {fprintf(stderr, "mysql_init() failed\n");exit(1);}if (mysql_real_connect(con1, "localhost", "root", "password", "company_db", 0, NULL, 0) == NULL ||mysql_real_connect(con2, "localhost", "root", "password", "company_db", 0, NULL, 0) == NULL) {finish_with_error(con1);finish_with_error(con2);}// Session 1: 开始事务并查询数据if (mysql_query(con1, "START TRANSACTION") ||mysql_query(con1, "SELECT * FROM employees")) {finish_with_error(con1);}MYSQL_RES *result1 = mysql_store_result(con1);if (result1 == NULL) {finish_with_error(con1);}MYSQL_ROW row;printf("Session 1 - First Query:\n");while ((row = mysql_fetch_row(result1))) {printf("%s %s %s\n", row[0], row[1], row[2]);}mysql_free_result(result1);// Session 2: 插入数据并提交事务if (mysql_query(con2, "START TRANSACTION") ||mysql_query(con2, "INSERT INTO employees (id, name, department) VALUES (2, 'Bob', 'Engineering')") ||mysql_query(con2, "COMMIT")) {finish_with_error(con2);}// Session 1: 再次查询数据if (mysql_query(con1, "SELECT * FROM employees")) {finish_with_error(con1);}MYSQL_RES *result2 = mysql_store_result(con1);if (result2 == NULL) {finish_with_error(con1);}printf("Session 1 - Second Query:\n");while ((row = mysql_fetch_row(result2))) {printf("%s %s %s\n", row[0], row[1], row[2]);}mysql_free_result(result2);mysql_close(con1);mysql_close(con2);return 0;
}
Go语言代码示例
package mainimport ("database/sql""fmt"_ "github.com/go-sql-driver/mysql"
)func main() {db1, err := sql.Open("mysql", "root:password@tcp(localhost:3306)/company_db?autocommit=false")if err != nil {panic(err)}defer db1.Close()db2, err := sql.Open("mysql", "root:password@tcp(localhost:3306)/company_db?autocommit=true")if err != nil {panic(err)}defer db2.Close()// Session 1: 开始事务并查询数据tx1, err := db1.Begin()if err != nil {panic(err)}rows1, err := tx1.Query("SELECT * FROM employees")if err != nil {panic(err)}defer rows1.Close()fmt.Println("Session 1 - First Query:")for rows1.Next() {var id intvar name, department stringerr = rows1.Scan(&id, &name, &department)if err != nil {panic(err)}fmt.Println(id, name, department)}// Session 2: 插入数据并提交事务tx2, err := db2.Begin()if err != nil {panic(err)}_, err = tx2.Exec("INSERT INTO employees (id, name, department) VALUES (?, ?, ?)", 2, "Bob", "Engineering")if err != nil {panic(err)}err = tx2.Commit()if err != nil {panic(err)}// Session 1: 再次查询数据rows2, err := tx1.Query("SELECT * FROM employees")if err != nil {panic(err)}defer rows2.Close()fmt.Println("Session 1 - Second Query:")for rows2.Next() {var id intvar name, department stringerr = rows2.Scan(&id, &name, &department)if err != nil {panic(err)}fmt.Println(id, name, department)}tx1.Commit()
}

总结

通过设置合适的事务隔离级别和使用行级锁,可以有效地解决在MySQL中同时执行SELECT和UPDATE操作同一张表时遇到的脏读、不可重复读和幻读问题。这不仅确保了数据的一致性,还提高了系统的并发处理能力。理解并正确使用这些机制,是保证数据库操作正确性和效率的关键。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • TCP/IP 网络模型详解(二)之输入网址到网页显示的过程
  • 嵌入式初学-C语言-八
  • Computer Analysis and Visualisation CITS2401
  • [C#]调用本地摄像头录制视频并保存
  • Python 环境管理大师:Virtualenv
  • redis存储结构
  • 蓝牙网关厂家推荐:北京桂花网科技有限公司
  • CSS 创建:从入门到精通
  • 力扣高频SQL 50题(基础版)第二十六题
  • 3.5.2、查找和排序算法-查找算法
  • 【区块链】浅谈面向小白的关于BlockChain那些事
  • 监控网络丢包脚本
  • C#中的泛型约束:如何利用泛型约束来提高代码的类型安全性和灵活性?
  • Git(分布式版本控制系统)、Gitlab、分支、分支冲突
  • 苦学Opencv的第十四天:人脸检测和人脸识别
  • 《网管员必读——网络组建》(第2版)电子课件下载
  • create-react-app做的留言板
  • DOM的那些事
  • E-HPC支持多队列管理和自动伸缩
  • HTTP 简介
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • java第三方包学习之lombok
  • Linux下的乱码问题
  • React Transition Group -- Transition 组件
  • VirtualBox 安装过程中出现 Running VMs found 错误的解决过程
  • vue--为什么data属性必须是一个函数
  • 当SetTimeout遇到了字符串
  • 道格拉斯-普克 抽稀算法 附javascript实现
  • 浅析微信支付:申请退款、退款回调接口、查询退款
  • 树莓派 - 使用须知
  • 我看到的前端
  • 运行时添加log4j2的appender
  • 智能合约开发环境搭建及Hello World合约
  • 大数据全解:定义、价值及挑战
  • 小白应该如何快速入门阿里云服务器,新手使用ECS的方法 ...
  • ​软考-高级-信息系统项目管理师教程 第四版【第14章-项目沟通管理-思维导图】​
  • ![CDATA[ ]] 是什么东东
  • # Redis 入门到精通(八)-- 服务器配置-redis.conf配置与高级数据类型
  • #Spring-boot高级
  • (1)虚拟机的安装与使用,linux系统安装
  • (2024,RWKV-5/6,RNN,矩阵值注意力状态,数据依赖线性插值,LoRA,多语言分词器)Eagle 和 Finch
  • (C#)Windows Shell 外壳编程系列9 - QueryInfo 扩展提示
  • (C语言)求出1,2,5三个数不同个数组合为100的组合个数
  • (Matlab)使用竞争神经网络实现数据聚类
  • (二)基于wpr_simulation 的Ros机器人运动控制,gazebo仿真
  • (二)什么是Vite——Vite 和 Webpack 区别(冷启动)
  • (附源码)ssm智慧社区管理系统 毕业设计 101635
  • (图)IntelliTrace Tools 跟踪云端程序
  • ******IT公司面试题汇总+优秀技术博客汇总
  • . NET自动找可写目录
  • .net 4.0发布后不能正常显示图片问题
  • .net core webapi 部署iis_一键部署VS插件:让.NET开发者更幸福
  • .Net Core中Quartz的使用方法
  • .NET DevOps 接入指南 | 1. GitLab 安装
  • .NET 中小心嵌套等待的 Task,它可能会耗尽你线程池的现有资源,出现类似死锁的情况