58、事务的基本特性和隔离级别
**事务的基本特性**
在数据库系统中,事务是指一个或多个操作组成的逻辑单元,它们要么全部成功,要么全部失败。事务的基本特性包括原子性、持久性、一致性和隔离性。
### 原子性原子性是指事务中的所有操作要么全部执行,要么全部不执行。这意味着如果一个事务中有多个操作,任何一个操作的失败都会导致整个事务的失败。例如,在银行转账系统中,如果从一个账户转出金额,但由于网络问题导致转入账户未能收到金额,那么整个转账事务应该被回滚。
### 持久性持久性是指一旦事务执行完成后,其结果将永久保存,即使发生了系统崩溃或断电。例如,在银行转账系统中,如果从一个账户转出金额并成功到达转入账户,那么这笔金额应该被永久保存。
### 一致性一致性是指事务执行前和执行后的数据库状态保持一致。例如,在银行转账系统中,如果从一个账户转出金额并成功到达转入账户,那么两个账户的总金额应该相等。
### 隔离性隔离性是指多个事务之间不会互相干扰,各自执行完毕后才会合并结果。例如,在银行转账系统中,如果有多个用户同时进行转账操作,那么每个用户的转账操作应该独立于其他用户,不会因为其他用户的操作而受到影响。
**隔离级别**
隔离级别是指数据库系统在实现事务隔离性时采用的策略。常见的隔离级别包括读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。
###读未提交读未提交是最低级别的隔离级别。在这种级别下,一个事务可以读取另一个事务尚未提交的数据。例如,在银行转账系统中,如果有两个用户同时进行转账操作,那么第一个用户可能会读取到第二个用户尚未提交的转账金额。
###读已提交读已提交是比读未提交更高级别的隔离级别。在这种级别下,一个事务只能读取已经提交的数据。例如,在银行转账系统中,如果有两个用户同时进行转账操作,那么第一个用户只会读取到第二个用户已经提交的转账金额。
### 可重复读可重复读是比读已提交更高级别的隔离级别。在这种级别下,一个事务在执行过程中不会被其他事务干扰。例如,在银行转账系统中,如果有两个用户同时进行转账操作,那么第一个用户会读取到一个固定的数据快照,而第二个用户的操作不会影响到这个快照。
###串行化串行化是最高级别的隔离级别。在这种级别下,所有事务都必须按顺序执行。例如,在银行转账系统中,如果有两个用户同时进行转账操作,那么第一个用户会被强制等待到第二个用户完成其操作后才会继续。
**示例代码**
以下是使用 Java 和 MySQL 的示例代码,演示了不同隔离级别的效果:
javaimport java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; public class IsolationLevelExample { public static void main(String[] args) throws Exception { // 连接数据库 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", ""); // 设置隔离级别为读未提交 conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); try (PreparedStatement pstmt1 = conn.prepareStatement("SELECT * FROM user WHERE id =1"); PreparedStatement pstmt2 = conn.prepareStatement("UPDATE user SET balance = balance +100 WHERE id =1")) { // 第一个事务读取用户数据 ResultSet rs = pstmt1.executeQuery(); System.out.println("第一个事务读取到的用户数据:" + rs.getString(1)); // 第二个事务更新用户数据 pstmt2.executeUpdate(); // 第一个事务再次读取用户数据 rs = pstmt1.executeQuery(); System.out.println("第一个事务再次读取到的用户数据:" + rs.getString(1)); } conn.close(); } }
在这个示例代码中,我们设置了隔离级别为读未提交,两个事务都可以读取到尚未提交的数据。结果是第一个事务读取到的用户数据和第二个事务再次读取到的用户数据相同。
javaimport java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; public class IsolationLevelExample { public static void main(String[] args) throws Exception { // 连接数据库 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", ""); // 设置隔离级别为读已提交 conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); try (PreparedStatement pstmt1 = conn.prepareStatement("SELECT * FROM user WHERE id =1"); PreparedStatement pstmt2 = conn.prepareStatement("UPDATE user SET balance = balance +100 WHERE id =1")) { // 第一个事务读取用户数据 ResultSet rs = pstmt1.executeQuery(); System.out.println("第一个事务读取到的用户数据:" + rs.getString(1)); // 第二个事务更新用户数据 pstmt2.executeUpdate(); // 第一个事务再次读取用户数据 rs = pstmt1.executeQuery(); System.out.println("第一个事务再次读取到的用户数据:" + rs.getString(1)); } conn.close(); } }
在这个示例代码中,我们设置了隔离级别为读已提交,第二个事务更新用户数据后,第一个事务再次读取到的用户数据会改变。
javaimport java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; public class IsolationLevelExample { public static void main(String[] args) throws Exception { // 连接数据库 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", ""); // 设置隔离级别为可重复读 conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); try (PreparedStatement pstmt1 = conn.prepareStatement("SELECT * FROM user WHERE id =1"); PreparedStatement pstmt2 = conn.prepareStatement("UPDATE user SET balance = balance +100 WHERE id =1")) { // 第一个事务读取用户数据 ResultSet rs = pstmt1.executeQuery(); System.out.println("第一个事务读取到的用户数据:" + rs.getString(1)); // 第二个事务更新用户数据 pstmt2.executeUpdate(); // 第一个事务再次读取用户数据 rs = pstmt1.executeQuery(); System.out.println("第一个事务再次读取到的用户数据:" + rs.getString(1)); } conn.close(); } }
在这个示例代码中,我们设置了隔离级别为可重复读,第二个事务更新用户数据后,第一个事务再次读取到的用户数据不会改变。
javaimport java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; public class IsolationLevelExample { public static void main(String[] args) throws Exception { // 连接数据库 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", ""); // 设置隔离级别为串行化 conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); try (PreparedStatement pstmt1 = conn.prepareStatement("SELECT * FROM user WHERE id =1"); PreparedStatement pstmt2 = conn.prepareStatement("UPDATE user SET balance = balance +100 WHERE id =1")) { // 第一个事务读取用户数据 ResultSet rs = pstmt1.executeQuery(); System.out.println("第一个事务读取到的用户数据:" + rs.getString(1)); // 第二个事务更新用户数据 pstmt2.executeUpdate(); // 第一个事务再次读取用户数据 rs = pstmt1.executeQuery(); System.out.println("第一个事务再次读取到的用户数据:" + rs.getString(1)); } conn.close(); } }
在这个示例代码中,我们设置了隔离级别为串行化,第二个事务更新用户数据后,第一个事务再次读取到的用户数据会改变。
以上是使用 Java 和 MySQL 的示例代码,演示了不同隔离级别的效果。