<?php
$pdo = new PDO('pgsql:host=127.0.0.1;dbname=test', 'postgres', '123456');
$lockId = 20240616;            // 自定义:同一资源必须用同一数字

// 尝试加锁(非阻塞)
$stmt = $pdo->query("SELECT pg_try_advisory_lock($lockId)");
if (!$stmt->fetchColumn()) {
    exit("资源正被其它进程占用,稍后再试\n");
}

echo "拿到锁,开始业务\n";
try {
    $pdo->beginTransaction();
    // TODO:写数据库 / 调用第三方
    $pdo->commit();
    echo "业务完成\n";
} finally {
    // 主动释放;若忘记释放,连接关闭也会自动释放
    $pdo->query("SELECT pg_advisory_unlock($lockId)");
}

解释

下面只解释这段代码本身在做什么,逐行说明其目的。

$pdo = new PDO('pgsql:host=127.0.0.1;dbname=test', 'postgres', '123456');
  1. 连接本地 PostgreSQL 数据库 test
    返回的 PDO 对象用于后续执行 SQL。
$lockId = 20240616;   // 同一资源统一用同一个数字
  1. 定义一个 锁 ID(64 位整数)。
    在 PostgreSQL 的“咨询锁”(advisory lock) 机制里,这个数字就是被锁定的资源标识;并发进程只要用同一个数字,就能互斥。
// 尝试加锁(非阻塞)
$stmt = $pdo->query("SELECT pg_try_advisory_lock($lockId)");
if (!$stmt->fetchColumn()) {
    exit("资源正被其它进程占用,稍后再试\n");
}
  1. 调用 pg_try_advisory_lock() 尝试获得锁
    • 若锁空闲→返回 true ⇒ 继续往下执行。
    • 若锁被其他进程持有→返回 false ⇒ 立即退出,提示“资源被占用”。
echo "拿到锁,开始业务\n";
try {
    $pdo->beginTransaction();          // 开启数据库事务
    // TODO:写数据库 / 调用第三方          ← 在这里放真正的业务代码
    $pdo->commit();                    // 提交事务
    echo "业务完成\n";
} finally {
  1. 执行业务逻辑
    • 先输出拿锁成功信息。
    • 用本地事务包裹对数据库的写操作或调用外部接口。
    • 成功后 commit();如果 try 块里抛异常会直接进入 finally
    // 主动释放;若忘记释放,连接关闭也会自动释放
    $pdo->query("SELECT pg_advisory_unlock($lockId)");
}
  1. 释放锁
    • pg_advisory_unlock() 显式把锁释放掉,让其他进程可以继续获得。
    • 即便忘记调用,数据库连接关闭时,PostgreSQL 会自动删除该会话持有的咨询锁,避免死锁。

总结一句话

这段代码利用 PostgreSQL 的咨询锁来实现 分布式互斥

  1. 先用 pg_try_advisory_lock() 尝试加锁;
  2. 拿到锁后在本地事务里安全执行写数据库/第三方调用;
  3. 最终显式 pg_advisory_unlock() 释放锁。
    若同时有多个进程运行、且使用同一 lockId,同一时刻最多只有一个进程能进入“业务区”,从而保证操作的串行安全性。
Logo

更多推荐