跳到主要内容

MySQL 中的事务是什么?

1. 基础概念类面试题

Q1: 什么是数据库事务?为什么需要事务?

考察点: 基础概念理解

参考答案: 事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。

为什么需要事务:

  • 保证数据一致性:避免数据处于中间状态
  • 并发控制:多用户同时访问时保证数据正确性
  • 故障恢复:系统崩溃时能恢复到一致状态

经典例子: 银行转账

// 转账操作必须保证原子性
func Transfer(from, to int, amount decimal.Decimal) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()

// 扣减转出账户
if err := debit(tx, from, amount); err != nil {
return err
}

// 增加转入账户
if err := credit(tx, to, amount); err != nil {
return err
}

return tx.Commit()
}

Q2: 详细解释 ACID 四大特性,并举例说明

考察点: 事务理论基础

原子性(Atomicity)

说明: 要么全部成功,要么全部失败。上图中账户B操作失败,整个事务回滚,账户A的扣减操作也被撤销。

一致性(Consistency)

数据库在事务执行前后都必须处于一致状态,不能违反任何完整性约束。

隔离性(Isolation)

并发执行的事务之间不能相互干扰:

持久性(Durability)

事务一旦提交,修改就是永久的,即使系统故障也不会丢失。

2. 隔离级别类面试题

Q3: MySQL 的四种隔离级别是什么?它们分别解决了什么问题?

考察点: 并发控制理解

隔离级别脏读不可重复读幻读
读未提交
读已提交
可重复读
串行化

Q4: 什么是脏读、不可重复读、幻读?请用时序图说明

考察点: 并发问题理解

脏读示例:

不可重复读示例:

幻读示例:

3. Go 语言实践类面试题

Q5: 在 Go 中如何正确使用数据库事务?

考察点: Go 语言数据库编程

func TransferMoney(db *sql.DB, fromID, toID int, amount decimal.Decimal) error {
// 开始事务
tx, err := db.Begin()
if err != nil {
return fmt.Errorf("开始事务失败: %w", err)
}

// 确保事务被正确处理
defer func() {
if p := recover(); p != nil {
tx.Rollback()
panic(p) // 重新抛出 panic
} else if err != nil {
tx.Rollback()
} else {
err = tx.Commit()
}
}()

// 检查转出账户余额
var balance decimal.Decimal
err = tx.QueryRow("SELECT balance FROM accounts WHERE id = ? FOR UPDATE", fromID).Scan(&balance)
if err != nil {
return fmt.Errorf("查询余额失败: %w", err)
}

if balance.LessThan(amount) {
return errors.New("余额不足")
}

// 扣减转出账户
_, err = tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, fromID)
if err != nil {
return fmt.Errorf("扣减失败: %w", err)
}

// 增加转入账户
_, err = tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, toID)
if err != nil {
return fmt.Errorf("转入失败: %w", err)
}

return nil
}

Q6: 如何在 Go 中设置事务隔离级别?

考察点: 事务控制细节

func QueryWithIsolation(db *sql.DB) error {
// 设置隔离级别
tx, err := db.BeginTx(context.Background(), &sql.TxOptions{
Isolation: sql.LevelRepeatableRead,
ReadOnly: false,
})
if err != nil {
return err
}
defer tx.Rollback()

// 执行查询
rows, err := tx.Query("SELECT * FROM accounts")
if err != nil {
return err
}
defer rows.Close()

// 处理结果...

return tx.Commit()
}

4. 高级面试题

Q7: MySQL 的可重复读是如何实现的?什么是 MVCC?

考察点: 底层实现原理

说明: MVCC通过为每行数据维护多个版本,结合ReadView机制实现不同事务看到不同的数据快照。

Q8: 什么情况下会发生死锁?如何避免?

考察点: 死锁问题处理

避免死锁的方法:

// 按固定顺序获取锁
func TransferAvoidDeadlock(fromID, toID int, amount decimal.Decimal) error {
// 确保按ID顺序锁定账户
if fromID > toID {
fromID, toID = toID, fromID
amount = amount.Neg()
}

tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()

// 按顺序锁定
_, err = tx.Exec("SELECT * FROM accounts WHERE id IN (?, ?) ORDER BY id FOR UPDATE",
fromID, toID)
if err != nil {
return err
}

// 执行转账逻辑...

return tx.Commit()
}

Q9: 在微服务架构中如何处理分布式事务?

考察点: 分布式系统设计

Go 实现示例:

type SagaTransaction struct {
steps []SagaStep
}

type SagaStep struct {
Action func() error
Compensate func() error
}

func (s *SagaTransaction) Execute() error {
var executedSteps []int

for i, step := range s.steps {
if err := step.Action(); err != nil {
// 执行补偿操作
for j := len(executedSteps) - 1; j >= 0; j-- {
s.steps[executedSteps[j]].Compensate()
}
return err
}
executedSteps = append(executedSteps, i)
}

return nil
}

5. 性能优化类面试题

Q10: 事务的性能考虑有哪些?

考察点: 性能优化能力

关键点:

  1. 事务大小控制
  2. 锁持有时间最小化
  3. 避免长事务
  4. 合理选择隔离级别
// ❌ 错误示例:长事务
func BadLongTransaction(orders []Order) error {
tx, _ := db.Begin()
defer tx.Rollback()

for _, order := range orders { // 可能有成千上万条
// 处理每个订单...
time.Sleep(100 * time.Millisecond) // 模拟复杂业务逻辑
}

return tx.Commit()
}

// ✅ 正确示例:批处理
func GoodBatchTransaction(orders []Order) error {
batchSize := 100

for i := 0; i < len(orders); i += batchSize {
end := i + batchSize
if end > len(orders) {
end = len(orders)
}

if err := processBatch(orders[i:end]); err != nil {
return err
}
}

return nil
}