数据库存数据时,逻辑上防重了为啥还会出现重复记录?
在很多异常情况下,比如高并发、网络糟糕的时候,数据库里偶尔会出现重复的记录。
假如现在有一张书籍表,结构类似这样
+----+--------------+
| id | name |
+----+--------------+
| 1 | 世界简史 |
+----+--------------+
在异常情况下,可能会出现下面这样的记录
+----+--------------+
| id | name |
+----+--------------+
| 1 | 世界简史 |
| 2 | 人类简史 |
| 3 | 人类简史 |
+----+--------------+
但是,想了想,自己在处理相关数据的时候也加了判重的相关逻辑,比如,新增时当图书 name 相同时,会提示图书重复而返回。
初次遇到这个情况的时候,感觉有点摸不着头脑,后面想了想,还是理清了,其实这和数据库的事务隔离级别有一定关系。
先简单说下数据库事务的 4 个隔离级别,然后重现下上述问题,最后说说解决办法。
1 数据库事务的 4 个隔离级别
1.1 未提交读
顾名思义,当事务隔离级别处于这个设置的时候,不同事务能读取其它事务中未提交的数据。
便于说明,我开了两个客户端(A 以及 B),并设置各自的隔离级别为未提交读。(并没有全局设置)
设置隔离级别命令
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
好了,开始。
Client A
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-UNCOMMITTED |
+------------------------+
1 row in set (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from books;
+----+--------------+
| id | name |
+----+--------------+
| 1 | 世界简史 |
+----+--------------+
1 row in set (0.00 sec)
mysql> insert into books(name) values('人类简史');
Query OK, 1 row affected (0.01 sec)
mysql> select * from books;
+----+--------------+
| id | name |
+----+--------------+
| 1 | 世界简史 |
| 4 | 人类简史 |
+----+--------------+
2 rows in set (0.00 sec)
当 A 中的事务没有关闭的时候,我们去 B 中看下数据
Client B
mysql> SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-UNCOMMITTED |
+------------------------+
1 row in set (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from books;
+----+--------------+
| id | name |
+----+--------------+
| 1 | 世界简史 |
| 4 | 人类简史 |
+----+--------------+
2 rows in set (0.00 sec)
B 中可以读取 A 未提交的数据,所谓未提交读就是这样。
最后,记得把各个事务提交。
Client A & Client B
mysql> commit;