PostgreSQL 12中PL/pgSQL函数内语句是否顺序执行?竞态问题咨询
关于PL/pgSQL函数竞态条件与RLS限制的问题解答
嘿,我来帮你理清这个问题,结合你的PostgreSQL 12环境和RLS场景给你拆解清楚:
核心事实先明确
首先要打消你两个顾虑:
- PL/pgSQL语句是严格按顺序执行的,而且整个函数默认跑在一个原子事务里——也就是说,函数里的
INSERT一定会先执行companies的插入,再执行departments的,完全不会出现乱序的情况。 - 针对你提到的RLS导致无法用
RETURNING的场景,你手动生成UUID的方案(第一个函数)是完全可行的,而且竞态条件的风险几乎可以忽略。
逐个分析你的函数
1. create_company_maybe_race_condition() 函数
你担心这个函数会有竞态,但实际上:
gen_random_uuid()是加密安全的随机生成器,生成重复UUID的概率低到可以忽略不计(远低于服务器硬件故障的概率),所以几乎不会出现两个并发调用生成相同company_id的情况。- 两个
INSERT属于同一个事务,PostgreSQL会保证事务的原子性:要么两个插入都成功,要么都回滚。而且在事务内部,第一个INSERT写入的companies行对当前事务是可见的,所以第二个INSERT的外键约束肯定能通过——PostgreSQL知道这两个操作的依赖关系,外键检查会正常工作。 - 你注释里写的“Postgres doesn't know that it depends on companies”是多虑了,外键约束本身就已经明确了这种依赖,事务内的可见性也保证了第二个插入能找到对应的公司行。
2. create_company_no_race_condition() 函数
这个函数的逻辑本身没问题,但在你的RLS场景下会直接失效:
- 因为RLS依赖权限表的访问控制列表,当你插入
companies行后,权限表还没有允许当前用户读取该行的条目,RETURNING子句会被RLS规则拦截,导致函数执行失败。这也是你说无法用RETURNING的根本原因,所以这个方案对你的场景不适用。
针对RLS场景的优化建议
既然不能用RETURNING,手动生成UUID的方案是最优解,不过可以加一点小优化让它更稳妥:
- 先把
companies表的company_id字段加上默认值,保持表结构的一致性:CREATE TABLE companies ( company_id uuid PRIMARY KEY DEFAULT gen_random_uuid(), ); - 如果你极端担心UUID重复(虽然完全没必要,但求个安心的话),可以在函数里加个简单的检查:
这里一定要注意:必须在插入CREATE FUNCTION create_company_safe () RETURNS void AS $$ DECLARE v_company_id uuid := gen_random_uuid(); BEGIN -- 极端场景下检查UUID是否已存在(可选) WHILE EXISTS (SELECT 1 FROM companies WHERE company_id = v_company_id) LOOP v_company_id := gen_random_uuid(); END LOOP; INSERT INTO companies (company_id) VALUES (v_company_id); -- 关键:先插入权限表的访问控制条目,确保后续操作符合RLS规则 INSERT INTO your_permission_table (user_id, company_id, permission) VALUES (current_user, v_company_id, 'read'); INSERT INTO departments (company_id, name) VALUES (v_company_id, 'My Department'); END; $$ LANGUAGE plpgsql;departments之前完成权限表的插入,否则departments的插入可能因为RLS对关联companies的权限检查而被拒绝。
总结
- PL/pgSQL内部的语句绝对不会乱序执行,事务的原子性已经帮你保障了数据一致性;
- 手动生成UUID的方案在你的RLS场景下完全可行,竞态条件的风险可以忽略;
- 记得在插入业务表之前先搞定权限表的条目,确保所有操作都符合RLS的规则。
内容的提问来源于stack exchange,提问作者BryceLarkin
相关产品推荐
相关产品推荐

