Contents
  1. 1. 数据库的事务隔离级别
  2. 2. 建表语句
  3. 3. 程序代码
  4. 4. 读未提交
  5. 5. 读提交
  6. 6. 可重复读
  7. 7. 可序列化
  8. 8. 总结

数据库的事务隔离级别

一.读未提交
读未提交是指当一个事务在执行过程中,若另一个事务修改了同一条数据,但没有提交,仍然会被当前数据读取到被更改但未提交的新值。此时可能发生脏读。
二.读提交
读提交是指当一个事务在执行过程中,若另一个事务修改了数据并且提交了修改。此时提交后的数据会被当前数据读取到。此时会发生不可重复读。
三.可重复读
可重复读是指当一个事务在执行过程中,不管读同一条数据多少次,读到的数据和第一次读取的值保持一致。此时可能发生幻读,即另一个事务插入了新数据,但没有被当前事务读取到。
四.可序列化
可序列化是指所有事务顺序执行,此时可避免幻读。

通过JDBC对数据库的访问,可验证数据库的事务隔离级别。

建表语句

1
2
3
4
5
6
7
8
CREATE TABLE Orders
(
O_id INT AUTO_INCREMENT,
OrderPrice DOUBLE(11, 2) NOT NULL,
Customer VARCHAR(50) NULL,
CONSTRAINT `PRIMARY`
PRIMARY KEY (O_id)
);

插入一条数据
1 1000 Bush 4

程序代码

编写两个Java程序,开启两个事务对MySQL数据库进行访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.sql.*;
public class JdbcTest {
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost/database2";
static final String USER = "root";
static final String PASS = "1234";
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
try {
Class.forName(JDBC_DRIVER);
System.out.println("connecting...");
connection = DriverManager.getConnection(DB_URL, USER, PASS);
connection.setAutoCommit(false);
statement = connection.createStatement();
String sql = "select * from Orders where O_id = ?";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setInt(1,1);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
System.out.println(rs.getInt(4));
}
rs = ps.executeQuery();
while (rs.next()) {
System.out.println(rs.getInt(4));
}
rs.close();
connection.commit();
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.sql.*;
public class JdbcTestTwo {
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost/database2?useSSL=false";
static final String USER = "root";
static final String PASS = "1234";
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
try {
Class.forName(JDBC_DRIVER);
System.out.println("connecting...");
connection = DriverManager.getConnection(DB_URL, USER, PASS);
connection.setAutoCommit(false);
statement = connection.createStatement();
String sql = "UPDATE Orders SET col = 2 WHERE O_id = ?";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setInt(1,1);
// ResultSet rs = ps.executeQuery();
// while (rs.next()) {
// System.out.println(rs.getInt(4));
// }
int count = ps.executeUpdate();
// while (rs.next()) {
// System.out.println(rs.getInt(4));
// }
// rs.close();
connection.commit();
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

在JdbcTest中对数据库表的同一条数据进行了两次查询,只需要在两次查询之间加一个断点,当程序暂停时,运行JdbcTest2,就可以观察到数据库中的数据变化是否符合隔离级别的定义。

读未提交

将MySQL数据库全局隔离级别设置为读未提交的命令:
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

1.运行JdbcTest,第一次查询出数据列col为4,暂停程序;
2.运行JdbcTest2,执行为update语句之后,暂停,不提交;
3.继续运行JdbcTest,第二次查询出数据列已经变为2;

读提交

将MySQL数据库全局隔离级别设置为读提交的命令:
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;

1.运行JdbcTest,第一次查询出数据列col为4,暂停程序;
2.运行JdbcTest,第一次查询出数据列为2,暂停程序,不提交;
3.继续运行JdbcTest,第二次查询出数据列仍然为4;

若第二步不暂停直接提交,则第三步查询出的数据将为2;

可重复读

将MySQL数据库全局隔离级别设置为可重复读的命令:
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;

1.运行JdbcTest,第一次查询出数据列col为4,暂停;
2.运行JdbcTest2,直接提交,将数据修改为2;
3.运行JdbcTest,读出数据依然为2。

可序列化

将MySQL数据库全局隔离级别设置为可重复读的命令:
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;

1.运行JdbcTest,第一次查询出数据列col为4,暂停;
2.运行JdbcTest2,将一直被阻塞,直到JdbcTest恢复执行完后,它才能完成事务,或者超时抛出异常。

总结

数据库的隔离级别是很重要的特性,在不同场合使用不同的隔离级别会有不同的用处。大多数情况下使用的是可重复读。通过编程来验证隔离级别,可以更好地加深对知识的理解。

Contents
  1. 1. 数据库的事务隔离级别
  2. 2. 建表语句
  3. 3. 程序代码
  4. 4. 读未提交
  5. 5. 读提交
  6. 6. 可重复读
  7. 7. 可序列化
  8. 8. 总结