确保事务的原子性、一致性、隔离性和持久性
- 原子性:
- 使用
Connection
的setAutoCommit(false)
方法开启事务,将所有相关操作包含在一个事务中。例如:
Connection conn = DriverManager.getConnection(url, username, password);
try {
conn.setAutoCommit(false);
// 批量插入主表数据
PreparedStatement insertMainStmt = conn.prepareStatement("INSERT INTO main_table (column1, column2) VALUES (?,?)");
for (MainTableData data : mainTableDataList) {
insertMainStmt.setString(1, data.getColumn1());
insertMainStmt.setString(2, data.getColumn2());
insertMainStmt.addBatch();
}
insertMainStmt.executeBatch();
// 根据主表插入结果批量更新关联表
// 获取主表插入生成的主键(如果有自增主键)
ResultSet generatedKeys = insertMainStmt.getGeneratedKeys();
List<Long> mainTableIds = new ArrayList<>();
while (generatedKeys.next()) {
mainTableIds.add(generatedKeys.getLong(1));
}
PreparedStatement updateRelatedStmt = conn.prepareStatement("UPDATE related_table SET related_column =? WHERE main_table_id =?");
for (int i = 0; i < mainTableIds.size(); i++) {
updateRelatedStmt.setString(1, "new_value");
updateRelatedStmt.setLong(2, mainTableIds.get(i));
updateRelatedStmt.addBatch();
}
updateRelatedStmt.executeBatch();
conn.commit();
} catch (SQLException e) {
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
} finally {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
- 一致性:
- 保证业务逻辑正确,通过数据库约束(如外键约束)和应用程序逻辑来确保数据一致性。在事务中,先插入主表数据,确保主表数据符合业务规则,再根据主表结果更新关联表,且更新逻辑也要符合业务要求。
- 隔离性:
- 设置合适的事务隔离级别。通过
Connection
的setTransactionIsolation(int level)
方法设置,如conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED)
。不同隔离级别有不同的特点和适用场景:
TRANSACTION_READ_UNCOMMITTED
:最低隔离级别,可能读取到未提交的数据(脏读)。
TRANSACTION_READ_COMMITTED
:只能读取到已提交的数据,可避免脏读,但可能出现不可重复读。
TRANSACTION_REPEATABLE_READ
:可避免脏读和不可重复读,但可能出现幻读。
TRANSACTION_SERIALIZABLE
:最高隔离级别,完全串行化执行事务,可避免所有并发问题,但性能较低。
- 持久性:
- 依赖于数据库自身的持久化机制。一旦事务提交成功,数据库会将修改持久化到存储设备(如磁盘),即使系统崩溃,已提交的数据也不会丢失。
高并发场景下可能面临的问题及解决方案
- 并发访问冲突:
- 问题:多个并发事务同时操作相同数据,可能导致数据竞争和不一致。
- 解决方案:
- 乐观锁:在更新数据时,通过版本号或时间戳机制,先检查数据是否被其他事务修改。例如,表结构中添加
version
字段,更新语句为UPDATE table SET column =?, version = version + 1 WHERE id =? AND version =?
,如果更新行数为0,则表示数据已被其他事务修改,需要重新读取数据并操作。
- 悲观锁:在读取数据时,使用
SELECT... FOR UPDATE
语句锁定数据行,其他事务在该事务提交或回滚前无法修改这些数据。例如PreparedStatement selectStmt = conn.prepareStatement("SELECT * FROM main_table WHERE id =? FOR UPDATE");
- 性能问题:
- 问题:高并发时,事务竞争锁资源,可能导致性能下降。
- 解决方案:
- 优化事务隔离级别:根据业务需求,选择合适的隔离级别,避免使用过高隔离级别导致不必要的性能损耗。例如,如果业务允许一定程度的不可重复读,可选择
TRANSACTION_READ_COMMITTED
隔离级别。
- 分库分表:将数据分散到多个数据库或表中,减少单个数据库或表的并发访问压力。例如,按照一定规则(如哈希、范围等)将数据分配到不同的数据库实例或表中。
- 使用连接池:使用数据库连接池(如HikariCP、C3P0等)管理数据库连接,提高连接复用率,减少连接创建和销毁的开销。