作者:杨向博
我们接触到的数据库,基本都提供了只读实例的功能。在业务允许的情况下可以将一些读取数据的请求下发至只读实例,减缓primary的压力,以获得更佳的性能。
物理从库只读已经是一个常识问题,在PostgreSQL中具体是怎么实现的呢,一起来看看。
一、报错分析
从报错入手分析,在只读库里执行写操作,报错如下:
postgres=# select * into tbl_t from tbl_test;
ERROR: cannot execute SELECT INTO in a read-only transaction
postgres=#
报错的函数为:
/*
* PreventCommandIfReadOnly: throw error if XactReadOnly
*
* This is useful partly to ensure consistency of the error message wording;
* some callers have checked XactReadOnly for themselves.
*/
void
PreventCommandIfReadOnly(const char *cmdname)
{
if (XactReadOnly)
ereport(ERROR,
(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
/* translator: %s is name of a SQL command, eg CREATE */
errmsg('cannot execute %s in a read-only transaction',
cmdname)));
}
当XactReadOnly为true时,就抛出报错,XactReadOnly是一个Bool类型的全局变量。那这里大概明白设计思路了,当执行一些操作时调用PreventCommandIfReadOnly函数,如果库是只读状态进行写操作就抛出报错。
报错堆栈:
(gdb) bt
#0 PreventCommandIfReadOnly (cmdname=0xbab6f8 'SELECT INTO') at utility.c:409
#1 0x00000000008c24a5 in standard_ProcessUtility (pstmt=0x1faef00, queryString=0x1fae098 'select * into tbl_t from tbl_test;', context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0, dest=0x1fafa18, qc=0x7ffc8ac8a180)
at utility.c:566
#2 0x00000000008c23cc in ProcessUtility (pstmt=0x1faef00, queryString=0x1fae098 'select * into tbl_t from tbl_test;', context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0, dest=0x1fafa18, qc=0x7ffc8ac8a180)
at utility.c:524
#3 0x00000000008c14e2 in PortalRunUtility (portal=0x1fffa58, pstmt=0x1faef00, isTopLevel=true, setHoldSnapshot=false, dest=0x1fafa18, qc=0x7ffc8ac8a180) at pquery.c:1157
#4 0x00000000008c16ce in PortalRunMulti (portal=0x1fffa58, isTopLevel=true, setHoldSnapshot=false, dest=0x1fafa18, altdest=0x1fafa18, qc=0x7ffc8ac8a180) at pquery.c:1303
#5 0x00000000008c0c6c in PortalRun (portal=0x1fffa58, count=9223372036854775807, isTopLevel=true, run_once=true, dest=0x1fafa18, altdest=0x1fafa18, qc=0x7ffc8ac8a180) at pquery.c:779
#6 0x00000000008bb204 in exec_simp