跳到主要内容数据库事务隔离级别与 Spring 传播行为深度解析 | 极客日志JavaPayjava
数据库事务隔离级别与 Spring 传播行为深度解析
深入解析数据库事务隔离级别(脏读、不可重复读、幻读)及 Spring 事务传播行为(REQUIRED、REQUIRES_NEW 等)。涵盖 ACID 原则、MVCC 实现、MySQL 锁机制(行锁、间隙锁)、性能测试数据及企业级实战案例(支付系统、批量处理)。提供事务监控配置、故障排查方法及最佳实践建议,帮助开发者避免数据不一致、死锁等问题,优化系统性能与稳定性。
鲜活7.9K 浏览 数据库事务隔离级别与 Spring 传播行为深度解析
1. 事务不是'要么全做,要么不做'那么简单
1.1 ACID 原则的真相
ACID 是事务的核心特性,但在不同数据库中的实现细节有所不同。
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 1;
ALTER TABLE account ADD CONSTRAINT balance_non_negative CHECK (balance >= 0);
SELECT * FROM account WHERE id = 1;
COMMIT;
代码清单 1:ACID 在 MySQL 中的实现
1.2 事务的'不可能三角'
事务设计中存在经典权衡:一致性 vs 隔离性 vs 性能,通常只能兼顾其中两项。
| 选择 | 结果 | 适用场景 |
|---|
| 强一致性 + 强隔离性 | 性能差 | 银行转账 |
| 强一致性 + 高性能 | 隔离性弱 | 读多写少 |
| 强隔离性 + 高性能 |
现实案例:支付系统初期使用 SERIALIZABLE,TPS 仅为 50。后续根据业务特点拆分不同场景使用不同隔离级别,TPS 提升至 2000。
2. 四种隔离级别深度解析
2.1 并发问题三兄弟
START TRANSACTION;
UPDATE users SET balance = balance - 100 WHERE id = 1;
START TRANSACTION;
SELECT balance FROM users WHERE id = 1;
COMMIT;
START TRANSACTION;
SELECT * FROM users WHERE id = 1;
UPDATE users SET name = 'Bob' WHERE id = 1;
COMMIT;
SELECT * FROM users WHERE id = 1;
COMMIT;
START TRANSACTION;
SELECT COUNT(*) FROM users WHERE age > 18;
INSERT INTO users(name, age) VALUES ('Charlie', 20);
COMMIT;
SELECT COUNT(*) FROM users WHERE age > 18;
COMMIT;
2.2 隔离级别对比
SELECT @@transaction_isolation;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 性能 | 实现机制 |
|---|
| READ UNCOMMITTED | ✅ 可能 | ✅ 可能 | ✅ 可能 | ⭐⭐⭐⭐⭐ | 无锁,直接读最新数据 |
| READ COMMITTED | ❌ 不可能 | ✅ 可能 | ✅ 可能 | ⭐⭐⭐⭐ | 语句级快照 |
| REPEATABLE READ | ❌ 不可能 | ❌ 不可能 | ✅ 可能 | ⭐⭐⭐ | 事务级快照 + 间隙锁 |
| SERIALIZABLE | ❌ 不可能 | ❌ 不可能 | ❌ 不可能 | ⭐ | 全表锁 + 范围锁 |
注意:MySQL 的 REPEATABLE READ 通过 Next-Key Locks 解决了大部分幻读问题。
2.3 MVCC:隔离级别的实现核心
多版本并发控制(MVCC)是理解隔离级别的关键机制。
public class MVCCRecord {
private long trxId;
private long rollPointer;
private Object data;
private boolean deleted;
}
public boolean isVisible(long readViewTrxId, long recordTrxId) {
if (recordTrxId == readViewTrxId) {
return !deleted;
}
if (recordTrxId < readViewTrxId) {
return isCommitted(recordTrxId) && !deleted;
}
return false;
}
3. 锁机制:事务隔离的基石
3.1 MySQL 锁类型详解
SELECT * FROM users WHERE id = 1 FOR UPDATE;
SELECT * FROM users WHERE age BETWEEN 20 AND 30 FOR UPDATE;
SELECT * FROM users WHERE id > 10 FOR UPDATE;
3.2 死锁分析与解决
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 2;
UPDATE account SET balance = balance + 100 WHERE id = 1;
@Transactional(timeout = 5)
public void transfer(Long from, Long to, BigDecimal amount) {
}
SHOW ENGINE INNODB STATUS;
@Service
public class AccountService {
@Transactional
public void transfer(Long from, Long to, BigDecimal amount) {
Long first = Math.min(from, to);
Long second = Math.max(from, to);
accountRepository.lockById(first);
accountRepository.lockById(second);
accountRepository.deduct(first, amount);
accountRepository.add(second, amount);
}
}
4. Spring 传播行为七剑客
4.1 传播行为定义
Spring 定义了 7 种传播行为,理解它们的关键在于事务的存在与否及新建策略。
public enum Propagation {
REQUIRED,
SUPPORTS,
MANDATORY,
REQUIRES_NEW,
NOT_SUPPORTED,
NEVER,
NESTED
}
4.2 实际工作流程
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager {
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
throw new IllegalTransactionStateException(
"Existing transaction found for transaction marked with propagation 'never'");
}
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
Object suspendedResources = suspend(transaction);
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(definition, null, false, newSynchronization, debugEnabled, suspendedResources);
}
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
return startTransaction(definition, transaction, debugEnabled, suspendedResources);
} catch (RuntimeException | Error ex) {
resume(transaction, suspendedResources);
throw ex;
}
}
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
if (useSavepointForNestedTransaction()) {
Object savepoint = createSavepoint();
return prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null, savepoint);
} else {
return startTransaction(definition, transaction, debugEnabled, null);
}
}
if (isValidateExistingTransaction()) {
}
prepareTransactionForPropagation(definition, transaction);
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}
}
4.3 实战测试
@SpringBootTest
@Slf4j
class PropagationTest {
@Autowired
private UserService userService;
@Autowired
private LogService logService;
@Test
void testRequiredPropagation() {
userService.createUserWithLog("张三");
}
@Test
void testRequiresNewPropagation() {
userService.createUserWithSeparateLog("李四");
}
}
@Service
class UserService {
@Transactional(propagation = Propagation.REQUIRED)
public void createUserWithLog(String name) {
userRepository.save(new User(name));
logService.saveLog("用户创建:" + name);
}
@Transactional(propagation = Propagation.REQUIRED)
public void createUserWithSeparateLog(String name) {
userRepository.save(new User(name));
logService.saveLogInNewTransaction("用户创建:" + name);
}
}
@Service
class LogService {
@Transactional(propagation = Propagation.REQUIRED)
public void saveLog(String message) {
logRepository.save(new Log(message));
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLogInNewTransaction(String message) {
logRepository.save(new Log(message));
}
}
5. 性能影响与优化
5.1 隔离级别性能测试
- MySQL 8.0.28
- 4 核 8GB
- 100 并发线程
- 10 万测试数据
| 隔离级别 | TPS | 平均响应时间 (ms) | 死锁次数 | CPU 使用率 |
|---|
| READ UNCOMMITTED | 3850 | 26 | 0 | 45% |
| READ COMMITTED | 3200 | 31 | 2 | 52% |
| REPEATABLE READ | 1850 | 54 | 5 | 65% |
| SERIALIZABLE | 420 | 238 | 0 | 85% |
5.2 传播行为性能测试
| 传播行为 | 事务数量 | 平均耗时 (ms) | 连接数 | 适用场景 |
|---|
| REQUIRED | 1 | 45 | 1 | 通用 |
| REQUIRES_NEW | 2 | 120 | 2 | 独立事务 |
| NESTED | 1 | 85 | 1 | 部分回滚 |
| NOT_SUPPORTED | 0 | 25 | 1 | 只读操作 |
REQUIRES_NEW 创建新连接,开销最大
NESTED 在 MySQL 中实际是 REQUIRED(不支持真嵌套)
- 无事务最快,但可能数据不一致
6. 企业级实战案例
6.1 电商订单支付系统
@Service
@Slf4j
public class PaymentService {
@Autowired
private AccountService accountService;
@Autowired
private OrderService orderService;
@Autowired
private TransactionTemplate transactionTemplate;
@Transactional(
isolation = Isolation.REPEATABLE_READ,
propagation = Propagation.REQUIRED,
rollbackFor = Exception.class,
timeout = 30
)
public PaymentResult processPayment(Long orderId, BigDecimal amount) {
Order order = orderService.validateOrder(orderId, amount);
inventoryService.reduceStock(order.getItems());
accountService.deduct(order.getUserId(), amount);
paymentRecordService.createRecord(orderId, amount);
orderService.updateStatus(orderId, OrderStatus.PAID);
messageService.sendPaymentSuccess(order.getUserId(), orderId);
return new PaymentResult(true, "支付成功");
}
public PaymentResult processPaymentOptimized(Long orderId, BigDecimal amount) {
PaymentContext context = validateAndReserve(orderId, amount);
CompletableFuture.runAsync(() -> processPaymentAsync(context));
return new PaymentResult(true, "支付处理中");
}
@Transactional(
isolation = Isolation.REPEATABLE_READ,
timeout = 5
)
private PaymentContext validateAndReserve(Long orderId, BigDecimal amount) {
Order order = orderService.lockOrder(orderId);
inventoryService.reserveStock(order.getItems());
accountService.freeze(order.getUserId(), amount);
return new PaymentContext(order, amount);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void processPaymentAsync(PaymentContext context) {
try {
accountService.deduct(context.getUserId(), context.getAmount());
inventoryService.commitReserve(context.getItems());
orderService.updateStatus(context.getOrderId(), OrderStatus.PAID);
paymentRecordService.createRecord(
context.getOrderId(), context.getAmount());
} catch (Exception e) {
compensationService.compensate(context);
throw e;
} finally {
transactionTemplate.execute(status -> {
messageService.sendPaymentEvent(context);
return null;
});
}
}
}
| 方案 | 平均耗时 | 锁持有时间 | 死锁概率 | 数据一致性 |
|---|
| 大事务 | 850ms | 850ms | 高 | 强一致 |
| 优化后 | 120ms | 50ms | 低 | 最终一致 |
6.2 批量数据处理
@Service
@Slf4j
public class BatchProcessService {
@Transactional
public void processAllUsers() {
List<User> users = userRepository.findAll();
for (User user : users) {
processUser(user);
}
}
public void processUsersInBatch() {
int page = 0;
int size = 100;
Page<User> userPage;
do {
userPage = userRepository.findAll(PageRequest.of(page, size));
processUserBatch(userPage.getContent());
page++;
} while (userPage.hasNext());
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void processUserBatch(List<User> users) {
for (User user : users) {
try {
processUser(user);
} catch (Exception e) {
log.error("处理用户失败:{}", user.getId(), e);
}
}
}
@Autowired
private TransactionTemplate transactionTemplate;
public void processWithProgrammaticTransaction() {
transactionTemplate.execute(status -> {
return null;
});
TransactionTemplate customTemplate = new TransactionTemplate();
customTemplate.setPropagationBehavior(Propagation.REQUIRES_NEW.value());
customTemplate.setIsolationLevel(Isolation.READ_COMMITTED.value());
customTemplate.setTimeout(30);
}
}
7. 监控与故障排查
7.1 事务监控配置
spring:
datasource:
hikari:
connection-test-query: SELECT 1
leak-detection-threshold: 30000
jpa:
open-in-view: false
properties:
hibernate:
generate_statistics: true
session.events.log.LOG_QUERIES_SLOWER_THAN_MS: 1000
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus,transactions
7.2 事务监控代码
@Component
@Slf4j
public class TransactionMonitor {
@Autowired
private PlatformTransactionManager transactionManager;
@Scheduled(fixedDelay = 30000)
public void monitorTransactions() {
if (transactionManager instanceof DataSourceTransactionManager) {
DataSourceTransactionManager dsTm = (DataSourceTransactionManager) transactionManager;
int active = getActiveTransactionCount();
int idle = getIdleConnectionCount();
if (active > 10) {
log.warn("活跃事务过多:{}", active);
}
if ((double) active / (active + idle) > 0.8) {
log.warn("连接池使用率过高:{}/{}", active, active + idle);
}
}
}
public void checkDeadlocks() {
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SHOW ENGINE INNODB STATUS")) {
if (rs.next()) {
String status = rs.getString("Status");
if (status.contains("LATEST DETECTED DEADLOCK")) {
log.error("检测到死锁:{}", status);
alertService.sendDeadlockAlert(status);
}
}
}
}
}
7.3 慢事务排查
SELECT * FROM information_schema.innodb_trx\G
SELECT * FROM information_schema.innodb_locks\G
SELECT * FROM information_schema.innodb_lock_waits\G
SHOW PROCESSLIST;
SELECT * FROM mysql.slow_log
WHERE start_time > NOW() - INTERVAL 1 HOUR
ORDER BY query_time DESC LIMIT 10;
SELECT trx_id, trx_started, TIMESTAMPDIFF(SECOND, trx_started, NOW()) as duration_seconds,
trx_state, trx_operation_state
FROM information_schema.innodb_trx
WHERE trx_state = 'RUNNING'
ORDER BY trx_started ASC;
8. 最佳实践总结
8.1 事务最佳实践
📜 第一条:合理选择隔离级别
- 查询用
READ_COMMITTED
- 支付用
REPEATABLE_READ
- 报表用
READ_UNCOMMITTED
- 特殊场景用
SERIALIZABLE
📜 第二条:正确使用传播行为
- 默认用
REQUIRED
- 独立操作用
REQUIRES_NEW
- 只读操作用
SUPPORTS
- 日志记录用
NOT_SUPPORTED
📜 第三条:控制事务粒度
- 事务要短小(<1 秒)
- 避免事务中 RPC 调用
- 批量操作要分页
- 及时提交事务
📜 第四条:做好监控告警
8.2 生产环境配置
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 5000
idle-timeout: 600000
max-lifetime: 1800000
leak-detection-threshold: 30000
transaction:
default-timeout: 30
rollback-on-commit-failure: true
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager tm = new DataSourceTransactionManager(dataSource);
tm.setDefaultTimeout(30);
tm.setNestedTransactionAllowed(true);
tm.setRollbackOnCommitFailure(true);
return tm;
}
}
9. 常见问题解决方案
9.1 事务不生效的 7 个原因
@Transactional
private void privateMethod() {
}
@Service
public class UserService {
public void createUser(User user) {
validateAndSave(user);
}
@Transactional
public void validateAndSave(User user) {
}
@Autowired
private UserService self;
public void createUserFixed(User user) {
self.validateAndSave(user);
}
}
@Transactional
public void saveData() throws Exception {
throw new Exception("业务异常");
}
@Transactional(rollbackFor = Exception.class)
public void saveData() throws Exception {
throw new Exception("业务异常");
}
9.2 死锁预防方案
@Service
public class AccountService {
@Transactional(isolation = Isolation.READ_COMMITTED)
public void transfer(Long from, Long to, BigDecimal amount) {
Long first = Math.min(from, to);
Long second = Math.max(from, to);
lockAccount(first);
lockAccount(second);
deduct(first, amount);
add(second, amount);
}
@Transactional
public boolean transferWithOptimisticLock(Long from, Long to, BigDecimal amount) {
int retry = 0;
while (retry < 3) {
Account fromAccount = accountRepository.findById(from).get();
Account toAccount = accountRepository.findById(to).get();
if (fromAccount.getBalance().compareTo(amount) < 0) {
throw new InsufficientBalanceException();
}
fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
toAccount.setBalance(toAccount.getBalance().add(amount));
fromAccount.setVersion(fromAccount.getVersion() + 1);
toAccount.setVersion(toAccount.getVersion() + 1);
try {
accountRepository.saveAll(Arrays.asList(fromAccount, toAccount));
return true;
} catch (ObjectOptimisticLockingFailureException e) {
retry++;
if (retry >= 3) {
throw new ConcurrentUpdateException("转账失败,请重试");
}
}
}
return false;
}
@Transactional(timeout = 5)
public void transferWithTimeout(Long from, Long to, BigDecimal amount) {
}
}
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online