04-锁机制完全指南

38 阅读11分钟

锁机制完全指南

文档类型: 锁机制深度解析
更新时间: 2025-10-28


目录

  1. 锁的基本概念
  2. 应用层锁
  3. 数据库锁
  4. 分布式锁
  5. 锁的性能优化
  6. 死锁问题
  7. 实战案例
  8. 面试高频题

1. 锁的基本概念

1.1 为什么需要锁?

并发问题示例:

线程1读取: balance = 100
线程2读取: balance = 100
线程1计算: balance = 100 + 50 = 150
线程2计算: balance = 100 - 30 = 70
线程1写入: balance = 150
线程2写入: balance = 70  ← 覆盖了线程1的结果

期望结果: 100 + 50 - 30 = 120
实际结果: 70 ❌

锁的作用:保证同一时刻只有一个线程/进程访问共享资源。

1.2 锁的分类

按粒度分:
├─ 表锁        (锁整张表)
├─ 行锁        (锁某一行)
├─ 页锁        (锁数据页)
└─ 间隙锁      (锁范围)

按模式分:
├─ 共享锁(S锁) (读锁,可并发读)
├─ 排他锁(X锁) (写锁,独占)
└─ 意向锁      (表级锁的标记)

按实现分:
├─ 悲观锁      (先加锁后操作)
├─ 乐观锁      (先操作后检查)
├─ 自旋锁      (忙等待)
└─ 互斥锁      (阻塞等待)

按范围分:
├─ 本地锁      (单机)
└─ 分布式锁    (跨机器)

1.3 锁兼容性矩阵

当前持有\请求  |  S锁  |  X锁
─────────────┼───────┼───────
   S锁        |  ✅   |  ❌
   X锁        |  ❌   |  ❌

说明:
✅ 兼容:可以并发
❌ 冲突:需要等待

示例:
- 多个事务可以同时持有S锁(并发读)
- 持有X锁时,其他事务不能获取任何锁(独占写)

2. 应用层锁

2.1 互斥锁(Mutex)

// PHP中的互斥锁
class MutexLock
{
    private $lock;
    
    public function __construct()
    {
        $this->lock = new \Swoole\Lock(SWOOLE_MUTEX);
    }
    
    public function execute(callable $callback)
    {
        $this->lock->lock();  // 获取锁
        
        try {
            return $callback();
        } finally {
            $this->lock->unlock();  // 释放锁
        }
    }
}

// 使用示例:保护临界区
$counter = 0;
$mutex = new MutexLock();

// 100个协程并发累加
for ($i = 0; $i < 100; $i++) {
    go(function () use ($mutex, &$counter) {
        for ($j = 0; $j < 100; $j++) {
            $mutex->execute(function () use (&$counter) {
                $counter++;  // 临界区
            });
        }
    });
}

// 结果:10000 ✅

2.2 读写锁(RWLock)

// 读写锁:读共享,写独占
class RWLock
{
    private $lock;
    
    public function __construct()
    {
        $this->lock = new \Swoole\Lock(SWOOLE_RWLOCK);
    }
    
    public function read(callable $callback)
    {
        $this->lock->lock_read();  // 读锁(可并发)
        try {
            return $callback();
        } finally {
            $this->lock->unlock();
        }
    }
    
    public function write(callable $callback)
    {
        $this->lock->lock();  // 写锁(独占)
        try {
            return $callback();
        } finally {
            $this->lock->unlock();
        }
    }
}

// 使用示例:缓存场景
class Cache
{
    private $data = [];
    private $rwlock;
    
    public function __construct()
    {
        $this->rwlock = new RWLock();
    }
    
    public function get(string $key)
    {
        return $this->rwlock->read(function () use ($key) {
            return $this->data[$key] ?? null;
        });
    }
    
    public function set(string $key, $value)
    {
        $this->rwlock->write(function () use ($key, $value) {
            $this->data[$key] = $value;
        });
    }
}

// 性能对比:
// 互斥锁:所有操作串行
// 读写锁:多个读操作可并发 ✅

2.3 自旋锁(Spinlock)

// 自旋锁:忙等待,不挂起线程
class SpinLock
{
    private $lock;
    
    public function __construct()
    {
        $this->lock = new \Swoole\Lock(SWOOLE_SPINLOCK);
    }
    
    public function execute(callable $callback)
    {
        while (!$this->lock->trylock()) {
            // 自旋等待(消耗CPU)
            usleep(1);
        }
        
        try {
            return $callback();
        } finally {
            $this->lock->unlock();
        }
    }
}

// 适用场景:
// ✅ 临界区执行时间极短(微秒级)
// ✅ CPU核心数充足
// ❌ 临界区执行时间长(浪费CPU)

2.4 信号量(Semaphore)

// 信号量:允许N个并发
class Semaphore
{
    private $sem;
    
    public function __construct(int $count)
    {
        $this->sem = new \Swoole\Lock(SWOOLE_SEM);
        // 初始化信号量为$count
    }
    
    public function acquire()
    {
        $this->sem->lock();  // P操作(-1)
    }
    
    public function release()
    {
        $this->sem->unlock();  // V操作(+1)
    }
}

// 使用示例:连接池
class ConnectionPool
{
    private $pool = [];
    private $semaphore;
    
    public function __construct(int $size)
    {
        $this->semaphore = new Semaphore($size);
        
        // 初始化连接池
        for ($i = 0; $i < $size; $i++) {
            $this->pool[] = $this->createConnection();
        }
    }
    
    public function getConnection()
    {
        $this->semaphore->acquire();  // 获取许可
        return array_pop($this->pool);
    }
    
    public function releaseConnection($conn)
    {
        $this->pool[] = $conn;
        $this->semaphore->release();  // 释放许可
    }
}

// 场景:限制并发数量
// 10个协程竞争3个连接
// 只有3个能同时执行,其他7个等待

2.5 条件变量(Condition Variable)

// 条件变量:线程间协作
class ConditionVariable
{
    private $mutex;
    private $cond;
    
    public function __construct()
    {
        $this->mutex = new \Swoole\Lock(SWOOLE_MUTEX);
        // PHP没有原生条件变量,用Channel模拟
        $this->cond = new \Swoole\Coroutine\Channel(1);
    }
    
    public function wait()
    {
        $this->mutex->unlock();  // 释放锁
        $this->cond->pop();      // 阻塞等待
        $this->mutex->lock();    // 重新获取锁
    }
    
    public function signal()
    {
        $this->cond->push(true);  // 唤醒一个等待者
    }
    
    public function broadcast()
    {
        // 唤醒所有等待者
        while (!$this->cond->isEmpty()) {
            $this->cond->push(true);
        }
    }
}

// 使用示例:生产者-消费者
class Queue
{
    private $items = [];
    private $notEmpty;
    private $notFull;
    private $capacity;
    
    public function __construct(int $capacity)
    {
        $this->capacity = $capacity;
        $this->notEmpty = new ConditionVariable();
        $this->notFull = new ConditionVariable();
    }
    
    public function put($item)
    {
        while (count($this->items) >= $this->capacity) {
            $this->notFull->wait();  // 队列满,等待
        }
        
        $this->items[] = $item;
        $this->notEmpty->signal();  // 通知消费者
    }
    
    public function get()
    {
        while (empty($this->items)) {
            $this->notEmpty->wait();  // 队列空,等待
        }
        
        $item = array_shift($this->items);
        $this->notFull->signal();  // 通知生产者
        return $item;
    }
}

3. 数据库锁

3.1 MySQL锁类型

表锁
-- 显式表锁
LOCK TABLES users READ;   -- 读锁(共享)
SELECT * FROM users;
UNLOCK TABLES;

LOCK TABLES users WRITE;  -- 写锁(排他)
UPDATE users SET name = 'John';
UNLOCK TABLES;

-- 特点:
-- ✅ 开销小
-- ❌ 粒度大,并发度低
-- 使用场景:MyISAM引擎
行锁
-- InnoDB行锁(隐式)

-- 共享锁(S锁)
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;

-- 排他锁(X锁)
SELECT * FROM users WHERE id = 1 FOR UPDATE;

-- 特点:
-- ✅ 粒度小,并发度高
-- ❌ 开销大
-- 使用场景:InnoDB引擎

3.2 InnoDB锁详解

记录锁(Record Lock)
-- 锁定某一行
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 只锁id=1这一行
COMMIT;
间隙锁(Gap Lock)
-- 锁定范围,防止幻读
BEGIN;
SELECT * FROM users WHERE id BETWEEN 5 AND 10 FOR UPDATE;
-- 锁定(5, 10)区间,防止插入id=6,7,8,9的记录
COMMIT;

-- 示例:
-- 现有记录: id = 3, 5, 10, 15
-- 执行: SELECT * FROM users WHERE id > 5 AND id < 15 FOR UPDATE;
-- 锁定间隙: (5, 10), (10, 15)
-- 阻止插入: id = 6,7,8,9,11,12,13,14
Next-Key Lock
-- Next-Key Lock = Record Lock + Gap Lock

-- 现有记录: 10, 20, 30
BEGIN;
SELECT * FROM users WHERE id >= 20 FOR UPDATE;

-- 锁定:
-- Record Lock: id = 20, 30
-- Gap Lock: (20, 30), (30, +∞)
-- Next-Key Lock: (10, 20], (20, 30], (30, +∞)

-- 阻止:
-- INSERT id = 21, 25, 31, 100... (间隙内的所有插入)
COMMIT;

3.3 锁等待与死锁

查看锁等待
-- 查看当前锁
SELECT * FROM information_schema.INNODB_LOCKS;

-- 查看锁等待
SELECT * FROM information_schema.INNODB_LOCK_WAITS;

-- 查看事务
SELECT * FROM information_schema.INNODB_TRX;

-- 实战:找出阻塞的事务
SELECT 
    waiting_trx_id AS '等待的事务',
    waiting_pid AS '等待的进程',
    blocking_trx_id AS '阻塞的事务',
    blocking_pid AS '阻塞的进程'
FROM (
    SELECT 
        r.trx_id AS waiting_trx_id,
        r.trx_mysql_thread_id AS waiting_pid,
        b.trx_id AS blocking_trx_id,
        b.trx_mysql_thread_id AS blocking_pid
    FROM information_schema.INNODB_LOCK_WAITS w
    JOIN information_schema.INNODB_TRX r ON r.trx_id = w.requesting_trx_id
    JOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_id
) t;

-- 杀死阻塞的进程
KILL blocking_pid;
死锁示例
-- 事务1
BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 1;  -- 锁A
-- 等待1秒
UPDATE account SET balance = balance + 100 WHERE id = 2;  -- 等待锁B
COMMIT;

-- 事务2
BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 2;  -- 锁B
UPDATE account SET balance = balance + 100 WHERE id = 1;  -- 等待锁A
COMMIT;

-- 结果:死锁检测
-- InnoDB检测到死锁,回滚其中一个事务
-- ERROR 1213 (40001): Deadlock found when trying to get lock
死锁日志
-- 查看最近一次死锁
SHOW ENGINE INNODB STATUS\G

-- 输出:
*** (1) TRANSACTION:
TRANSACTION 1234, ACTIVE 5 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
UPDATE account SET balance = balance + 100 WHERE id = 2

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 3 n bits 72 index PRIMARY of table `test`.`account`
trx id 1234 lock_mode X locks rec but not gap waiting

*** (2) TRANSACTION:
...

*** WE ROLL BACK TRANSACTION (1)

3.4 悲观锁 vs 乐观锁

悲观锁
// 悲观锁:先加锁后操作
class PessimisticLock
{
    public function transfer(int $fromId, int $toId, int $amount)
    {
        DB::beginTransaction();
        
        try {
            // 加排他锁
            $from = DB::table('accounts')
                ->where('id', $fromId)
                ->lockForUpdate()  // FOR UPDATE
                ->first();
            
            $to = DB::table('accounts')
                ->where('id', $toId)
                ->lockForUpdate()
                ->first();
            
            if ($from->balance < $amount) {
                throw new Exception('余额不足');
            }
            
            // 扣款
            DB::table('accounts')
                ->where('id', $fromId)
                ->decrement('balance', $amount);
            
            // 入账
            DB::table('accounts')
                ->where('id', $toId)
                ->increment('balance', $amount);
            
            DB::commit();
        } catch (Exception $e) {
            DB::rollBack();
            throw $e;
        }
    }
}

// 特点:
// ✅ 数据一致性强
// ❌ 并发性能低
// 适用:写操作多、冲突频繁
乐观锁
// 乐观锁:版本号机制
class OptimisticLock
{
    public function updateUser(int $userId, array $data)
    {
        // 读取数据和版本号
        $user = DB::table('users')
            ->where('id', $userId)
            ->first();
        
        $currentVersion = $user->version;
        
        // 更新(比较版本号)
        $affected = DB::table('users')
            ->where('id', $userId)
            ->where('version', $currentVersion)  // 版本号未变
            ->update([
                'name' => $data['name'],
                'version' => $currentVersion + 1  // 版本号+1
            ]);
        
        if ($affected === 0) {
            throw new Exception('数据已被修改,请重试');
        }
    }
}

// SQL实现
UPDATE users 
SET name = 'John', 
    version = version + 1 
WHERE id = 1 
  AND version = 10;  -- 当前版本号

-- affected_rows = 0 表示冲突

// 特点:
// ✅ 并发性能高
// ❌ 冲突时需要重试
// 适用:读操作多、冲突少

3.5 MVCC多版本并发控制

MVCC原理:
- 每行记录保存多个版本
- 事务根据事务ID选择对应版本
- 实现无锁读

记录结构:
┌────┬──────┬─────────┬────────┬───────────┐
│ id │ name │ trx_id  │roll_ptr│ delete_bit│
├────┼──────┼─────────┼────────┼───────────┤
│ 1  │ Tom  │  100NULL0      │ ← 当前版本
│ 1  │ John │   90    │  ptr   │    0      │ ← 历史版本11  │ Mike │   80    │  ptr   │    0      │ ← 历史版本2
└────┴──────┴─────────┴────────┴───────────┘

事务隔离级别与MVCC:

READ COMMITTED:
- 每次读取都生成新的Read View
- 可以读到其他事务已提交的修改

REPEATABLE READ:
- 事务开始时生成Read View
- 整个事务期间使用同一个Read View
- 读到的数据始终一致(可重复读)

示例:
T1: BEGIN; (trx_id = 100)
T2: BEGIN; (trx_id = 101)
T1: SELECT * FROM users WHERE id = 1;  -- name = 'Tom'
T2: UPDATE users SET name = 'John' WHERE id = 1;
T2: COMMIT;
T1: SELECT * FROM users WHERE id = 1;  
    -- REPEATABLE READ: name = 'Tom' (读旧版本)
    -- READ COMMITTED: name = 'John' (读新版本)

4. 分布式锁

4.1 为什么需要分布式锁?

单机锁的局限:
┌─────────┐   ┌─────────┐   ┌─────────┐
│ Server1 │   │ Server2 │   │ Server3 │
│  Lock A │   │  Lock A │   │  Lock A │
└─────────┘   └─────────┘   └─────────┘
     ▲             ▲             ▲
     │             │             │
  不能跨进程  →  需要分布式锁

分布式锁要求:
✅ 互斥性:任何时刻只有一个客户端持有锁
✅ 不会死锁:即使客户端崩溃,锁也能被释放
✅ 容错性:只要大部分节点正常,就能工作

4.2 Redis分布式锁

基础实现
class RedisLock
{
    private $redis;
    private $lockKey;
    private $lockValue;
    
    /**
     * 获取锁
     */
    public function acquire(string $key, int $ttl = 10): bool
    {
        $this->lockKey = "lock:{$key}";
        $this->lockValue = uniqid(gethostname(), true);
        
        // SET key value NX EX ttl
        // NX: 不存在才设置
        // EX: 过期时间
        $result = $this->redis->set(
            $this->lockKey,
            $this->lockValue,
            ['NX', 'EX' => $ttl]
        );
        
        return $result === true;
    }
    
    /**
     * 释放锁(Lua脚本保证原子性)
     */
    public function release(): bool
    {
        $script = <<<LUA
        if redis.call("get", KEYS[1]) == ARGV[1] then
            return redis.call("del", KEYS[1])
        else
            return 0
        end
        LUA;
        
        return $this->redis->eval(
            $script,
            [$this->lockKey, $this->lockValue],
            1
        ) === 1;
    }
}

// 使用
$lock = new RedisLock($redis);

if ($lock->acquire('order:123', 10)) {
    try {
        // 业务逻辑
        processOrder(123);
    } finally {
        $lock->release();
    }
} else {
    // 获取锁失败
    echo "系统繁忙,请稍后重试\n";
}
自动续期(看门狗)
class RedisLockWithWatchdog extends RedisLock
{
    private $watchdogRunning = false;
    
    public function acquire(string $key, int $ttl = 10): bool
    {
        if (parent::acquire($key, $ttl)) {
            $this->startWatchdog($ttl);
            return true;
        }
        return false;
    }
    
    /**
     * 看门狗:自动续期
     */
    private function startWatchdog(int $ttl)
    {
        $this->watchdogRunning = true;
        
        go(function () use ($ttl) {
            while ($this->watchdogRunning) {
                // 每TTL/3时间续期一次
                co::sleep($ttl / 3);
                
                if (!$this->watchdogRunning) {
                    break;
                }
                
                // 续期
                $script = <<<LUA
                if redis.call("get", KEYS[1]) == ARGV[1] then
                    return redis.call("expire", KEYS[1], ARGV[2])
                else
                    return 0
                end
                LUA;
                
                $this->redis->eval(
                    $script,
                    [$this->lockKey, $this->lockValue, $ttl],
                    1
                );
            }
        });
    }
    
    public function release(): bool
    {
        $this->watchdogRunning = false;  // 停止看门狗
        return parent::release();
    }
}
RedLock算法(高可用)
/**
 * RedLock: 多个Redis实例实现高可用分布式锁
 */
class RedLock
{
    private $redisInstances = [];
    private $quorum;  // 至少需要获取的锁数量
    
    public function __construct(array $redisInstances)
    {
        $this->redisInstances = $redisInstances;
        $this->quorum = count($redisInstances) / 2 + 1;
    }
    
    /**
     * 获取锁
     */
    public function acquire(string $key, int $ttl = 10): bool
    {
        $lockValue = uniqid(gethostname(), true);
        $startTime = microtime(true);
        $successCount = 0;
        
        // 尝试在所有实例上获取锁
        foreach ($this->redisInstances as $redis) {
            if ($this->acquireOnInstance($redis, $key, $lockValue, $ttl)) {
                $successCount++;
            }
        }
        
        // 检查是否获取到足够数量的锁
        $elapsedTime = microtime(true) - $startTime;
        $validityTime = $ttl - $elapsedTime - 0.001;  // 减去网络延迟
        
        if ($successCount >= $this->quorum && $validityTime > 0) {
            return true;  // 成功获取锁
        }
        
        // 获取失败,释放已获取的锁
        $this->release($key, $lockValue);
        return false;
    }
    
    private function acquireOnInstance($redis, string $key, string $value, int $ttl): bool
    {
        return $redis->set(
            "lock:{$key}",
            $value,
            ['NX', 'EX' => $ttl]
        ) === true;
    }
    
    /**
     * 释放锁
     */
    public function release(string $key, string $lockValue)
    {
        $script = <<<LUA
        if redis.call("get", KEYS[1]) == ARGV[1] then
            return redis.call("del", KEYS[1])
        else
            return 0
        end
        LUA;
        
        foreach ($this->redisInstances as $redis) {
            $redis->eval($script, ["lock:{$key}", $lockValue], 1);
        }
    }
}

// 使用
$redLock = new RedLock([
    new Redis(['host' => '192.168.1.1']),
    new Redis(['host' => '192.168.1.2']),
    new Redis(['host' => '192.168.1.3']),
    new Redis(['host' => '192.168.1.4']),
    new Redis(['host' => '192.168.1.5']),
]);

if ($redLock->acquire('resource', 10)) {
    // 业务逻辑
    $redLock->release('resource', $lockValue);
}

4.3 ZooKeeper分布式锁

/**
 * ZooKeeper临时顺序节点实现分布式锁
 */
class ZookeeperLock
{
    private $zk;
    private $lockPath = '/locks';
    private $nodePath;
    
    public function acquire(string $key): bool
    {
        $path = "{$this->lockPath}/{$key}";
        
        // 创建临时顺序节点
        $this->nodePath = $this->zk->create(
            "{$path}/lock_",
            '',
            [
                'flags' => Zookeeper::EPHEMERAL | Zookeeper::SEQUENCE
            ]
        );
        
        // 获取所有子节点
        $children = $this->zk->getChildren($path);
        sort($children);
        
        // 判断自己是否是最小节点
        $myNode = basename($this->nodePath);
        
        if ($children[0] === $myNode) {
            return true;  // 获取锁成功
        }
        
        // 监听前一个节点
        $index = array_search($myNode, $children);
        $prevNode = $children[$index - 1];
        
        // 等待前一个节点删除
        $watcher = function ($event) {
            if ($event['type'] === Zookeeper::DELETED_EVENT) {
                // 前一个节点删除,尝试获取锁
                $this->acquire($key);
            }
        };
        
        $this->zk->exists("{$path}/{$prevNode}", $watcher);
        
        return false;
    }
    
    public function release()
    {
        if ($this->nodePath) {
            $this->zk->delete($this->nodePath);  // 删除临时节点
        }
    }
}

// 特点:
// ✅ 公平锁(按顺序获取)
// ✅ 崩溃自动释放(临时节点)
// ✅ 避免惊群效应(只监听前一个节点)

4.4 数据库分布式锁

/**
 * 数据库实现分布式锁
 */
class DatabaseLock
{
    /**
     * 获取锁
     */
    public function acquire(string $key, int $ttl = 10): bool
    {
        $expireAt = time() + $ttl;
        
        try {
            // 插入记录
            DB::table('distributed_locks')->insert([
                'lock_key' => $key,
                'lock_value' => uniqid(gethostname(), true),
                'expire_at' => $expireAt,
                'created_at' => time(),
            ]);
            
            return true;
        } catch (\Exception $e) {
            // 唯一索引冲突,锁已存在
            return false;
        }
    }
    
    /**
     * 释放锁
     */
    public function release(string $key, string $lockValue): bool
    {
        $affected = DB::table('distributed_locks')
            ->where('lock_key', $key)
            ->where('lock_value', $lockValue)
            ->delete();
        
        return $affected > 0;
    }
    
    /**
     * 清理过期锁(定时任务)
     */
    public function cleanExpired()
    {
        DB::table('distributed_locks')
            ->where('expire_at', '<', time())
            ->delete();
    }
}

// 表结构
CREATE TABLE distributed_locks (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    lock_key VARCHAR(255) NOT NULL,
    lock_value VARCHAR(255) NOT NULL,
    expire_at INT NOT NULL,
    created_at INT NOT NULL,
    UNIQUE KEY uk_lock_key (lock_key),
    KEY idx_expire_at (expire_at)
) ENGINE=InnoDB;

4.5 分布式锁对比

方案优点缺点适用场景
Redis性能高、实现简单单点故障、时钟漂移高性能、短时锁
RedLock高可用实现复杂、性能略低关键业务
ZooKeeper可靠性高、支持公平锁性能较低、依赖ZK对可靠性要求高
数据库简单、不依赖外部组件性能低小规模、低并发
Etcd高可用、强一致性运维复杂分布式系统

5. 锁的性能优化

5.1 减小锁粒度

// ❌ 粗粒度锁:锁整个方法
class BadExample
{
    private $lock;
    private $data = [];
    
    public function processData(string $key, $value)
    {
        $this->lock->lock();  // 锁住整个方法
        
        // 业务逻辑(耗时)
        sleep(1);
        
        // 更新数据
        $this->data[$key] = $value;
        
        $this->lock->unlock();
    }
}

// ✅ 细粒度锁:只锁关键部分
class GoodExample
{
    private $lock;
    private $data = [];
    
    public function processData(string $key, $value)
    {
        // 业务逻辑(不需要锁)
        $processedValue = $this->process($value);
        sleep(1);
        
        // 只锁数据更新部分
        $this->lock->lock();
        $this->data[$key] = $processedValue;
        $this->lock->unlock();
    }
}

5.2 锁分段

/**
 * 分段锁:将数据分成多个段,每段一个锁
 */
class SegmentLock
{
    private $segments = 16;  // 16个分段
    private $locks = [];
    private $data = [];
    
    public function __construct()
    {
        for ($i = 0; $i < $this->segments; $i++) {
            $this->locks[$i] = new \Swoole\Lock(SWOOLE_MUTEX);
            $this->data[$i] = [];
        }
    }
    
    private function getSegment(string $key): int
    {
        return crc32($key) % $this->segments;
    }
    
    public function set(string $key, $value)
    {
        $segment = $this->getSegment($key);
        
        $this->locks[$segment]->lock();
        $this->data[$segment][$key] = $value;
        $this->locks[$segment]->unlock();
    }
    
    public function get(string $key)
    {
        $segment = $this->getSegment($key);
        
        $this->locks[$segment]->lock();
        $value = $this->data[$segment][$key] ?? null;
        $this->locks[$segment]->unlock();
        
        return $value;
    }
}

// 优势:
// 16个锁可以支持16个并发操作
// 减少锁冲突概率:1/16

5.3 读写分离

/**
 * 读写锁:读不互斥,写互斥
 */
class OptimizedCache
{
    private $rwlock;
    private $data = [];
    
    public function __construct()
    {
        $this->rwlock = new RWLock();
    }
    
    // 读操作:可并发
    public function get(string $key)
    {
        return $this->rwlock->read(function () use ($key) {
            return $this->data[$key] ?? null;
        });
    }
    
    // 写操作:独占
    public function set(string $key, $value)
    {
        $this->rwlock->write(function () use ($key, $value) {
            $this->data[$key] = $value;
        });
    }
}

// 性能提升:
// 读多写少场景:提升10-100倍

5.4 无锁编程

/**
 * CAS (Compare And Swap) 无锁操作
 */
class AtomicCounter
{
    private $atomic;
    
    public function __construct(int $initial = 0)
    {
        $this->atomic = new \Swoole\Atomic($initial);
    }
    
    // 原子加法(无锁)
    public function increment(int $delta = 1): int
    {
        return $this->atomic->add($delta);
    }
    
    // CAS操作
    public function compareAndSet(int $expect, int $update): bool
    {
        return $this->atomic->cmpset($expect, $update);
    }
    
    public function get(): int
    {
        return $this->atomic->get();
    }
}

// 使用
$counter = new AtomicCounter(0);

// 1000个协程并发累加
for ($i = 0; $i < 1000; $i++) {
    go(function () use ($counter) {
        $counter->increment();
    });
}

// 性能:比互斥锁快10-100倍

6. 死锁问题

6.1 死锁检测

/**
 * 死锁检测器
 */
class DeadlockDetector
{
    private $waitGraph = [];  // 等待图
    
    /**
     * 添加等待关系
     */
    public function addWait(int $txId, int $holderId)
    {
        $this->waitGraph[$txId][] = $holderId;
    }
    
    /**
     * 检测死锁(查找环)
     */
    public function detectDeadlock(): bool
    {
        foreach ($this->waitGraph as $txId => $holders) {
            $visited = [];
            if ($this->hasCycle($txId, $visited)) {
                return true;  // 检测到死锁
            }
        }
        return false;
    }
    
    private function hasCycle(int $node, array &$visited): bool
    {
        if (isset($visited[$node])) {
            return true;  // 找到环
        }
        
        $visited[$node] = true;
        
        if (isset($this->waitGraph[$node])) {
            foreach ($this->waitGraph[$node] as $next) {
                if ($this->hasCycle($next, $visited)) {
                    return true;
                }
            }
        }
        
        unset($visited[$node]);
        return false;
    }
}

// 示例:
// TX1等待TX2: detector->addWait(1, 2)
// TX2等待TX3: detector->addWait(2, 3)
// TX3等待TX1: detector->addWait(3, 1)  ← 形成环
// detector->detectDeadlock() → true

6.2 死锁预防

/**
 * 按顺序获取锁(破坏循环等待)
 */
class OrderedLock
{
    private $locks = [];
    
    public function acquire(array $resourceIds)
    {
        // 排序资源ID
        sort($resourceIds);
        
        // 按顺序获取锁
        foreach ($resourceIds as $id) {
            $this->locks[$id]->lock();
        }
    }
    
    public function release(array $resourceIds)
    {
        // 按相反顺序释放
        rsort($resourceIds);
        
        foreach ($resourceIds as $id) {
            $this->locks[$id]->unlock();
        }
    }
}

// 转账示例
public function transfer(int $fromId, int $toId, int $amount)
{
    $resourceIds = [$fromId, $toId];
    
    $this->acquire($resourceIds);
    
    try {
        // 转账逻辑
    } finally {
        $this->release($resourceIds);
    }
}

// 保证:所有事务都按ID顺序获取锁
// 结果:不会出现循环等待

7. 实战案例

7.1 商品库存扣减

/**
 * 高并发库存扣减
 */
class InventoryService
{
    /**
     * 方案1:悲观锁
     */
    public function decreaseWithPessimisticLock(int $productId, int $quantity): bool
    {
        DB::beginTransaction();
        
        try {
            // 加排他锁
            $product = DB::table('products')
                ->where('id', $productId)
                ->lockForUpdate()
                ->first();
            
            if ($product->stock < $quantity) {
                throw new Exception('库存不足');
            }
            
            // 扣减库存
            DB::table('products')
                ->where('id', $productId)
                ->decrement('stock', $quantity);
            
            DB::commit();
            return true;
        } catch (Exception $e) {
            DB::rollBack();
            return false;
        }
    }
    
    /**
     * 方案2:乐观锁
     */
    public function decreaseWithOptimisticLock(int $productId, int $quantity, int $maxRetry = 3): bool
    {
        for ($i = 0; $i < $maxRetry; $i++) {
            $product = DB::table('products')
                ->where('id', $productId)
                ->first();
            
            if ($product->stock < $quantity) {
                return false;
            }
            
            // CAS更新
            $affected = DB::table('products')
                ->where('id', $productId)
                ->where('stock', $product->stock)  // 版本检查
                ->where('stock', '>=', $quantity)
                ->decrement('stock', $quantity);
            
            if ($affected > 0) {
                return true;  // 成功
            }
            
            // 失败,重试
            usleep(rand(10000, 50000));  // 随机退避
        }
        
        return false;
    }
    
    /**
     * 方案3:Redis原子操作
     */
    public function decreaseWithRedis(int $productId, int $quantity): bool
    {
        $key = "product:stock:{$productId}";
        
        // Lua脚本保证原子性
        $script = <<<LUA
        local stock = redis.call('get', KEYS[1])
        if not stock then
            return -1
        end
        
        stock = tonumber(stock)
        if stock < tonumber(ARGV[1]) then
            return 0
        end
        
        redis.call('decrby', KEYS[1], ARGV[1])
        return 1
        LUA;
        
        $result = $this->redis->eval($script, [$key, $quantity], 1);
        
        return $result === 1;
    }
}

7.2 分布式ID生成

/**
 * 基于Redis的分布式ID生成器(号段模式)
 */
class DistributedIdGenerator
{
    private $redis;
    private $segmentSize = 1000;  // 每次获取1000个ID
    
    private $currentId;
    private $maxId;
    
    public function nextId(string $bizType): int
    {
        // 检查当前号段是否用完
        if ($this->currentId >= $this->maxId) {
            $this->getNewSegment($bizType);
        }
        
        return ++$this->currentId;
    }
    
    private function getNewSegment(string $bizType)
    {
        $key = "id_generator:{$bizType}";
        
        // 分布式锁
        $lock = new RedisLock($this->redis);
        $lock->acquire($key, 10);
        
        try {
            // 获取当前最大ID
            $currentMax = (int) $this->redis->get($key) ?: 0;
            
            // 预分配号段
            $this->currentId = $currentMax;
            $this->maxId = $currentMax + $this->segmentSize;
            
            // 更新Redis
            $this->redis->set($key, $this->maxId);
        } finally {
            $lock->release();
        }
    }
}

// 优势:
// - 减少Redis访问次数(批量获取)
// - 高性能(本地缓存)
// - 分布式环境下不重复

7.3 幂等性保证

/**
 * 接口幂等性实现
 */
class IdempotentService
{
    /**
     * 基于分布式锁的幂等性
     */
    public function createOrder(string $requestId, array $orderData): array
    {
        $lockKey = "idempotent:{$requestId}";
        $cacheKey = "idempotent:result:{$requestId}";
        
        // 检查缓存
        $cachedResult = $this->redis->get($cacheKey);
        if ($cachedResult) {
            return json_decode($cachedResult, true);
        }
        
        // 获取分布式锁
        $lock = new RedisLock($this->redis);
        if (!$lock->acquire($lockKey, 30)) {
            throw new Exception('请勿重复提交');
        }
        
        try {
            // 再次检查缓存(双重检查)
            $cachedResult = $this->redis->get($cacheKey);
            if ($cachedResult) {
                return json_decode($cachedResult, true);
            }
            
            // 创建订单
            $result = $this->doCreateOrder($orderData);
            
            // 缓存结果(24小时)
            $this->redis->setex($cacheKey, 86400, json_encode($result));
            
            return $result;
        } finally {
            $lock->release();
        }
    }
}

8. 面试高频题

Q1: 什么是死锁?如何避免?

A: 见6节

核心要点:

  • 4个必要条件:互斥、持有并等待、不可剥夺、循环等待
  • 预防方法:破坏任一条件
  • 最常用:按固定顺序获取锁

Q2: 乐观锁和悲观锁的区别?

A:

维度悲观锁乐观锁
锁时机操作前加锁操作后检查
实现方式数据库锁(FOR UPDATE)版本号/CAS
并发性能低(串行)高(并发)
冲突处理等待重试
适用场景写多、冲突频繁读多、冲突少

示例:

-- 悲观锁
BEGIN;
SELECT * FROM products WHERE id = 1 FOR UPDATE;  -- 加锁
UPDATE products SET stock = stock - 1 WHERE id = 1;
COMMIT;

-- 乐观锁
-- 1. 读取
SELECT stock, version FROM products WHERE id = 1;
-- 2. 更新(比较版本号)
UPDATE products 
SET stock = stock - 1, version = version + 1 
WHERE id = 1 AND version = 10;  -- 版本号未变才更新

Q3: Redis分布式锁有什么问题?如何解决?

A:

问题1:超时释放

客户端A获取锁,超时10秒
客户端A执行业务,耗时1510秒后锁自动释放
客户端B获取锁
客户端A完成,释放锁(误删B的锁)

解决:使用唯一标识

// 加锁时保存唯一值
$lockValue = uniqid();
$redis->set($lockKey, $lockValue, ['NX', 'EX' => 10]);

// 释放时比较值
$script = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) end";
$redis->eval($script, [$lockKey, $lockValue], 1);

问题2:单点故障

Redis宕机 → 锁不可用
主从切换 → 锁丢失

解决:RedLock算法(多个Redis实例)

问题3:时钟漂移

锁设置10秒过期
系统时钟快进20秒
锁立即过期

解决:使用单调时钟,或ZooKeeper


Q4: MySQL的行锁是怎么实现的?

A:

实现机制:通过索引实现

-- ✅ 命中索引:行锁
SELECT * FROM users WHERE id = 1 FOR UPDATE;  -- 只锁id=1

-- ❌ 未命中索引:表锁
SELECT * FROM users WHERE name = 'John' FOR UPDATE;  -- 锁整表

-- 解决:添加索引
CREATE INDEX idx_name ON users(name);

锁类型

  1. Record Lock:锁单行
  2. Gap Lock:锁间隙(防幻读)
  3. Next-Key Lock:Record + Gap

示例

-- 现有记录:id = 1, 5, 10
SELECT * FROM users WHERE id >= 5 FOR UPDATE;

-- 锁定:
-- Record: id=5, id=10
-- Gap: (5,10), (10,+∞)
-- 阻止插入:id=6,7,8,9,11,12...

Q5: 如何设计一个高性能的分布式锁?

A:

设计要点

  1. 互斥性
// 使用NX保证
$redis->set($key, $value, ['NX', 'EX' => $ttl]);
  1. 防止死锁
// 设置过期时间
['EX' => 10]

// 自动续期(看门狗)
go(function () {
    while ($holding) {
        sleep($ttl / 3);
        $redis->expire($key, $ttl);
    }
});
  1. 防止误删
// 唯一标识
$value = uniqid();

// Lua脚本原子释放
$script = "if redis.call('get',KEYS[1])==ARGV[1] then ...";
  1. 高可用
// RedLock:多个Redis实例
// 超过半数成功才算获取锁
  1. 高性能
// 号段模式:批量获取
$ids = range($currentId, $currentId + 1000);

// 减少锁竞争:分段锁
$segment = crc32($key) % 16;

总结

核心要点

  1. 锁分类:表锁/行锁、悲观/乐观、本地/分布式
  2. 数据库锁:InnoDB行锁、MVCC、死锁检测
  3. 分布式锁:Redis、ZooKeeper、数据库实现
  4. 性能优化:减小粒度、锁分段、读写分离
  5. 死锁预防:按顺序获取、超时机制

面试建议

  • 理解各种锁的原理和适用场景
  • 掌握分布式锁的常见实现方式
  • 能够分析死锁问题并提出解决方案
  • 准备实际项目中的锁使用案例

推荐资源:

  • 《高性能MySQL》
  • 《Redis设计与实现》
  • 《分布式系统原理与范型》