You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel 8 PostgreSQL读写分离如何确认主从复制完成再执行后续操作

Laravel 8 对接PostgreSQL主从时复制延迟问题解决方案

PostgreSQL 主从异步复制架构下,写操作提交后从库存在同步延迟,此时请求路由到从库读刚写入的数据就会触发404、数据不存在等错误。要实现写操作后确认复制完成再执行后续逻辑,可根据业务场景选择以下方案:

方案1:基于PG原生WAL位点校验(和预期逻辑完全匹配)

PostgreSQL 每个写事务提交后都会生成唯一的WAL日志序列号(LSN),主库写入后拿到当前LSN,轮询从库的WAL回放进度,当从库回放LSN大于等于主库写入LSN时,就代表复制已经完成。
实现步骤:

  • 先确认config/database.php中pgsql连接的读写分离配置正确,暂时关闭sticky参数避免框架自动路由干扰:
'pgsql' => [
    'driver' => 'pgsql',
    'read' => [
        'host' => [env('DB_READ_HOST', '127.0.0.1')],
    ],
    'write' => [
        'host' => env('DB_WRITE_HOST', '127.0.0.1'),
    ],
    'sticky' => false,
    // 其余端口、库名、账号密码配置保持不变
],
  • 封装通用的复制状态校验方法,可放到全局辅助函数或者BaseModel里:
/**
 * 等待从库同步追上主库指定位点
 * @param string $primaryLsn 主库写入位点
 * @param int $timeout 最大等待秒数
 * @param int $intervalUs 轮询间隔(微秒)
 * @return bool 同步是否完成
 */
function waitReplicationDone(string $primaryLsn, int $timeout = 2, int $intervalUs = 50000): bool
{
    $startTime = microtime(true);
    // 拿到从库连接实例
    $readPdo = DB::connection('pgsql')->getReadPdo();
    while (microtime(true) - $startTime < $timeout) {
        // 查询从库当前回放的LSN位点
        $replicaLsn = $readPdo->query("SELECT pg_last_wal_replay_lsn() AS lsn")->fetch(\PDO::FETCH_ASSOC)['lsn'];
        if ($replicaLsn) {
            // 位点对比:从库回放位点 >= 主库写入位点代表同步完成
            $isDone = $readPdo->query("SELECT '{$primaryLsn}'::pg_lsn <= '{$replicaLsn}'::pg_lsn AS done")->fetch(\PDO::FETCH_ASSOC)['done'];
            if ($isDone) return true;
        }
        usleep($intervalUs);
    }
    // 超时未同步完成返回false,可根据业务降级走主库查询或抛出友好提示
    return false;
}
  • 业务调用逻辑和预期写法完全对齐:
// 执行插入操作,需要拿自增ID用insertGetId即可
$userId = DB::table('users')->insertGetId([
    // 写入字段
]);
// 从主库拿本次写入对应的WAL位点
$primaryLsn = DB::connection('pgsql')->getPdo()
    ->query("SELECT pg_current_wal_lsn() AS lsn")
    ->fetch(\PDO::FETCH_ASSOC)['lsn'];

if (waitReplicationDone($primaryLsn)) {
    // 复制完成,后续查询走从库也能读到最新数据
    $user = DB::table('users')->find($userId);
    // 执行后续业务逻辑
}

方案2:开启Laravel自带Sticky粘滞模式(同请求下零成本解决)

Laravel 8 原生支持读写分离场景下的sticky配置,开启后同一个请求生命周期内只要执行过写操作,后续所有读请求会自动路由到主库,完全不需要额外做复制校验,配置成本极低:

'pgsql' => [
    'driver' => 'pgsql',
    'read' => [
        'host' => [env('DB_READ_HOST', '127.0.0.1')],
    ],
    'write' => [
        'host' => env('DB_WRITE_HOST', '127.0.0.1'),
    ],
    'sticky' => true, // 开启粘滞
    // 其余配置不变
],

注意:sticky模式仅作用于当前请求,比如写入后跳转到详情页的场景属于新的请求,sticky不会生效,这类跨请求的写后读场景需要用方案1或者方案3。

方案3:强一致场景强制走主库(最简单兜底)

对于写入后必须立刻读到最新数据的核心场景,不需要等待复制,直接在查询时指定使用写连接(主库)即可,没有额外轮询开销:

$userId = DB::table('users')->insertGetId([...]);
// 强制走主库查询,完全不受从库延迟影响
$user = DB::table('users')->useWritePdo()->find($userId);

注意事项

  • 轮询校验复制的方案超时时间不要设置过长,内网正常环境下PG主从复制延迟通常在几十毫秒级别,设置1-2秒超时即可,超时后直接降级走主库查询,避免阻塞用户请求。
  • 不要全局给所有写操作加复制等待逻辑,只在确实存在写后读从库需求的场景使用,否则会整体拉高所有写接口的响应耗时。

内容的提问来源于stack exchange,提问作者Mikaele

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 02:51:30