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

打印 上一主题 下一主题

主题 909|帖子 909|积分 2727

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

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

在多事务环境下,大概会遇到以下问题:

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

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

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

  • READ UNCOMMITTED: 答应脏读、不可重复读和幻读。
  • READ COMMITTED: 防止脏读,但答应不可重复读和幻读。
  • REPEATABLE READ: 防止脏读和不可重复读,但答应幻读。
  • SERIALIZABLE: 防止脏读、不可重复读和幻读。
可以通过设置事务隔离级别来控制并发事务的行为。下面是设置事务隔离级别的示例:
  1. -- 设置事务隔离级别为 REPEATABLE READ
  2. SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
  3. START TRANSACTION;
  4. -- 你的查询和更新操作
  5. SELECT * FROM employees WHERE id = 1 FOR UPDATE;
  6. UPDATE employees SET name = 'new_name' WHERE id = 1;
  7. COMMIT;
复制代码
锁机制

MySQL的InnoDB存储引擎提供了行级锁,可以在查询时使用锁来确保数据的一致性。
  1. -- 设置事务隔离级别为 SERIALIZABLE
  2. SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
  3. START TRANSACTION;
  4. -- 你的查询和更新操作
  5. SELECT * FROM employees WHERE id = 1 FOR UPDATE;
  6. UPDATE employees SET name = 'new_name' WHERE id = 1;
  7. COMMIT;
复制代码
在上述示例中,使用FOR UPDATE语句对查询的行加锁,以防止其他事务在当前事务完成之前修改这些行。
现实案例分析

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

假设我们有一个名为employees的表,表结构如下:
  1. CREATE TABLE employees (
  2.     id INT PRIMARY KEY,
  3.     name VARCHAR(255),
  4.     department VARCHAR(255)
  5. );
复制代码
实行步调和SQL语句


  • 环境预备

    • 创建数据库和表。
    • 插入初始数据。

  1. -- 创建数据库CREATE DATABASE company_db;-- 选择数据库USE company_db;-- 创建表CREATE TABLE employees (
  2.     id INT PRIMARY KEY,
  3.     name VARCHAR(255),
  4.     department VARCHAR(255)
  5. );
  6. -- 插入初始数据INSERT INTO employees (id, name, department) VALUES (1, 'Alice', 'HR');
复制代码

  • Session 1: 开始事务,查询employees表。
  1. -- Session 1
  2. START TRANSACTION;
  3. SELECT * FROM employees;
复制代码

  • Session 2: 插入一行数据到employees表并提交事务。
  1. -- Session 2
  2. START TRANSACTION;
  3. INSERT INTO employees (id, name, department) VALUES (2, 'Bob', 'Engineering');
  4. COMMIT;
复制代码

  • Session 1: 再次查询employees表。
  1. -- Session 1
  2. 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和隔离级别参数,验证查询结果是否符合预期。
验证组合


  • autocommit=ON,隔离级别=REPEATABLE-READ: Session 1可否在T3时刻看到Session 2提交的数据?

    • 答:能。

  • autocommit=OFF,隔离级别=REPEATABLE-READ: Session 1可否在T3时刻看到Session 2提交的数据?

    • 答:不能。

  • autocommit=ON,隔离级别=READ-COMMITTED: Session 1可否在T3时刻看到Session 2提交的数据?

    • 答:能。

  • autocommit=OFF,隔离级别=READ-COMMITTED: Session 1可否在T3时刻看到Session 2提交的数据?

    • 答:能。

代码示例

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

  1. import pymysql
  2. # 连接数据库
  3. conn1 = pymysql.connect(host='localhost', user='root', password='password', db='company_db', autocommit=False)
  4. conn2 = pymysql.connect(host='localhost', user='root', password='password', db='company_db', autocommit=True)
  5. try:
  6.     with conn1.cursor() as cursor1, conn2.cursor() as cursor2:
  7.         # Session 1: 开始事务并查询数据
  8.         cursor1.execute("START TRANSACTION;")
  9.         cursor1.execute("SELECT * FROM employees;")
  10.         result1 = cursor1.fetchall()
  11.         print("Session 1 - First Query:", result1)
  12.         # Session 2: 插入数据并提交事务
  13.         cursor2.execute("INSERT INTO employees (id, name, department) VALUES (2, 'Bob', 'Engineering');")
  14.         cursor2.execute("COMMIT;")
  15.         # Session 1: 再次查询数据
  16.         cursor1.execute("SELECT * FROM employees;")
  17.         result2 = cursor1.fetchall()
  18.         print("Session 1 - Second Query:", result2)
  19. finally:
  20.     conn1.close()
  21.     conn2.close()
复制代码
C语言代码示例

  1. #include <mysql/mysql.h>
  2. #include <stdio.h>
  3. void finish_with_error(MYSQL *con) {
  4.     fprintf(stderr, "%s\n", mysql_error(con));
  5.     mysql_close(con);
  6.     exit(1);
  7. }
  8. int main() {
  9.     MYSQL *con1 = mysql_init(NULL);
  10.     MYSQL *con2 = mysql_init(NULL);
  11.     if (con1 == NULL || con2 == NULL) {
  12.         fprintf(stderr, "mysql_init() failed\n");
  13.         exit(1);
  14.     }
  15.     if (mysql_real_connect(con1, "localhost", "root", "password", "company_db", 0, NULL, 0) == NULL ||
  16.         mysql_real_connect(con2, "localhost", "root", "password", "company_db", 0, NULL, 0) == NULL) {
  17.         finish_with_error(con1);
  18.         finish_with_error(con2);
  19.     }
  20.     // Session 1: 开始事务并查询数据
  21.     if (mysql_query(con1, "START TRANSACTION") ||
  22.         mysql_query(con1, "SELECT * FROM employees")) {
  23.         finish_with_error(con1);
  24.     }
  25.     MYSQL_RES *result1 = mysql_store_result(con1);
  26.     if (result1 == NULL) {
  27.         finish_with_error(con1);
  28.     }
  29.     MYSQL_ROW row;
  30.     printf("Session 1 - First Query:\n");
  31.     while ((row = mysql_fetch_row(result1))) {
  32.         printf("%s %s %s\n", row[0], row[1], row[2]);
  33.     }
  34.     mysql_free_result(result1);
  35.    
  36. // Session 2: 插入数据并提交事务
  37.     if (mysql_query(con2, "START TRANSACTION") ||
  38.         mysql_query(con2, "INSERT INTO employees (id, name, department) VALUES (2, 'Bob', 'Engineering')") ||
  39.         mysql_query(con2, "COMMIT")) {
  40.         finish_with_error(con2);
  41.     }
  42.     // Session 1: 再次查询数据
  43.     if (mysql_query(con1, "SELECT * FROM employees")) {
  44.         finish_with_error(con1);
  45.     }
  46.     MYSQL_RES *result2 = mysql_store_result(con1);
  47.     if (result2 == NULL) {
  48.         finish_with_error(con1);
  49.     }
  50.     printf("Session 1 - Second Query:\n");
  51.     while ((row = mysql_fetch_row(result2))) {
  52.         printf("%s %s %s\n", row[0], row[1], row[2]);
  53.     }
  54.     mysql_free_result(result2);
  55.     mysql_close(con1);
  56.     mysql_close(con2);
  57.     return 0;
  58. }
复制代码
Go语言代码示例

  1. package main
  2. import (
  3.     "database/sql"
  4.     "fmt"
  5.     _ "github.com/go-sql-driver/mysql"
  6. )
  7. func main() {
  8.     db1, err := sql.Open("mysql", "root:password@tcp(localhost:3306)/company_db?autocommit=false")
  9.     if err != nil {
  10.         panic(err)
  11.     }
  12.     defer db1.Close()
  13.     db2, err := sql.Open("mysql", "root:password@tcp(localhost:3306)/company_db?autocommit=true")
  14.     if err != nil {
  15.         panic(err)
  16.     }
  17.     defer db2.Close()
  18.     // Session 1: 开始事务并查询数据
  19.     tx1, err := db1.Begin()
  20.     if err != nil {
  21.         panic(err)
  22.     }
  23.     rows1, err := tx1.Query("SELECT * FROM employees")
  24.     if err != nil {
  25.         panic(err)
  26.     }
  27.     defer rows1.Close()
  28.     fmt.Println("Session 1 - First Query:")
  29.     for rows1.Next() {
  30.         var id int
  31.         var name, department string
  32.         err = rows1.Scan(&id, &name, &department)
  33.         if err != nil {
  34.             panic(err)
  35.         }
  36.         fmt.Println(id, name, department)
  37.     }
  38.     // Session 2: 插入数据并提交事务
  39.     tx2, err := db2.Begin()
  40.     if err != nil {
  41.         panic(err)
  42.     }
  43.     _, err = tx2.Exec("INSERT INTO employees (id, name, department) VALUES (?, ?, ?)", 2, "Bob", "Engineering")
  44.     if err != nil {
  45.         panic(err)
  46.     }
  47.     err = tx2.Commit()
  48.     if err != nil {
  49.         panic(err)
  50.     }
  51.     // Session 1: 再次查询数据
  52.     rows2, err := tx1.Query("SELECT * FROM employees")
  53.     if err != nil {
  54.         panic(err)
  55.     }
  56.     defer rows2.Close()
  57.     fmt.Println("Session 1 - Second Query:")
  58.     for rows2.Next() {
  59.         var id int
  60.         var name, department string
  61.         err = rows2.Scan(&id, &name, &department)
  62.         if err != nil {
  63.             panic(err)
  64.         }
  65.         fmt.Println(id, name, department)
  66.     }
  67.     tx1.Commit()
  68. }
复制代码
总结

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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

西河刘卡车医

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表