postgres數據庫是開源數據庫,數據庫底層是通過C語言來實現的。這裏介紹使用eclipse的debug方式來動態跟蹤postgres中一個事務調用的開始過程(StartTransactionCommand)。
/* ----------------------------------------------------------------
* PostgresMain
* postgres main loop -- all backends, interactive or otherwise start here
*
* argc/argv are the command line arguments to be used. (When being forked
* by the postmaster, these are not the original argv array of the process.)
* dbname is the name of the database to connect to, or NULL if the database
* name should be extracted from the command line arguments or defaulted.
* username is the PostgreSQL user name to be used for the session.
* ----------------------------------------------------------------
*/
void
PostgresMain(int argc, char *argv[],
const char *dbname,
const char *username)
{
.
.--//中間略
.
switch (firstchar)
{
case 'Q': /* simple query */
{
const char *query_string;
/* Set statement_timestamp() */
SetCurrentStatementStartTimestamp();
query_string = pq_getmsgstring(&input_message);
pq_getmsgend(&input_message);
if (am_walsender)
{
if (!exec_replication_command(query_string))
exec_simple_query(query_string);
}
else
exec_simple_query(query_string);
.
.--//中間略
.
}
--//中間有一個exec_simple_query函數調用
--//開啓一個會話
--//使用eclipse調試debug6561進程號
--//4179行和4182行添加斷點
--//6561會話執行一條insert 語句
--//查看eclipse界面
--//查看exec_simple_query函數
/*
* exec_simple_query
*
* Execute a "simple Query" protocol message.
*/
static void
exec_simple_query(const char *query_string)
{
.
.--//中間略
.
/*
* Start up a transaction command. All queries generated by the
* query_string will be in this same command block, *unless* we find a
* BEGIN/COMMIT/ABORT statement; we have to force a new xact command after
* one of those, else bad things will happen in xact.c. (Note that this
* will normally change current memory context.)
*/
start_xact_command(); //調用start_xact_command啓動一個事務
.
.--//中間略
.
}
--//exec_simple_query中調用了start_xact_command();函數
--//在start_xact_command 函數上加斷點
--//查看start_xact_command函數
/*
* Convenience routines for starting/committing a single command.
*/
static void
start_xact_command(void)
{
if (!xact_started)
{
StartTransactionCommand(); //--調用StartTransactionCommand函數啓動一個事務
xact_started = true;
}
/*
* Start statement timeout if necessary. Note that this'll intentionally
* not reset the clock on an already started timeout, to avoid the timing
* overhead when start_xact_command() is invoked repeatedly, without an
* interceding finish_xact_command() (e.g. parse/bind/execute). If that's
* not desired, the timeout has to be disabled explicitly.
*/
enable_statement_timeout();
}
--//在StartTransactionCommand()處加上斷點
--//查看StartTransactionCommand()函數
/*
* StartTransactionCommand
*/
void
StartTransactionCommand(void)
{
//--初始化事物信息(具體初始化可以參考)
TransactionState s = CurrentTransactionState;
switch (s->blockState)
{
/*
* if we aren't in a transaction block, we just do our usual start
* transaction.
*/
case TBLOCK_DEFAULT:
StartTransaction(); --//調用啓動事務函數
s->blockState = TBLOCK_STARTED;
break;
/*
* We are somewhere in a transaction block or subtransaction and
* about to start a new command. For now we do nothing, but
* someday we may do command-local resource initialization. (Note
* that any needed CommandCounterIncrement was done by the
* previous CommitTransactionCommand.)
*/
case TBLOCK_INPROGRESS:
case TBLOCK_IMPLICIT_INPROGRESS:
case TBLOCK_SUBINPROGRESS:
break;
/*
* Here we are in a failed transaction block (one of the commands
* caused an abort) so we do nothing but remain in the abort
* state. Eventually we will get a ROLLBACK command which will
* get us out of this state. (It is up to other code to ensure
* that no commands other than ROLLBACK will be processed in these
* states.)
*/
case TBLOCK_ABORT:
case TBLOCK_SUBABORT:
break;
/* These cases are invalid. */
case TBLOCK_STARTED:
case TBLOCK_BEGIN:
case TBLOCK_PARALLEL_INPROGRESS:
case TBLOCK_SUBBEGIN:
case TBLOCK_END:
case TBLOCK_SUBRELEASE:
case TBLOCK_SUBCOMMIT:
case TBLOCK_ABORT_END:
case TBLOCK_SUBABORT_END:
case TBLOCK_ABORT_PENDING:
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBRESTART:
case TBLOCK_SUBABORT_RESTART:
case TBLOCK_PREPARE:
elog(ERROR, "StartTransactionCommand: unexpected state %s",
BlockStateAsString(s->blockState));
break;
}
/*
* We must switch to CurTransactionContext before returning. This is
* already done if we called StartTransaction, otherwise not.
*/
Assert(CurTransactionContext != NULL);
MemoryContextSwitchTo(CurTransactionContext); --//設置事務上下文信息
}
--//其中TransactionState是TransactionStateData類型的結構體指針
--//定義如下
--//typedef TransactionStateData *TransactionState;
--//位置如下
[root@postgres backend]# grep -nir 'typedef TransactionStateData' ./
./access/transam/xact.c:194:typedef TransactionStateData *TransactionState;
[root@postgres backend]#
--//TransactionStateData爲一個結構體
文件./access/transam/xact.c
/*
* transaction state structure
*/
typedef struct TransactionStateData
{
TransactionId transactionId; /* my XID, or Invalid if none */
SubTransactionId subTransactionId; /* my subxact ID */
char *name; /* savepoint name, if any */
int savepointLevel; /* savepoint level */
TransState state; /* low-level state */
TBlockState blockState; /* high-level state */
int nestingLevel; /* transaction nesting depth */
int gucNestLevel; /* GUC context nesting depth */
MemoryContext curTransactionContext; /* my xact-lifetime context */
ResourceOwner curTransactionOwner; /* my query resources */
TransactionId *childXids; /* subcommitted child XIDs, in XID order */
int nChildXids; /* # of subcommitted child XIDs */
int maxChildXids; /* allocated size of childXids[] */
Oid prevUser; /* previous CurrentUserId setting */
int prevSecContext; /* previous SecurityRestrictionContext */
bool prevXactReadOnly; /* entry-time xact r/o state */
bool startedInRecovery; /* did we start in recovery? */
bool didLogXid; /* has xid been included in WAL record? */
int parallelModeLevel; /* Enter/ExitParallelMode counter */
struct TransactionStateData *parent; /* back link to parent */
} TransactionStateData;
--//CurrentTransactionState爲一個已經初始化的結構體
static TransactionState CurrentTransactionState = &TopTransactionStateData;
--//TopTransactionStateData的定義爲
/*
* CurrentTransactionState always points to the current transaction state
* block. It will point to TopTransactionStateData when not in a
* transaction at all, or when in a top-level transaction.
*/
static TransactionStateData TopTransactionStateData = {
0, /* transaction id */
0, /* subtransaction id */
NULL, /* savepoint name */
0, /* savepoint level */
TRANS_DEFAULT, /* transaction state */
TBLOCK_DEFAULT, /* transaction block state from the client
* perspective */
0, /* transaction nesting depth */
0, /* GUC context nesting depth */
NULL, /* cur transaction context */
NULL, /* cur transaction resource owner */
NULL, /* subcommitted child Xids */
0, /* # of subcommitted child Xids */
0, /* allocated size of childXids[] */
InvalidOid, /* previous CurrentUserId setting */
0, /* previous SecurityRestrictionContext */
false, /* entry-time xact r/o state */
false, /* startedInRecovery */
false, /* didLogXid */
0, /* parallelModeLevel */
NULL /* link to parent state block */
};
--//StartTransaction()上設置斷點
--//查看此時的變量信息,這些信息正是變量TopTransactionStateData的值
--//查看StartTransaction()
/*
* StartTransaction
*/
static void
StartTransaction(void)
{
TransactionState s; //--定義TransactionState變量
VirtualTransactionId vxid;
/*
* Let's just make sure the state stack is empty
*/
s = &TopTransactionStateData; //--初始化爲TopTransactionStateData結構體的值
CurrentTransactionState = s; //--賦值給CurrentTransactionState變量
Assert(XactTopTransactionId == InvalidTransactionId);
/*
* check the current transaction state
*/
if (s->state != TRANS_DEFAULT)
elog(WARNING, "StartTransaction while in %s state",
TransStateAsString(s->state));
/*
* set the current transaction state information appropriately during
* start processing
* 在啓動處理期間適當地設置當前事務狀態信息
*/
s->state = TRANS_START;
s->transactionId = InvalidTransactionId; /* until assigned 直到分配 */
/*
* Make sure we've reset xact state variables
*
* If recovery is still in progress, mark this transaction as read-only.
* We have lower level defences in XLogInsert and elsewhere to stop us
* from modifying data during recovery, but this gives the normal
* indication to the user that the transaction is read-only.
* 確保我們已經重置了xact狀態變量,如果恢復仍在進行中,將該事務標記爲只讀。我們在XLogInsert和其他地方有較低級別的防禦機制來阻止我們在恢復期* 間修改數據,但是這向用戶提供了事務是隻讀的正常指示。
*/
if (RecoveryInProgress())
{
s->startedInRecovery = true;
XactReadOnly = true;
}
else
{
s->startedInRecovery = false;
XactReadOnly = DefaultXactReadOnly;
}
XactDeferrable = DefaultXactDeferrable; //DefaultXactDeferrable=false
XactIsoLevel = DefaultXactIsoLevel; //DefaultXactIsoLevel = XACT_READ_COMMITTED
forceSyncCommit = false;
MyXactFlags = 0;
/*
* reinitialize within-transaction counters 重新啓動within-transaction計數器
*/
s->subTransactionId = TopSubTransactionId; //TopSubTransactionId=1
currentSubTransactionId = TopSubTransactionId; //TopSubTransactionId=1
currentCommandId = FirstCommandId; //FirstCommandId=0
currentCommandIdUsed = false;
/*
* initialize reported xid accounting 初始化報告的xid
*/
nUnreportedXids = 0;
s->didLogXid = false; //*has xid been included in WAL record (xid是否包含在WAL record中)
/*
* must initialize resource-management stuff first 必須先初始化資源管理
*/
//創建事物上下文
AtStart_Memory();
//設置事物owner
AtStart_ResourceOwner();
/*
* Assign a new LocalTransactionId, and combine it with the backendId to
* form a virtual transaction id.
* 分配一個新的LocalTransactionId,並將其與backendId組合起來形成一個虛擬事務id。
*/
vxid.backendId = MyBackendId;
vxid.localTransactionId = GetNextLocalTransactionId();
/*
* Lock the virtual transaction id before we announce it in the proc array
* 在將虛擬事務id聲明到proc數組之前鎖定它
*/
VirtualXactLockTableInsert(vxid);
/*
* Advertise it in the proc array. We assume assignment of
* LocalTransactionID is atomic, and the backendId should be set already.
* 在proc數組中發佈它。我們假設LocalTransactionID的賦值是原子的,並且應該已經設置了backendId。
*/
Assert(MyProc->backendId == vxid.backendId);
MyProc->lxid = vxid.localTransactionId;
TRACE_POSTGRESQL_TRANSACTION_START(vxid.localTransactionId);
/*
* set transaction_timestamp() (a/k/a now()). Normally, we want this to
* be the same as the first command's statement_timestamp(), so don't do a
* fresh GetCurrentTimestamp() call (which'd be expensive anyway). But
* for transactions started inside procedures (i.e., nonatomic SPI
* contexts), we do need to advance the timestamp. Also, in a parallel
* worker, the timestamp should already have been provided by a call to
* SetParallelStartTimestamps().
*/
if (!IsParallelWorker())
{
if (!SPI_inside_nonatomic_context())
xactStartTimestamp = stmtStartTimestamp;
else
xactStartTimestamp = GetCurrentTimestamp();
}
else
Assert(xactStartTimestamp != 0);
pgstat_report_xact_timestamp(xactStartTimestamp);
/* Mark xactStopTimestamp as unset. */
xactStopTimestamp = 0;
/*
* initialize current transaction state fields
*
* note: prevXactReadOnly is not used at the outermost level
*/
s->nestingLevel = 1;
s->gucNestLevel = 1;
s->childXids = NULL;
s->nChildXids = 0;
s->maxChildXids = 0;
GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext);
/* SecurityRestrictionContext should never be set outside a transaction */
Assert(s->prevSecContext == 0);
/*
* initialize other subsystems for new transaction
*/
AtStart_GUC(); //--
AtStart_Cache();
AfterTriggerBeginXact();
/*
* done with start processing, set current transaction state to "in
* progress"
*/
s->state = TRANS_INPROGRESS;
ShowTransactionState("StartTransaction");
}
--//進入StartTransaction函數單步調試
--//查看ShowTransactionState函數
/*
* ShowTransactionState
* Debug support
*/
static void
ShowTransactionState(const char *str)
{
/* skip work if message will definitely not be printed */
if (log_min_messages <= DEBUG5 || client_min_messages <= DEBUG5)
ShowTransactionStateRec(str, CurrentTransactionState);
}
--//查看ShowTransactionStateRec函數
/*
* ShowTransactionStateRec
* Recursive subroutine for ShowTransactionState
*/
static void
ShowTransactionStateRec(const char *str, TransactionState s)
{
StringInfoData buf;
initStringInfo(&buf);
if (s->nChildXids > 0)
{
int i;
appendStringInfo(&buf, ", children: %u", s->childXids[0]);
for (i = 1; i < s->nChildXids; i++)
appendStringInfo(&buf, " %u", s->childXids[i]);
}
if (s->parent)
ShowTransactionStateRec(str, s->parent);
/* use ereport to suppress computation if msg will not be printed */
ereport(DEBUG5,
(errmsg_internal("%s(%d) name: %s; blockState: %s; state: %s, xid/subid/cid: %u/%u/%u%s%s",
str, s->nestingLevel,
PointerIsValid(s->name) ? s->name : "unnamed",
BlockStateAsString(s->blockState),
TransStateAsString(s->state),
(unsigned int) s->transactionId,
(unsigned int) s->subTransactionId,
(unsigned int) currentCommandId,
currentCommandIdUsed ? " (used)" : "",
buf.data)));
pfree(buf.data);
}
--//report提示打印debug5的信息,目前數據庫日誌級別是配置的debug5
postgres=# show client_min_messages;
DEBUG: StartTransaction(1) name: unnamed; blockState: DEFAULT; state: INPROGRESS, xid/subid/cid: 0/1/0
DEBUG: CommitTransaction(1) name: unnamed; blockState: STARTED; state: INPROGRESS, xid/subid/cid: 0/1/0
client_min_messages
---------------------
debug5
(1 row)
postgres=# show log_min_messages;
DEBUG: StartTransaction(1) name: unnamed; blockState: DEFAULT; state: INPROGRESS, xid/subid/cid: 0/1/0
DEBUG: CommitTransaction(1) name: unnamed; blockState: STARTED; state: INPROGRESS, xid/subid/cid: 0/1/0
log_min_messages
------------------
debug5
(1 row)
postgres=#
--//查看會話信息已經和eclipse看到的同步,到了ereport(DEBUG5..)
上面使用eclipse演示了postgres中事物開始的一個過程,通過eclipse加斷點的方式可以一步步的跟蹤函數調用,這樣對於源碼的學習會更方便。上面的演示中insert語句並沒有完成,因爲這裏還有好多函數沒有判斷完,比如enable_statement_timeout()、CommitTransactionCommand()等相關的函數調用在這裏沒有演示。其中在StartTransaction()函數中的AtStart_Memory()函數是關於postgres的MemoryContext內存上下文的申請和釋放問題,其過程也相當複雜。具體分析可以參考鏈接:https://blog.csdn.net/m15217321304/article/details/105476959