脏读,不可重复读,幻读
MySQL事务隔离级别:
在介绍脏读,不可重复读,幻读现象之前,我们先来了解MySQL的事务隔离级别,因为脏读,不可重复读,幻读等现象都是由数据库里的事务隔离级别来决定是否可能发生的。
在MySQL里共有四个隔离级别,分别是:Read uncommttied(可以读取未提交数据)、Read committed(可以读取已提交数据)、Repeatable read(可重复读)、Serializable(可串行化)。
在MySQL数据库里,默认的事务隔离级别是Repeatable read(可重复读)。
使用select @@tx_isolation; 命令可以查看MySQL默认的事务隔离级别:
每个事务隔离级别会导致的数据现象:
但是这里有一点需要注意的是数据库的默认引擎是InnoDB在使用InnoDB引擎下,即便设定的事务隔离级别是Repeatable read,也不会出现数据幻读现象。
-原因:MySQL InnoDB存储引擎,实现的是基于多版本的并发控制协议——MVCC (Multi-Version Concurrency Control) 所以在Repeatable Read (RR)隔离级别下不存在幻读。
脏读现象:
在默认的事务隔离级别下,我们是无法读取到未提交的数据的,在能够读取到未提交数据的事务隔离级别下,才会出现脏读现象。脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据(Dirty Data),依据脏数据所做的操作可能是不正确的。
简而言之会出现脏读现象就是因为用户能够读取到未提交到数据里的数据,也即是无效的数据,然后对这些无效的脏数据进行了操作,所以这些操作都是无效或者错误的。
用言语来描述可能有点抽象、不好理解,下面我们打开两个MySQL客户端,来进行脏读现象的实验:
1.使用SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; 命令将两个MySQL客户端的事务隔离级别设定为Read uncommttied级别:
2.现在我们使用其中一个用户,往表格里插入一条数据,但是不执行commit命令,会发现另一个用户也能读取到这个未提交的数据:
这就是脏读现象,此现象称之为脏读因为读取出来的是无效数据,无效数据就等于是垃圾数据垃圾就当然就是脏的所以才叫脏读,而且如果我们以这个脏数据作为某些参数的话,必然会出现错误。
不可重复读现象:
在一个事务内,多次读同一个数据。在这个事务还没有结束时,另一个事务也访问该同一数据。那么,在第一个事务的两次读数据之间。由于第二个事务的修改,那么第一个事务读到的数据可能不一样,这样就发生了在一个事务内两次读到的数据是不一样的,因此称为不可重复读,即原始读取不可重复。
下面我们通过实验来看看不可重复读现象:
1.使用SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;命令将两个MySQL客户端的事务隔离级别设定为Read committed,这是因为要避免出现脏读现象:
2.现在我们使用其中一个用户,修改表格里的一条数据,但是不执行commit命令,会发现另一个用户不能读取到这个未提交的数据:
3.但是用户B执行commit命令后就不一样了:
不可重复读现象主要是指,在一个事务结束前(执行commit或rollback前),进行两次或多次读取同一个数据会出现不同的结果,所以称为不可重复读,因为重复读取就会出现这种数据不一致的情况。
幻读现象:
幻读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的“全部数据行”。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入“一行新数据”。那么,以后就会发生操作第一个事务的用户发现表中还存在没有修改的数据行,就好象发生了幻觉一样.一般解决幻读的方法是增加范围锁RangeS,锁定检索范围为只读,这样就避免了幻读。
幻读实际上和不可重复读有一点类似,都是第二次或多次查询的时候发现数据发生了变化,但是幻读侧重在表格里数据的数量上的变化,而且也是在事务生命周期内的查询上发生的变化,所以有一点要注意的是:严格意义上只有当用户A在事务生命周期内多次查询数据时数据发生变化,才能算得上是不可重复读或幻读现象,如果用户A在一个事务结束后接着在另一个新的事务里查询后发现数据发生了变化,那么这就不算是不可重复读或者幻读。
下面我们通过实验来看看幻读现象:
1.因为实际上在InnoDB存储引擎里的Repeatable read级别,已经解决了幻读现象,所以我们不需要更改隔离级别,仍旧使用Read committed级别即可:
2.现在我们使用其中一个用户,往表格了里表格里插入一条数据,但是不执行commit命令,同样的会发现另一个用户不能读取到这个未提交的数据:
3.当用户B commit之后用户A再查询就会发现多了一行数据:
然后用户B把这条数据删除了:
所以幻读称之为幻读的原因就是这,在一个事务生命周期内的查询上发生的表格数据数量上的变化,一下多了几行数据,一下又少了几行数据,跟活在梦一样,分分钟上下几百万。
不可重复读和幻读的区别:
不可重复读强调的是每次读取的是相同位置的数据,且该数据在另一个事务下被修改。注重的是修改。这个位置指的是哪一行、哪一个字段的数据。
幻读强调的是第二次读比第一次读取时,内容多了或者少了几行,注重的是新增和删除。
Serializable级别:
完全串行化的读,每次读都需要获得表级共享锁,读写相互会相互互斥,这样可以更好的解决数据一致性的问题,但是同样会大大的降低数据库的实际吞吐性能。所以该隔离级别因为损耗太大,一般很少在开发中使用,在此就不介绍了。
幻读的实际应用例题:
以上介绍的那些现象并不是数据库的BUG或者一些问题什么的,实际上有些业务需求就是需要这些数据现象来完成。例如幻读现象,在车票、电影票锁座等方面都有幻读的应用例子。
例如假设在购买车票的时候,你一开始明明查询只有三张票,但是一会再查一次就发现多了五张票,这就是幻读的现象。因为别人查询到这几张票的时候这几张票处于锁定状态,所以你就查询不到,如果对方放弃购买的话,这些票又重新回到出售界面了,所以你第二次查询的才会发现多了几张票,这就是幻读在实际生活中的一个应用例子。
现在我们编写一个简单的票务系统来演示幻读的应用:
图形界面代码示例:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.sql.SQLException;
import java.util.Vector;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
public class PiaoWuSystem extends JFrame {
private JTable table;
private JComboBox comboBox;
/**
* Launch the application
*
* @param args
*/
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
PiaoWuSystem frame = new PiaoWuSystem();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public Vector<String> cols = new Vector();
public Vector<Vector<String>> rows = new Vector();
public final JLabel label_2 = new JLabel();
PiaoWuDB piaoWuDB;
/**
* Create the frame
*/
/**
*
*/
int time = 100;
public PiaoWuSystem() {
super();
Thread thread = new Thread(new Runnable() {
public void run() {
while (true) {
label_2.setText("有效时间: " + time + " 秒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
time--;
if (time == 0) {
break;
}
}
try {
piaoWuDB.rollback();
rows.clear();
table.updateUI();
} catch (Exception e) {
// TODO: handle exception
}
}
});
thread.start();
setResizable(false);
setBounds(100, 100, 750, 389);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JLabel label = new JLabel();
label.setFont(new Font("微软雅黑", Font.BOLD, 24));
label.setHorizontalAlignment(SwingConstants.CENTER);
label.setText("凯哥学堂-票务实验");
getContentPane().add(label, BorderLayout.NORTH);
final JPanel panel = new JPanel();
panel.setLayout(null);
getContentPane().add(panel, BorderLayout.CENTER);
final JLabel label_1 = new JLabel();
label_1.setText("卧铺数量:");
label_1.setBounds(10, 14, 66, 18);
panel.add(label_1);
comboBox = new JComboBox();
comboBox.addItem("1");
comboBox.addItem("2");
comboBox.addItem("3");
comboBox.addItem("4");
comboBox.addItem("5");
comboBox.setBounds(82, 10, 66, 27);
panel.add(comboBox);
final JButton button = new JButton();
button.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent e) {
if (piaoWuDB != null) {
try {
piaoWuDB.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
time=30;
try {
piaoWuDB = new PiaoWuDB();
piaoWuDB.openTran();
Vector<Vector<String>> r = piaoWuDB.chaxun(Integer.parseInt(comboBox.getSelectedItem().toString()));
rows.clear();
rows.addAll(r);
table.updateUI();
} catch (Exception e2) {
e2.printStackTrace();
}
}
});
button.setText("查询票务");
button.setBounds(628, 9, 106, 28);
panel.add(button);
label_2.setForeground(new Color(255, 0, 0));
label_2.setFont(new Font("微软雅黑", Font.BOLD, 15));
label_2.setHorizontalAlignment(SwingConstants.CENTER);
label_2.setText("有效时间:");
label_2.setBounds(464, 10, 158, 27);
panel.add(label_2);
final JScrollPane scrollPane = new JScrollPane();
scrollPane.setBounds(10, 52, 724, 234);
panel.add(scrollPane);
cols.add("编号");
cols.add("类型");
cols.add("铺位");
table = new JTable(rows, cols);
scrollPane.setViewportView(table);
final JButton button_1 = new JButton();
button_1.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent e) {
try {
piaoWuDB.rollback();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
rows.clear();
table.updateUI();
}
});
button_1.setText("取消查询");
button_1.setBounds(628, 292, 106, 28);
panel.add(button_1);
final JButton button_2 = new JButton();
button_2.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent e) {
try {
piaoWuDB.commit();
} catch (SQLException e1) {
e1.printStackTrace();
}
rows.clear();
table.updateUI();
}
});
button_2.setText("确认购买");
button_2.setBounds(516, 292, 106, 28);
panel.add(button_2);
//
}
}
JDBC代码示例:
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Vector;
import org.zero01.DBManager.DBManager;
public class PiaoWuDB {
private Connection conn = null;
// 开启事务
public void openTran() throws SQLException {
conn = DBManager.getDBManager();
conn.setAutoCommit(false);
// 设置事务隔离等级
conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
}
public Vector<Vector<String>> chaxun(int num) throws SQLException {
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery("select * from piaowu where state=1 limit 0," + num);
Vector<Vector<String>> rows = new Vector<Vector<String>>();
while (rs.next()) {
st = conn.createStatement();
int rowNum = st.executeUpdate("UPDATE PIAOWU SET STATE=0 WHERE PID=" + rs.getInt(1) + " AND STATE=1");
if (rowNum >= 1) {
Vector<String> row = new Vector();
row.add(rs.getInt(1)+"");
row.add(rs.getString(2));
row.add(rs.getString(3));
rows.add(row);
}
}
return rows;
}
public void commit() throws SQLException {
conn.commit();
conn.close();
}
public void rollback() throws SQLException {
conn.rollback();
conn.close();
}
}
运行结果:
用户B想买五张票,但是查询的时候用户B只能查到编号为6、7、8、9的四张车票,因为其他票都被用户A锁定了:
然后第二次查询的时候用户B发现能够查到五张票了,这是因为用户A放弃了购买,这些票又重新回到出售界面了,这就是幻读的实际应用例子:
- Elasticsearch聚合 之 Histogram 直方图聚合
- Elasticsearch聚合 之 Date Histogram聚合
- Elasticsearch聚合 之 Terms
- Elasticsearch聚合初探——metric篇
- AngularJS API之$injector ---- 依赖注入
- AngularJS API之extend扩展对象
- AngularJS API之equal比较对象
- Elasticsearch之_default_—— 为索引添加默认映射
- Elasticsearch 动态映射——自动检测
- Elaticsearch REST API常用技巧
- C++拷贝构造函数
- 记录安装oracle的那些事(一)之oracle我很大
- Elasticsearch 连接查询
- 小程序加戏成功!变身游戏成新版微信最大主角,用户玩得不亦乐乎
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Python正则表达式(上)
- 附001.Nginx location语法规则
- 016.Nginx HTTPS
- 架构师写的BUG,非比寻常
- Python爬虫获取豆瓣TOP250电影详情
- docker容器常用命令
- MapReduce之自定义分区器Partitioner
- MySQL ORDER BY,GROUPBY 与各种JOIN
- (三) Mybatis动态SQL语句 - Titan的Mybatis系列学习笔记
- (二) 使用Mybatis完成CRUD操作 - Titan的Mybatis系列学习笔记
- 轻松使用纯css3打造有点意思的故障艺术(附React加强组件版)
- (一)Mybatis的入门 - Titan的Mybatis系列学习笔记
- Hadoop安装与环境配置入门
- Elasticsearch源码解析:环境搭建
- SpringBoot整合JWT认证机制实现接口鉴权