事物隔离级别——幻读(Phantom Read)
程序员文章站
2022-06-14 15:30:34
...
数据库事务隔离级别设置为READ-UNCOMMITTED(未提交读)并重启MySQL服务。
场景:Tom的老婆工作在银行部门,她时常通过银行内部系统查看Tom的工资卡消费记录。2019年5月的某一天,她查询到Tom当月工资卡的总消费额
select sum(amount) from record where card_id='6226090219290000' and date_format(create_time,'%Y-%m')='2019-05'
为80元,Tom的老婆非常吃惊,心想“老公真是太节俭了,嫁给他真好!”,而Tom此时正好在外面胡吃海塞后在收银台买单,消费1000元,即新增了一条1000元的消费记录并提交了事务,沉浸在幸福中的老婆查询了Tom当月工资卡消费明细
select amount from record where card_id='6226090219290000' and date_format(create_time,'%Y-%m')='2019-05'
一探究竟,可查出的结果竟然发现有一笔1000元的消费,Tom的老婆瞬间怒气冲天,外卖订购了一个大号的榴莲,傍晚降临,Tom生活在了水深火热之中,只感到膝盖针扎的痛…
分析:上述情况即为幻读,两个并发的事务,“事务A:获取事务B消费记录”、“事务B:添加了新的消费记录”,事务A获取事务B消费记录时数据多出了一条。
创建如下两个表:
create table account(
id int(36) primary key comment '主键',
card_id varchar(16) unique comment '卡号',
name varchar(8) not null comment '姓名',
balance float(10,2) default 0 comment '余额'
)engine=innodb;
insert into account (id,card_id,name,balance) values (1,'6226090219290000','Tom',3000);
银行记录表:
create table record(
id int(36) primary key comment '主键',
card_id varchar(16) comment '卡号',
amount float(10,2) comment '金额',
create_time date comment '消费时间'
)engine=innodb;
insert into record (id,card_id,amount,create_time) values (1,'6226090219290000',37,'2019-05-01');
insert into record (id,card_id,amount,create_time) values (2,'6226090219290000',43,'2019-05-07');
妻子银行后台查询代码
import java.sql.*;
public class Bank {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://127.0.0.1:3306/test";
connection = DriverManager.getConnection(url, "root", "root");
connection.setAutoCommit(false);
statement = connection.createStatement();
String sql = "select sum(amount) total from record where card_id='6226090219290000' and date_format(create_time,'%Y-%m')='2019-05'";
resultSet = statement.executeQuery(sql);
if(resultSet.next()) {
System.out.println("总额:"+resultSet.getDouble("total"));
}
Thread.sleep(30000);//30秒后查询2019年5月消费明细
sql="select amount from record where card_id='6226090219290000' and date_format(create_time,'%Y-%m')='2019-05'";
resultSet = statement.executeQuery(sql);
System.out.println("消费明细:");
while(resultSet.next()) {
double amount = resultSet.getDouble("amount");
System.out.println(amount);
}
connection.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放资源
}
}
}
tom消费记录:
import java.sql.*;
public class Husband {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
try {
double sum=1000;//消费金额
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://127.0.0.1:3306/test";
connection = DriverManager.getConnection(url, "root", "root");
connection.setAutoCommit(false);
statement = connection.createStatement();
String sql = "update account set balance=balance-"+sum+" where card_id='6226090219290000'";
statement.executeUpdate(sql);
sql = "insert into record (id,card_id,amount,create_time) values (3,'6226090219290000',"+sum+",'2019-05-19');";
statement.executeUpdate(sql);
connection.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放资源
}
}
}
演示流程:
先执行Bank类中main方法——>再执行Husband类中main方法——>观察Bank类中main方法输出。
第一步:tom妻子用银行内部系统,查询tom账户的记录。
第二步:tom消费了1000元。
第三步:30秒后,tom妻子查询明细记录如下:
推荐阅读
-
MySQL数据库的隔离级别之可重复读为什么能够有效防止幻读现象的出现
-
数据库事务隔离级别 与 脏读、不可重复读、幻读
-
数据库的隔离级别以及脏读,不可重复读和幻读
-
仅此一文让你明白事务隔离级别、脏读、不可重复读、幻读
-
理解数据库事务隔离级别以及脏读, 不可重复读, 幻读
-
Mysql事务以及四中隔离级别实例2以及InnoDB如何解决当时读的幻读问题
-
MySQL事务隔离级别与相关示例(脏读、不可重复读、幻读)
-
数据库事务隔离级别-- 脏读、幻读、不可重复读
-
事务的隔离级别和幻读问题
-
数据库基础知识ACID,隔离级别RC,RR,RU,SERIALIZABLE,Phantom Rows幻读,解决幻读,脏读dirty read