面试题答案
一键面试事务的ACID特性
- 原子性(Atomicity):
- 事务中的所有操作要么全部成功提交,要么全部失败回滚,就像一个不可分割的原子一样。例如,在银行转账操作中,从账户A向账户B转账100元,这涉及从账户A扣除100元以及向账户B增加100元两个操作。这两个操作必须作为一个整体执行,如果其中一个操作失败(比如账户A余额不足),整个转账事务应回滚,保证数据的一致性,不会出现账户A钱扣了但账户B没收到的情况。
- 一致性(Consistency):
- 事务执行前后,数据库的完整性约束没有被破坏。例如,在上述银行转账例子中,转账前A + B的总金额等于转账后A + B的总金额,数据库的一致性得到维护。数据库中的各种约束(如主键约束、外键约束等)必须在事务结束后仍然成立。
- 隔离性(Isolation):
- 多个并发事务之间相互隔离,一个事务的执行不能被其他事务干扰。不同的事务隔离级别决定了这种隔离的程度。例如,在高并发场景下,多个用户同时进行转账操作,隔离性确保每个用户的操作相互不影响,不会出现数据混乱的情况。
- 持久性(Durability):
- 一旦事务提交,对数据库的修改就是永久性的。即使系统崩溃或出现其他故障,已提交的事务对数据的修改也不会丢失。例如,银行转账事务提交后,无论后续系统发生什么情况,账户A和账户B的余额变化都是确定且持久的。
不同事务隔离级别在并发操作数据库时的问题及避免方法
- 读未提交(Read Uncommitted):
- 问题:会出现脏读(Dirty Read),即一个事务可以读取到另一个未提交事务的数据。例如,事务T1更新了一条记录,但未提交,事务T2此时读取了这条更新后的数据,如果T1随后回滚,T2读取到的数据就是无效的“脏数据”。
- 避免方法:提高事务隔离级别到读已提交或更高。
- 读已提交(Read Committed):
- 问题:会出现不可重复读(Non - Repeatable Read),在一个事务内多次读取同一数据,由于其他事务的修改并提交,导致每次读取结果不一致。例如,事务T1读取了一条记录,事务T2对该记录进行了修改并提交,T1再次读取时得到不同的结果。
- 避免方法:将事务隔离级别提高到可重复读。
- 可重复读(Repeatable Read):
- 问题:会出现幻读(Phantom Read),在一个事务内执行相同的查询,由于其他事务插入新数据并提交,导致查询结果集的行数发生变化。例如,事务T1查询符合某条件的记录集,事务T2插入了符合该条件的新记录并提交,T1再次执行相同查询时,结果集中多了T2插入的记录。
- 避免方法:将事务隔离级别提高到串行化。
- 串行化(Serializable):
- 问题:由于事务是串行执行的,并发性能极低,会严重影响系统的吞吐量。
- 避免方法:在实际应用中,只有在对数据一致性要求极高且并发量较小的场景下使用。一般可根据业务需求,选择较低的隔离级别以提高并发性能。
在Java代码中通过JDBC或Spring框架来管理事务
- 通过JDBC管理事务:
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; public class JdbcTransactionExample { public static void main(String[] args) { Connection connection = null; PreparedStatement pstmt1 = null; PreparedStatement pstmt2 = null; try { // 加载数据库驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 获取数据库连接 connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root", "password"); // 关闭自动提交,开启事务 connection.setAutoCommit(false); // 执行第一个SQL语句 String sql1 = "UPDATE accounts SET balance = balance - 100 WHERE account_id = 1"; pstmt1 = connection.prepareStatement(sql1); pstmt1.executeUpdate(); // 执行第二个SQL语句 String sql2 = "UPDATE accounts SET balance = balance + 100 WHERE account_id = 2"; pstmt2 = connection.prepareStatement(sql2); pstmt2.executeUpdate(); // 提交事务 connection.commit(); } catch (ClassNotFoundException | SQLException e) { if (connection != null) { try { // 回滚事务 connection.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); } finally { if (pstmt1 != null) { try { pstmt1.close(); } catch (SQLException e) { e.printStackTrace(); } } if (pstmt2 != null) { try { pstmt2.close(); } catch (SQLException e) { e.printStackTrace(); } } if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }
- 通过Spring框架管理事务:
- 基于XML配置:
- 配置数据源和事务管理器:
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mydb"/> <property name="username" value="root"/> <property name="password" value="password"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation - driven transaction - manager="transactionManager"/>
- 在Service层使用事务注解:
import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class AccountService { @Transactional public void transfer(int fromAccountId, int toAccountId, double amount) { // 转账逻辑,调用DAO层方法更新数据库 } }
- 基于Java配置:
import org.apache.commons.dbcp2.BasicDataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; @Configuration @ComponentScan(basePackages = "com.example") @EnableTransactionManagement public class AppConfig { @Bean public DataSource dataSource() { BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/mydb"); dataSource.setUsername("root"); dataSource.setPassword("password"); return dataSource; } @Bean public PlatformTransactionManager transactionManager() { return new DataSourceTransactionManager(dataSource()); } }
- Service层同样使用
@Transactional
注解进行事务管理:
import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class AccountService { @Transactional public void transfer(int fromAccountId, int toAccountId, double amount) { // 转账逻辑,调用DAO层方法更新数据库 } }
- Service层同样使用
- 基于XML配置: