事务、悲观锁、乐观锁、Redis锁 功能与区别对比文档

一、核心概念与功能总览

类型 核心定义 核心解决问题 适用场景
事务 数据库层面保证操作的原子性、一致性、隔离性、持久性(ACID),分为本地事务/分布式事务 单库操作的原子性(要么全提交,要么全回滚),解决数据操作的一致性问题 单库的增删改操作(如订单创建)
悲观锁 数据库层面的排他性锁(如for update),认为并发冲突一定会发生,提前锁定资源 单库高并发下的更新丢失问题,保证同一时间只有一个事务修改目标数据 单库高频写场景(如库存扣减)
乐观锁 基于版本号/时间戳的无锁机制,认为并发冲突概率低,仅在提交时校验冲突 低并发场景下的并发修改冲突,避免锁带来的性能损耗 读多写少场景(如商品信息更新)
Redis锁 基于Redis的分布式锁,利用SETNX等命令实现跨服务的资源抢占 分布式系统(多实例/多服务)下的资源互斥,解决跨服务的并发修改问题 分布式场景(如分布式任务调度)

二、详细功能与能力边界

1. 事务(以MySQL为例)

核心功能

  • 本地事务:保证单个数据库内多个操作的原子性,提交时全成功,异常时全回滚;
  • 隔离级别控制:通过读未提交、读已提交、可重复读、串行化,解决脏读、不可重复读、幻读问题;
  • 分布式事务(如Seata):扩展至跨库/跨服务场景,保证全局原子性。

能解决的问题

  • 单库操作的原子性(如“创建订单+扣减库存”在单库内要么都成,要么都败);
  • 控制多事务间的隔离性,避免读写冲突导致的数据不一致;
  • 保证事务执行后的持久性(数据落地,不会因宕机丢失)。

无法解决的问题

  • 本地事务无法解决跨库/跨服务的分布式原子性问题;
  • 仅靠事务无法解决高并发下的“更新丢失”(需配合悲观锁/乐观锁);
  • 事务隔离级别越高,性能损耗越大,无法兼顾高并发与高性能;
  • 长事务会占用数据库连接,导致连接池耗尽,且锁持有时间过长引发性能问题。

Go代码示例(MySQL事务)

package main

import (
"database/sql"
"errors"
"fmt"
_ "github.com/go-sql-driver/mysql"
)

// 初始化数据库连接
func initDB() (*sql.DB, error) {
dsn := "root:123456@tcp(127.0.0.1:3306)/test_db?charset=utf8mb4&parseTime=True&loc=Local"
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
}
// 测试连接
if err := db.Ping(); err != nil {
return nil, err
}
return db, nil
}

// 事务示例:创建订单+扣减库存,保证原子性
func createOrderWithTx(db *sql.DB, orderID int, goodsID int, num int) error {
// 开启事务
tx, err := db.Begin()
if err != nil {
return err
}
defer func() {
// 异常回滚
if r := recover(); r != nil {
tx.Rollback()
}
}()

// 1. 扣减库存
_, err = tx.Exec("UPDATE stock SET num = num - ? WHERE goods_id = ?", num, goodsID)
if err != nil {
tx.Rollback()
return err
}

// 2. 创建订单
_, err = tx.Exec("INSERT INTO `order` (id, goods_id, num) VALUES (?, ?, ?)", orderID, goodsID, num)
if err != nil {
tx.Rollback()
return err
}

// 校验库存(模拟业务异常)
var stockNum int
err = tx.QueryRow("SELECT num FROM stock WHERE goods_id = ?", goodsID).Scan(&stockNum)
if err != nil {
tx.Rollback()
return err
}
if stockNum < 0 {
tx.Rollback()
return errors.New("库存不足,事务回滚")
}

// 提交事务
return tx.Commit()
}

func main() {
db, err := initDB()
if err != nil {
fmt.Printf("初始化数据库失败:%v\n", err)
return
}
defer db.Close()

// 执行事务
err = createOrderWithTx(db, 1001, 1, 2)
if err != nil {
fmt.Printf("事务执行失败:%v\n", err)
} else {
fmt.Println("事务执行成功")
}
}

2. 悲观锁(以select ... for update行锁为例)

核心功能

  • 行级排他锁:锁定目标行数据,同一时间仅一个事务可持有锁;
  • 阻塞机制:其他事务的修改/加锁操作会被阻塞,直到锁释放;
  • 与事务配合:锁持有周期与事务一致,事务提交/回滚后释放锁。

能解决的问题

  • 单库高并发下的更新丢失问题(如库存扣减时避免多线程同时修改);
  • 保证事务内读取的数据是“独占”的,修改时不会被其他事务干扰;
  • 行锁粒度细,仅锁定目标行,对其他行无影响。

无法解决的问题

  • 无法解决分布式场景(多服务/多库)的并发问题;
  • 锁阻塞会降低并发性能,高并发下易导致线程等待;
  • 无索引时会升级为表锁,导致整张表读写阻塞;
  • 可能引发死锁(多事务加锁顺序不一致时);
  • 不影响普通select查询(其他事务可读取快照数据),无法控制“只读不写”的场景。

Go代码示例(MySQL悲观锁)

package main

import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)

// 悲观锁示例:扣减库存前加行锁,避免并发更新丢失
func deductStockWithPessimisticLock(db *sql.DB, goodsID int, num int) error {
// 开启事务
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()

// 1. 查询库存并加排他行锁(for update)
var stockNum int
err = tx.QueryRow("SELECT num FROM stock WHERE goods_id = ? FOR UPDATE", goodsID).Scan(&stockNum)
if err != nil {
return err
}

// 2. 校验库存
if stockNum < num {
return fmt.Errorf("库存不足,当前库存:%d,扣减数量:%d", stockNum, num)
}

// 3. 扣减库存
_, err = tx.Exec("UPDATE stock SET num = num - ? WHERE goods_id = ?", num, goodsID)
if err != nil {
return err
}

// 提交事务(释放锁)
return tx.Commit()
}

func main() {
db, err := initDB() // 复用上方initDB函数
if err != nil {
fmt.Printf("初始化数据库失败:%v\n", err)
return
}
defer db.Close()

// 执行悲观锁扣减库存
err = deductStockWithPessimisticLock(db, 1, 1)
if err != nil {
fmt.Printf("扣减库存失败:%v\n", err)
} else {
fmt.Println("扣减库存成功")
}
}

3. 乐观锁(以版本号version为例)

核心功能

  • 无锁机制:不主动加锁,仅在更新时通过版本号/时间戳校验;
  • 冲突检测:更新语句带版本条件(update ... where id=? and version=?),版本不匹配则更新失败;
  • 非阻塞:所有事务可同时读取数据,仅在提交时校验冲突。

能解决的问题

  • 低并发场景下的并发修改冲突,避免悲观锁的阻塞性能损耗;
  • 读多写少场景(如商品详情更新)的一致性保障,兼顾性能与数据正确性;
  • 无需占用数据库锁资源,对数据库连接无额外消耗。

无法解决的问题

  • 高并发写场景下会频繁更新失败,导致大量重试,反而降低性能;
  • 无法解决分布式场景的并发问题(跨服务版本号无法统一校验);
  • 仅能检测“修改冲突”,无法防止“读时数据被改”(如读取后、更新前数据被其他事务修改);
  • 不支持复杂的业务逻辑校验(如多表关联更新的冲突检测)。

Go代码示例(MySQL乐观锁)

package main

import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)

// 乐观锁示例:基于版本号更新库存
func updateStockWithOptimisticLock(db *sql.DB, goodsID int, num int) (bool, error) {
// 1. 查询当前库存和版本号
var (
stockNum int
version int
)
err := db.QueryRow("SELECT num, version FROM stock WHERE goods_id = ?", goodsID).Scan(&stockNum, &version)
if err != nil {
return false, err
}

// 2. 校验库存
if stockNum < num {
return false, fmt.Errorf("库存不足,当前库存:%d,扣减数量:%d", stockNum, num)
}

// 3. 乐观锁更新:版本号匹配才更新
result, err := db.Exec(
"UPDATE stock SET num = num - ?, version = version + 1 WHERE goods_id = ? AND version = ?",
num, goodsID, version,
)
if err != nil {
return false, err
}

// 4. 检查受影响行数,判断是否更新成功(版本号冲突则行数为0)
rowsAffected, err := result.RowsAffected()
if err != nil {
return false, err
}

return rowsAffected > 0, nil
}

func main() {
db, err := initDB() // 复用上方initDB函数
if err != nil {
fmt.Printf("初始化数据库失败:%v\n", err)
return
}
defer db.Close()

// 执行乐观锁更新(可循环重试,此处简化)
success, err := updateStockWithOptimisticLock(db, 1, 1)
if err != nil {
fmt.Printf("更新库存失败:%v\n", err)
} else if !success {
fmt.Println("库存更新冲突(版本号不匹配),请重试")
} else {
fmt.Println("库存更新成功")
}
}

4. Redis锁(以SETNX + EXPIRE为例,基于redigo库)

核心功能

  • 分布式互斥:利用SETNX命令实现跨服务的资源抢占,同一时间仅一个客户端持有锁;
  • 自动过期:通过EXPIRE设置锁超时,避免客户端宕机导致死锁;
  • 可重入/公平锁:扩展实现可重入(如Redisson)、公平锁等特性。

能解决的问题

  • 分布式系统(多实例/多服务)下的资源互斥(如分布式定时任务避免重复执行);
  • 跨库/跨服务的并发修改问题(如多服务操作同一缓存/数据库);
  • 性能高于数据库锁,支持高并发场景的分布式锁需求。

无法解决的问题

  • 无法保证100%可靠(如Redis主从切换时可能丢失锁);
  • 锁超时设置不合理时,可能导致“锁失效”(业务未执行完锁已释放);
  • 无法解决分布式事务的原子性问题(仅解决资源互斥,不保证操作回滚);
  • 依赖Redis可用性,Redis宕机则锁机制失效;
  • 不支持事务的ACID特性,仅能控制“谁能执行”,无法控制“执行结果是否一致”。

Go代码示例(Redis分布式锁)

package main

import (
"fmt"
"time"

"github.com/gomodule/redigo/redis"
)

// 初始化Redis连接池
func initRedisPool() *redis.Pool {
return &redis.Pool{
MaxIdle: 10,
IdleTimeout: 240 * time.Second,
Dial: func() (redis.Conn, error) {
conn, err := redis.Dial("tcp", "127.0.0.1:6379")
if err != nil {
return nil, err
}
// 密码认证(无密码则注释)
// if _, err := conn.Do("AUTH", "123456"); err != nil {
// conn.Close()
// return nil, err
// }
return conn, nil
},
TestOnBorrow: func(c redis.Conn, t time.Time) error {
_, err := c.Do("PING")
return err
},
}
}

// 获取Redis分布式锁(SET NX EX 原子操作,避免死锁)
func acquireRedisLock(conn redis.Conn, lockKey string, lockValue string, expire time.Duration) bool {
// SET key value NX EX seconds:仅当key不存在时设置,且自动过期
result, err := redis.String(conn.Do("SET", lockKey, lockValue, "NX", "EX", int(expire.Seconds())))
if err != nil || result != "OK" {
return false
}
return true
}

// 释放Redis分布式锁(校验value避免误删其他客户端的锁)
func releaseRedisLock(conn redis.Conn, lockKey string, lockValue string) error {
// 用Lua脚本保证原子性:先校验value,再删除
script := `
if redis.call('GET', KEYS[1]) == ARGV[1] then
return redis.call('DEL', KEYS[1])
else
return 0
end
`
_, err := redis.Int(conn.Do("EVAL", script, 1, lockKey, lockValue))
return err
}

// 分布式锁保护的业务逻辑:扣减库存
func deductStockWithRedisLock(redisPool *redis.Pool, db *sql.DB, goodsID int, num int) error {
lockKey := fmt.Sprintf("stock_lock_%d", goodsID)
lockValue := "unique_value_123" // 唯一值,避免误删锁
expire := 30 * time.Second // 锁超时时间

// 获取Redis连接
conn := redisPool.Get()
defer conn.Close()

// 1. 获取分布式锁
if !acquireRedisLock(conn, lockKey, lockValue, expire) {
return fmt.Errorf("获取分布式锁失败,其他客户端正在操作")
}
// 2. 释放锁(无论成功失败都释放)
defer releaseRedisLock(conn, lockKey, lockValue)

// 3. 执行业务逻辑(扣减库存,复用上方悲观锁/乐观锁逻辑)
success, err := updateStockWithOptimisticLock(db, goodsID, num)
if err != nil {
return err
}
if !success {
return fmt.Errorf("库存更新失败,版本号冲突")
}

return nil
}

func main() {
// 初始化Redis和DB连接
redisPool := initRedisPool()
defer redisPool.Close()

db, err := initDB()
if err != nil {
fmt.Printf("初始化数据库失败:%v\n", err)
return
}
defer db.Close()

// 执行分布式锁保护的库存扣减
err = deductStockWithRedisLock(redisPool, db, 1, 1)
if err != nil {
fmt.Printf("扣减库存失败:%v\n", err)
} else {
fmt.Println("扣减库存成功")
}
}

三、核心区别对比表

对比维度 事务 悲观锁 乐观锁 Redis锁
作用范围 单库(本地)/跨库(分布式) 单库 单库 分布式(跨服务/跨库)
实现层面 数据库层 数据库层 应用层(代码控制) 缓存层
并发策略 隔离控制 悲观(提前锁) 乐观(事后校验) 悲观(分布式抢占)
性能影响 隔离级别越高,性能越低 高并发下阻塞,性能较低 无阻塞,性能高 性能高,无数据库压力
原子性保障 支持(ACID) 不支持(仅锁资源) 不支持(仅校验冲突) 不支持(仅互斥)
死锁风险 低(数据库自动检测) 高(加锁顺序不当) 低(超时自动释放)
典型使用语法/API BEGIN/COMMIT/ROLLBACK select ... for update update ... where version=? SETNX/EXPIRE/Lua脚本

四、选型建议

  1. 单库原子性需求:优先用事务,高并发写加悲观锁,读多写少加乐观锁
  2. 分布式资源互斥:优先用Redis锁(高性能),高可靠性需求用Zookeeper锁;
  3. 分布式原子性需求:用Seata等分布式事务框架,配合Redis锁控制并发;
  4. 性能优先+低并发:选乐观锁;数据一致性优先+高并发:选悲观锁+事务
  5. 跨服务并发控制:必选Redis锁(或Zookeeper锁),不依赖数据库锁。