点击蓝字 阅读更多干货
PostgreSQL11开始支持了存储过程的写法,同时也允许了在存储过程中嵌入事务。今天刚好碰到一个相关的问题。
do language plpgsql $$BEGINFOR i IN 0..9 LOOPINSERT INTO t1(id) VALUES (i);IF i % 2 = 0 THENCOMMIT;ELSEROLLBACK;END IF;END LOOP;EXCEPTIONWHEN others THENinsert into t1 values(100);END;$$;
上面这段存储过程大家可以先想想执行完会是什么结果?
从字面看显然就是把i % 2 = 0的数插入到t1表中了,但事实却是:
bill=# do language plpgsql $$BEGINFOR i IN 0..9 LOOPINSERT INTO t1(id) VALUES (i);IF i % 2 = 0 THENCOMMIT;ELSEROLLBACK;END IF;END LOOP;EXCEPTIONWHEN others THENinsert into t1 values(100);END;$$;DObill=# select * from t1;id-----100(1 row)
竟然上面的循环部分全部跳过了,直接执行了异常捕获部分的语句。
为什么会这样呢?可能比较熟悉的人会知道,PG的存储过程中捕获异常和commit/rollback是会冲突的,如下:
bill=# do language plpgsql $$bill$# beginbill$# beginbill$# insert into t1(id) values (1);bill$# commit;bill$# insert into t1(id) values (1/0);bill$# rollback;bill$# exceptionbill$# when division_by_zero thenbill$# raise notice 'caught division_by_zero';bill$# end;bill$# end;bill$# $$;ERROR: cannot commit while a subtransaction is activeCONTEXT: PL/pgSQL function inline_code_block line 5 at COMMIT
那么问题来了,为什么前面的语句没有报错呢,而是执行了异常部分的语句?
通过对比我们发现,关键在于异常捕获的对象名称。在PG中特殊的条件名OTHERS匹配除了QUERY_CANCELED和ASSERT_FAILURE之外的所有错误类型,但是这又有什么不同呢?
经过跟踪发现两个SQL调用的堆栈都是一样的,而且执行的结果也是一样:
(lldb) b _SPI_commitBreakpoint 1: where = postgres`_SPI_commit + 10 at spi.c:225:6, address = 0x0000000101b4a52a(lldb) cProcess 62848 resumingpostgres was compiled with optimization - stepping may behave oddly; variables may not be available.Process 62848 stopped* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1frame #0: 0x0000000101b4a52a postgres`_SPI_commit(chain=<unavailable>) at spi.c:225:6 [opt]222 {223 MemoryContext oldcontext = CurrentMemoryContext;224-> 225 if (_SPI_current->atomic)226 ereport(ERROR,227 (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),228 errmsg("invalid transaction termination")));Target 0: (postgres) stopped.(lldb) nProcess 62848 stopped* thread #1, queue = 'com.apple.main-thread', stop reason = step overframe #0: 0x0000000101b4a543 postgres`_SPI_commit(chain=<unavailable>) at spi.c:239:6 [opt]236 * this restriction would have to be refined or the check possibly be237 * moved out of SPI into the PLs.238 */-> 239 if (IsSubTransaction())240 ereport(ERROR,241 (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),242 errmsg("cannot commit while a subtransaction is active")));Target 0: (postgres) stopped.(lldb) nProcess 62848 stopped* thread #1, queue = 'com.apple.main-thread', stop reason = step overframe #0: 0x0000000101b4a5ad postgres`_SPI_commit(chain=<unavailable>) at spi.c:240:3 [opt]237 * moved out of SPI into the PLs.238 */239 if (IsSubTransaction())-> 240 ereport(ERROR,241 (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),242 errmsg("cannot commit while a subtransaction is active")));243Target 0: (postgres) stopped.(lldb) p IsSubTransaction(bool (*)()) $0 = 0x00000001019fe8e0 (postgres`IsSubTransaction at xact.c:4729)
按理说EXCEPTION WHEN others最终也应该是输出"cannot commit while a subtransaction is active"的错误,可却没有报错,而是执行了错误的命令,这就十分奇怪了。
总结:
在PostgreSQL的存储过程中,事务的commit/rollback是会和异常捕获冲突的,会抛出"cannot commit while a subtransaction is active"的错误。
但是当异常捕获的选项为OTHERS时,竟然没有抛出错误,而是直接执行了异常捕获部分的内容。目前看来像是个BUG,后续再继续研究下。

觉得内容还不错的话,给我点个“在看”呗

文章转载自 晟数学苑,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。





