幻读通常发生在范围查询中,下面通过一个简单的例子来说明幻读现象:
假设有一个名为 employees
的表,其中包含员工的ID和部门ID:
CREATE TABLE employees (id INT,department_id INT,name VARCHAR(255)
);
现在有两个并发事务 A 和 B:
-
事务 A 开始执行,它查询部门ID为 5 的所有员工记录:
BEGIN; SELECT * FROM employees WHERE department_id = 5;
假设查询结果显示部门5目前有 3 名员工。
-
事务 B 开始执行,它向
employees
表中插入了一个新的员工记录,该员工属于部门 5:BEGIN; INSERT INTO employees (id, department_id, name) VALUES (4, 5, 'New Employee'); COMMIT;
-
事务 A 再次执行相同的查询,以检查是否有新的员工加入部门 5:
SELECT * FROM employees WHERE department_id = 5;
这时,事务 A 发现部门 5 现在有 4 名员工,包括事务 B 刚刚插入的新员工。尽管事务 A 在两次查询之间没有修改任何数据,但它看到了“幻影”记录,这就是幻读。
为了解决这个问题,可以采取以下措施:
-
提高隔离级别:将事务 A 的隔离级别设置为
SERIALIZABLE
:BEGIN; SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; SELECT * FROM employees WHERE department_id = 5;
在这个隔离级别下,事务 A 会锁定查询范围内的所有记录,事务 B 将无法插入新记录,直到事务 A 提交或回滚。
-
使用悲观锁:在事务 A 的第一次查询时使用
FOR UPDATE
锁定所有符合条件的记录:BEGIN; SELECT * FROM employees WHERE department_id = 5 FOR UPDATE;
这样,事务 B 将无法插入新记录,直到事务 A 提交或回滚。
请注意,这些解决方案可能会影响数据库的并发性能。在实际应用中,需要根据业务需求和性能要求来选择合适的解决方案。