PostgreSQL 的乐观锁
Optimistic Locking with PostgreSQL
问题陈述:我正在使用存储库模式通过 pg
npm 模块。如果两个用户几乎同时尝试修改同一条记录,则第一个用户提交的更改将被第二个用户提交的更改覆盖。我想阻止提交第二个用户的更改,直到他们用第一个用户的更改更新了他们的本地记录副本。
我知道像 CouchDB 这样的数据库有一个“_rev”属性,它在这种情况下用于检测从陈旧快照更新的尝试。随着我研究的深入,我发现这称为乐观锁定 ()。因此,我将在我的 table 中添加一个 rev 列,并在我的 SQL 更新语句中使用它。
UPDATE tableX
SET field1 = value1,
field2 = value2,
...,
rev_field = uuid_generate_v4()
WHERE id_field = id_value
AND rev_field = rev_value
但是,如果 id_value
匹配而 rev_value
不匹配,则不会告诉我的存储库代码该记录已过时,只有 0 行受到查询的影响。
所以我有一个我在 pgAdmin 中编写的脚本,它将检测 update
影响 0 行的情况,然后检查 rev_field.
DO $$
DECLARE
i_id numeric;
i_uuid uuid;
v_count numeric;
v_rev uuid;
BEGIN
i_id := 1;
i_uuid := '20b2e135-42d0-4a49-94c0-5557dd09abd1';
UPDATE account_r
SET account_name = 'savings',
rev = uuid_generate_v4()
WHERE account_id = i_id
AND rev = i_uuid;
GET DIAGNOSTICS v_count = ROW_COUNT;
IF v_count < 1 THEN
SELECT rev INTO v_rev
FROM account_r
WHERE account_id = i_id;
IF v_rev <> i_uuid THEN
RAISE EXCEPTION 'revision mismatch';
END IF;
END IF;
RAISE NOTICE 'rows affected: %', v_count;
END $$;
虽然我很乐意table将此代码改编成存储过程并从 Node 调用它,但我希望有一个解决方案,它不会那么复杂。一方面,将这些函数移至数据库将清理我的 JS 代码,另一方面,这需要编写大量样板文件 SQL,因为必须为 UPDATE
完成和 DELETE
每个 table.
有没有更简单的方法来完成这项工作? (也许 处的代码是更简单的方法?)我是否应该查看 ORM 来帮助减少这里的头痛?
无需维护 rev
值。您可以获得 table 行的 md5 哈希值。
create table mytable (
id int primary key,
some_text text,
some_int int,
some_date timestamptz
);
insert into mytable
values (1, 'First entry', 0, now() - interval '1 day'),
(2, 'Second entry', 1, now()),
(3, 'Third entry', 2, now() + interval '1 day');
select *, md5(mytable::text) from mytable order by id;
fiddle 包括其他查询以证明计算的 md5()
是基于行的值。
使用该散列进行乐观锁定,更新可以采用以下形式:
update mytable
set some_int = -1
where id = 1
and md5(mytable::text) = <md5 hash from select>
returning *
您仍然需要检查没有 return 行,但这可以在节点端抽象掉。
看起来 result.row_count
包含受影响的行数,因此您不需要 returning *
部分。
问题陈述:我正在使用存储库模式通过 pg
npm 模块。如果两个用户几乎同时尝试修改同一条记录,则第一个用户提交的更改将被第二个用户提交的更改覆盖。我想阻止提交第二个用户的更改,直到他们用第一个用户的更改更新了他们的本地记录副本。
我知道像 CouchDB 这样的数据库有一个“_rev”属性,它在这种情况下用于检测从陈旧快照更新的尝试。随着我研究的深入,我发现这称为乐观锁定 (
UPDATE tableX
SET field1 = value1,
field2 = value2,
...,
rev_field = uuid_generate_v4()
WHERE id_field = id_value
AND rev_field = rev_value
但是,如果 id_value
匹配而 rev_value
不匹配,则不会告诉我的存储库代码该记录已过时,只有 0 行受到查询的影响。
所以我有一个我在 pgAdmin 中编写的脚本,它将检测 update
影响 0 行的情况,然后检查 rev_field.
DO $$
DECLARE
i_id numeric;
i_uuid uuid;
v_count numeric;
v_rev uuid;
BEGIN
i_id := 1;
i_uuid := '20b2e135-42d0-4a49-94c0-5557dd09abd1';
UPDATE account_r
SET account_name = 'savings',
rev = uuid_generate_v4()
WHERE account_id = i_id
AND rev = i_uuid;
GET DIAGNOSTICS v_count = ROW_COUNT;
IF v_count < 1 THEN
SELECT rev INTO v_rev
FROM account_r
WHERE account_id = i_id;
IF v_rev <> i_uuid THEN
RAISE EXCEPTION 'revision mismatch';
END IF;
END IF;
RAISE NOTICE 'rows affected: %', v_count;
END $$;
虽然我很乐意table将此代码改编成存储过程并从 Node 调用它,但我希望有一个解决方案,它不会那么复杂。一方面,将这些函数移至数据库将清理我的 JS 代码,另一方面,这需要编写大量样板文件 SQL,因为必须为 UPDATE
完成和 DELETE
每个 table.
有没有更简单的方法来完成这项工作? (也许
无需维护 rev
值。您可以获得 table 行的 md5 哈希值。
create table mytable (
id int primary key,
some_text text,
some_int int,
some_date timestamptz
);
insert into mytable
values (1, 'First entry', 0, now() - interval '1 day'),
(2, 'Second entry', 1, now()),
(3, 'Third entry', 2, now() + interval '1 day');
select *, md5(mytable::text) from mytable order by id;
fiddle 包括其他查询以证明计算的 md5()
是基于行的值。
使用该散列进行乐观锁定,更新可以采用以下形式:
update mytable
set some_int = -1
where id = 1
and md5(mytable::text) = <md5 hash from select>
returning *
您仍然需要检查没有 return 行,但这可以在节点端抽象掉。
看起来 result.row_count
包含受影响的行数,因此您不需要 returning *
部分。