MySQL同一事务中先更新在查询,结果与数据库中不一致问题

本文深入探讨MySQL事务的特性,特别是同一事务中先更新后查询的行为。通过实例展示,在一个未提交的事务中,更新后的数据如何仅对当前事务可见,但对其他事务不可见,解释了这种现象背后的原因及与脏读的区别。

MySQL同一事务中先更新在查询,结果与数据库中不一致问题

问题背景:

java 代码在同一个事务中先执行update 再执行select 发现select 的数据是update之后的结果,此时事务还没有提交,直接查询数据库,数据库的数据还没有发生变更.

问题结论:

在数据库事务是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行,这是一种机制,用以维护数据库的完整性。在你这个事务单元中存在两个操作,一个是update,一个是select;你前面的update执行成功了,再执行select肯定能够查询到前面update的数据,因为你是在一个单元中。但是另外一个事务(即另一个单元)则不能select到这个update的数据,因为你update的数据还没提交,只在该单元中有效;

注意:此处要与脏读区分开,脏读所指的读到了未提交的数据(这是两个事务操作,而本文的问题前提是在同一个事务中)。也就是说,一个事务正在对一条记录做修改,在这个事务完成并提交之前,这条数据是处于待定状态的(可能提交也可能回滚),这时,第二个事务来读取这条没有提交的数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被称为脏读。

问题验证:

mysql> SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |  --可重复读
+-----------------+
1 row in set
mysql> select * from shop_item_stock where id=145106957739827200;   -- 先查询数据此时shop=11111
+--------------------+-------+---------------+------------+----------+---------------------+---------------------+
| id                 | shop  | productsaleid | type       | stock    | createtime          | ts                  |
+--------------------+-------+---------------+------------+----------+---------------------+---------------------+
| 145106957739827200 | 11111 |    1201152020 | SHOP_STOCK | 84964984 | 2017-12-06 10:03:14 | 2019-09-20 10:43:01 |
+--------------------+-------+---------------+------------+----------+---------------------+---------------------+
1 row in set

mysql> begin; -- 开启事务
Query OK, 0 rows affected

mysql> update shop_item_stock set shop = 110 where id=145106957739827200; -- 更新shop为110
Query OK, 1 row affected

mysql> select * from shop_item_stock where id=145106957739827200;  --事务未提交查询 发现shop正是刚修改的110   若此时你新开一个事务查询会发现shop还是11111
+--------------------+------+---------------+------------+----------+---------------------+---------------------+
| id                 | shop | productsaleid | type       | stock    | createtime          | ts                  |
+--------------------+------+---------------+------------+----------+---------------------+---------------------+
| 145106957739827200 |  110 |    1201152020 | SHOP_STOCK | 84964984 | 2017-12-06 10:03:14 | 2019-10-18 09:57:04 |
+--------------------+------+---------------+------------+----------+---------------------+---------------------+
1 row in set

mysql> commit; --提交事务
Query OK, 0 rows affected

mysql> select * from shop_item_stock where id=145106957739827200; --查询结果是110
+--------------------+------+---------------+------------+----------+---------------------+---------------------+
| id                 | shop | productsaleid | type       | stock    | createtime          | ts                  |
+--------------------+------+---------------+------------+----------+---------------------+---------------------+
| 145106957739827200 |  110 |    1201152020 | SHOP_STOCK | 84964984 | 2017-12-06 10:03:14 | 2019-10-18 09:57:04 |
+--------------------+------+---------------+------------+----------+---------------------+---------------------+
1 row in set

mysql> --若此时新开一个事务再查询shop也是110   即同一事务中操作的数据在可见在不同事务中不可见

 

 

### 可能的原因及解决方法 程序执行 SQL 查询后返回的 ID 数据库实际 ID 一致问题可能由以下原因引起,并且有相应的解决方法: #### 1. **SQL 注入问题** 如果程序未对用户输入进行适当的验证和转义,可能导致恶意用户通过 SQL 注入篡改查询条件。例如,在引用中提到的联合查询场景中,如果直接将用户输入拼接到 SQL 查询字符串中,可能会导致意外的结果[^1]。 - **解决方案**: 使用参数化查询或预编译语句来防止 SQL 注入。例如,在 Python 中可以使用 SQLAlchemy 或者原生的 `execute` 方法: ```python # 使用参数化查询 cursor.execute("SELECT * FROM users WHERE id = %s", (user_input,)) ``` #### 2. **数据类型匹配** 如果查询条件中的 ID 类型数据库表定义的类型一致,可能会导致查询结果错误。例如,数据库中的 ID 列是整数类型,而用户输入的是字符串类型。 - **解决方案**: 确保查询条件的数据类型数据库表定义一致。可以通过显式转换来避免问题: ```sql -- 在 SQL 中强制转换 SELECT * FROM users WHERE CAST(id AS INTEGER) = ? ``` #### 3. **查询逻辑错误** 查询语句可能包含逻辑错误,例如误用 `LIMIT`、`OFFSET` 或者其他聚合函数,导致返回的 ID 正确。 - **解决方案**: 检查 SQL 查询逻辑是否符合预期。确保有多余的过滤条件或者排序规则影响了结果。例如: ```sql -- 正确的查询示例 SELECT id, name FROM users WHERE id = ? ORDER BY id ASC LIMIT 1; ``` #### 4. **缓存问题** 某些情况下,程序可能依赖于缓存机制(如 Redis 或 Memcached),如果缓存中的数据数据库中的实际数据一致,也可能导致返回的 ID 错误。 - **解决方案**: 确保缓存机制数据库保持同步。在更新数据库时,及时刷新或清除相关缓存。 #### 5. **并发问题** 在高并发场景下,多个请求可能同时修改同一数据,导致查询结果一致。 - **解决方案**: 使用事务机制保证数据一致性。例如,在 MySQL 中可以启用事务支持: ```sql START TRANSACTION; -- 执行查询 COMMIT; ``` #### 6. **数据库连接池配置问题** 如果数据库连接池配置当,可能会导致查询结果混乱。例如,连接池中的连接未正确释放,或者连接池大小足。 - **解决方案**: 检查数据库连接池的配置,确保连接池大小合理,并且每次查询后正确释放连接。 --- ### 示例代码 以下是一个完整的示例,展示如何通过参数化查询事务机制解决上述问题: ```python import mysql.connector # 连接数据库 conn = mysql.connector.connect( host="localhost", user="root", password="password", database="testdb" ) cursor = conn.cursor() try: # 开启事务 conn.start_transaction() # 参数化查询 query = "SELECT id, name FROM users WHERE id = %s" cursor.execute(query, (user_input,)) result = cursor.fetchone() if result: print(f"ID: {result[0]}, Name: {result[1]}") else: print("No result found.") # 提交事务 conn.commit() except Exception as e: # 回滚事务 conn.rollback() print(f"Error: {e}") finally: # 关闭连接 cursor.close() conn.close() ``` ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值