postgres 咨询锁性能问题

2023-11-23 15:17:57 +08:00
 ieesk

CREATE TABLE IF NOT EXISTS account
(
    id              INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    username        TEXT    NOT NULL,
    coin            NUMERIC NOT NULL DEFAULT 0 CHECK ( coin >= (0)::numeric ),
    version         BIGINT  NOT NULL DEFAULT 0,
    created_at      TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at      TIMESTAMP WITH TIME ZONE
);
comment on table account is '账户表.';
comment on column account.id is '自增唯一 ID 标示.';
comment on column account.username is '账户名.';
comment on column account.coin is '余额.';
comment on column account.version is '账户余额版本标识(乐观锁).';


CREATE TABLE IF NOT EXISTS transaction
(
    id             INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    account_id        INTEGER NOT NULL REFERENCES account (id),
    value          NUMERIC NOT NULL,
    balance        NUMERIC NOT NULL DEFAULT 0 CHECK ( balance <> 'NaN'::numeric AND (balance >= (0)::numeric)),
    type           INTEGER NOT NULL CHECK ( type IN (1, 2, 4, 8, 16) ),
    narration      TEXT    NOT NULL         DEFAULT '',
    created_at     TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    updated_at     TIMESTAMP WITH TIME ZONE,
    deleted_at     TIMESTAMP WITH TIME ZONE
);
comment on table transaction is '账户余额变动交易表, 此表记录了每笔交易账户余额变动日志.';
comment on column transaction.id is '自增唯一 ID 标示.';
comment on column transaction.account_id is '账户 ID, 关联账户表.';
comment on column transaction.value is '交易代币数额,收入为正, 支出为负.';
comment on column transaction.balance is '交易完成后剩余的账户余额.';
comment on column transaction.narration is '交易描述.';
comment on column transaction.type is '交易类型.';


-- TRIGGER
CREATE OR REPLACE FUNCTION transaction__sync_balance()
    RETURNS TRIGGER AS
$$
BEGIN
    -- UPDATE
    UPDATE account
    SET coin = coin + NEW.value,version = version + 1
    WHERE id = NEW.user_id
    RETURNING coin INTO NEW.balance;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER sync_balance_trigger
    BEFORE INSERT
    ON transaction
    FOR EACH ROW
EXECUTE FUNCTION transaction__sync_balance();
SELECT pg_advisory_xact_lock(1001, account_id)
1672 次点击
所在节点    PostgreSQL
9 条回复
neoblackcap
2023-11-23 15:29:18 +08:00
我们以前的做法是尽量让锁前置,不用数据库。比如特定的竞争操作只有 master 进程有写入的权限。这样就去掉了锁竞争
frank000
2023-11-23 15:34:00 +08:00
这是必须要使用数据库触发器来做这件事么?还是有什么特别的考虑因素?
ieesk
2023-11-23 15:36:48 +08:00
@frank000 是的,不然就用 sql 更新了。
ieesk
2023-11-23 15:37:55 +08:00
@neoblackcap 这样改,我这业务牺牲有多大
binbin0915jjpp
2023-11-23 16:06:36 +08:00
放到 AP 端吧 比如 mybatis 的拦截器里
MoYi123
2023-11-23 18:35:16 +08:00
为什么会有脏写?
是用 set coin = xxx 的写法吗?
为什么不用 set coin = coin + xxx?
ZZ74
2023-11-23 19:03:35 +08:00
你用锁也没比其他的高性能啊,只是把压力放到了数据库层而已。性能就和数据库服务器性能强相关。
+钱操作简单 直接插入+更新即可。
-钱就是 where coin - xxx >0 更新成功就插入。或者代码层面分布式锁。

你要是想改的少,用存储过程或者 function 啊,也比触发器合适多了
neoblackcap
2023-11-23 21:10:23 +08:00
@ieesk 其实完全可以很少改动,你把写入的操作放在一个独立的服务,那个服务只有一个进程,开放一个接口。现有的服务在写入的时候就调用这个接口。
上锁,释放锁的速度并不慢。慢是因为锁竞争。单线程写入的话,性能上限应该可以逼近你数据的写入效率极限。

不过这样改的话,运维会多了很多工作。毕竟无缘故就多了一个服务需要运维,还让系统引入了一个单点问题。如果要解决单点问题的话,又要引入分布式锁。
iseki
306 天前
悲观锁定能不用就不用,看上去你不需要锁定啊,你只是需要确保记录更新的事务性而已,那为什么不调高事物隔离级别?此外看了下您这个触发器,似乎默认的 RC 级别已经够用了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/994540

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX