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

为何在PostgreSQL中同时使用乐观并发与咨询锁?

为什么Eventide Message Store的write_message函数同时使用咨询锁和乐观并发控制?

我是PostgreSQL新手,从Eventide项目的Message Store数据库中获取了如下write_message函数:

CREATE OR REPLACE FUNCTION write_message(
  id varchar,
  stream_name varchar,
  "type" varchar,
  data jsonb,
  metadata jsonb DEFAULT NULL,
  expected_version bigint DEFAULT NULL
)
RETURNS bigint
AS $$
DECLARE
  _message_id uuid;
  _stream_version bigint;
  _next_position bigint;
BEGIN
  PERFORM acquire_lock(write_message.stream_name);

  _stream_version := stream_version(write_message.stream_name);

  IF _stream_version IS NULL THEN
    _stream_version := -1;
  END IF;

  IF write_message.expected_version IS NOT NULL THEN
    IF write_message.expected_version != _stream_version THEN
      RAISE EXCEPTION
        'Wrong expected version: % (Stream: %, Stream Version: %)',
        write_message.expected_version,
        write_message.stream_name,
        _stream_version;
    END IF;
  END IF;

  _next_position := _stream_version + 1;

  _message_id = uuid(write_message.id);

  INSERT INTO messages
    (
      id,
      stream_name,
      position,
      type,
      data,
      metadata
    )
  VALUES
    (
      _message_id,
      write_message.stream_name,
      _next_position,
      write_message.type,
      write_message.data,
      write_message.metadata
    )
  ;

  IF current_setting('debug_write', true) = 'on' OR current_setting('debug', true) = 'on' THEN
    RAISE NOTICE '» write_message';
    RAISE NOTICE 'id ($1): %', write_message.id;
    RAISE NOTICE 'stream_name ($2): %', write_message.stream_name;
    RAISE NOTICE 'type ($3): %', write_message.type;
    RAISE NOTICE 'data ($4): %', write_message.data;
    RAISE NOTICE 'metadata ($5): %', write_message.metadata;
    RAISE NOTICE 'expected_version ($6): %', write_message.expected_version;
    RAISE NOTICE '_stream_version: %', _stream_version;
    RAISE NOTICE '_next_position: %', _next_position;
  END IF;

  RETURN _next_position;
END;
$$ LANGUAGE plpgsql
VOLATILE;

该函数对write_message.stream_name参数使用咨询锁,同时基于stream_version(write_message.stream_name)实现了乐观并发控制——stream_version本质是查询与传入参数同名的stream_name下messages表的max(position),position是同stream_name下记录的递增序号。若函数执行期间有其他同stream_name的消息写入,传入的expected_version会小于当前的max(position)。

我的问题是:为何要同时对同一对象使用类似操作系统信号量(如Windows临界区/.NET的Monitor.Enter)的咨询锁与乐观并发这两种机制?

以下是acquire_lock函数的定义:

CREATE OR REPLACE FUNCTION acquire_lock(
  stream_name varchar
)
RETURNS bigint
AS $$
DECLARE
  _category varchar;
  _category_name_hash bigint;
BEGIN
  _category := category(acquire_lock.stream_name);
  _category_name_hash := hash_64(_category);
  PERFORM pg_advisory_xact_lock(_category_name_hash);

  IF current_setting('debug_write', true) = 'on' OR current_setting('debug', true) = 'on' THEN
    RAISE NOTICE '» acquire_lock';
    RAISE NOTICE 'stream_name: %', acquire_lock.stream_name;
    RAISE NOTICE '_category: %', _category;
    RAISE NOTICE '_category_name_hash: %', _category_name_hash;
  END IF;

  RETURN _category_name_hash;
END;
$$ LANGUAGE plpgsql
VOLATILE;

以下是stream_version函数的定义:

CREATE OR REPLACE FUNCTION message_store.stream_version(
  stream_name varchar
)
RETURNS bigint
AS $$
DECLARE
  _stream_version bigint;
BEGIN
  SELECT
    max(position) into _stream_version
  FROM
    messages
  WHERE
    messages.stream_name = stream_version.stream_name;

  RETURN _stream_version;
END;
$$ LANGUAGE plpgsql
VOLATILE;

解答

这两种机制是互补的,分别解决不同层面的问题:

  • 咨询锁的作用:减少高并发下的乐观锁冲突重试
    从acquire_lock的实现可以看到,它并不是给单个流加锁,而是给流所属的**分类(category)**加事务级咨询锁。这样同一分类下的所有流写入会被串行化,避免同一分类下大量流同时写入时,频繁触发乐观锁的版本冲突(因为每个流的写入都要查max(position),高并发下会出现大量“预期版本不符”的异常,导致客户端不断重试)。通过分类锁把同一组相关流的写入串行化,能有效降低冲突率,提升整体吞吐量。

  • 乐观并发控制的作用:保证单个流的版本一致性
    乐观锁的expected_version检查是为了确保客户端是基于自己读取到的最新流版本进行写入。假设客户端先查询了流版本,然后等待咨询锁的释放,在等待期间可能已经有其他客户端写入了该流的新消息,如果没有乐观锁检查,当前客户端就会基于旧版本写入,破坏流的顺序性和一致性——这正是事件溯源模式中绝对不允许的情况。乐观锁的检查能最终确保写入操作符合客户端预期的流状态。

  • 两者结合的必要性
    如果只使用乐观锁:高并发场景下,同一分类的多个流写入会导致大量版本冲突,客户端重试次数激增,系统性能下降;如果只使用咨询锁:虽然避免了冲突,但客户端可能基于旧的流版本写入(比如查询版本后等待锁的间隙,流已经被修改),无法保证业务层面的一致性。

两者搭配使用,既通过分类级的咨询锁降低了冲突概率,又通过单流的乐观锁检查保障了最终的业务正确性,是针对事件溯源场景高并发写入的一种优化方案。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 10:37:32