数据库的事务隔离级别
一.读未提交
读未提交是指当一个事务在执行过程中,若另一个事务修改了同一条数据,但没有提交,仍然会被当前数据读取到被更改但未提交的新值。此时可能发生脏读。
二.读提交
读提交是指当一个事务在执行过程中,若另一个事务修改了数据并且提交了修改。此时提交后的数据会被当前数据读取到。此时会发生不可重复读。
三.可重复读
可重复读是指当一个事务在执行过程中,不管读同一条数据多少次,读到的数据和第一次读取的值保持一致。此时可能发生幻读,即另一个事务插入了新数据,但没有被当前事务读取到。
四.可序列化
可序列化是指所有事务顺序执行,此时可避免幻读。
通过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); int count = ps.executeUpdate(); 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恢复执行完后,它才能完成事务,或者超时抛出异常。
总结
数据库的隔离级别是很重要的特性,在不同场合使用不同的隔离级别会有不同的用处。大多数情况下使用的是可重复读。通过编程来验证隔离级别,可以更好地加深对知识的理解。