以源碼探索 PHP 查詢 MySQL 後儲存資料結果的方式
基於以下系列討論內容的一大串源碼追蹤。以印證討論內容。
Remember this: Open your mind, use the source.
Source code version: PHP-5.2.5 release
/ext/mysql/php_mysql.c
1.line: 1395-1400, context of mysql_query()
/* proto resource mysql_query(string query [, int link_identifier])
Sends an SQL query to MySQL */
PHP_FUNCTION(mysql_query)
{
php_mysql_do_query(INTERNAL_FUNCTION_PARAM_PASSTHRU, MYSQL_STORE_RESULT);
}
2.line: 1404-1409, context of mysql_unbuffered_query()
/* proto resource mysql_unbuffered_query(string query [, int link_identifier])
Sends an SQL query to MySQL, without fetching and buffering the result rows */
PHP_FUNCTION(mysql_unbuffered_query)
{
php_mysql_do_query(INTERNAL_FUNCTION_PARAM_PASSTHRU, MYSQL_USE_RESULT);
}
3.line: 1345-1359, context of php_mysql_do_query_general()
if(use_store == MYSQL_USE_RESULT) {
mysql_result=mysql_use_result(&mysql->conn);
} else {
mysql_result=mysql_store_result(&mysql->conn);
}
if (!mysql_result) {
if (PHP_MYSQL_VALID_RESULT(&mysql->conn)) { /* query should have returned rows */
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to save result set");
RETURN_FALSE;
} else {
RETURN_TRUE;
}
}
MySG(result_allocated)++;
ZEND_REGISTER_RESOURCE(return_value, mysql_result, le_result);
上面的源碼內容符合 PHP Manual, PHP Large result sets and summary tables 等文的解釋。當程序員透過 mysql_query()
查詢時,資料結果集(result set)會被儲存在 client (即PHP 這端)。而用 mysql_unbuffered_query()
時,則只會在 clinet 儲存 "current row"。
MySQL 5.1 Reference Manual
看完 PHP 的源碼,再看 MySQL 的部份,以進一步確認 MySQL 的源碼實際上是否符合前述解釋。
PHP 源碼顯示,PHP 的 mysql_query()
調用 MySQL C API 的 mysql_store_result()
,mysql_unbuffered_query()
調用 MySQL C API 的 mysql_use_result()
。故列示於下。
mysql_store_result()
mysql_store_result() reads the entire result of a query to the client, allocates a MYSQL_RES structure, and places the result into this structure.
在 client 端配置一個 MYSQL_RES 的結構體,並儲放資料結果於此結構中。
mysql_use_result()
mysql_use_result() initiates a result set retrieval but does not actually read the result set into the client like mysql_store_result() does. Instead, each row must be retrieved individually by making calls to mysql_fetch_row().
MYSQL_RES
This structure represents the result of a query that returns rows (SELECT, SHOW, DESCRIBE, EXPLAIN). The information returned from a query is called the result set in the remainder of this section.
Source code version: mysql-5.1.21-beta
接著我們看 MySQL 源碼以印證手冊說明。
/include/mysql.h
line: 316-332, context of MYSQL_RES
typedef struct st_mysql_res {
my_ulonglong row_count;
MYSQL_FIELD *fields;
MYSQL_DATA *data;
MYSQL_ROWS *data_cursor;
unsigned long *lengths; /* column lengths of current row */
MYSQL *handle; /* for unbuffered reads */
const struct st_mysql_methods *methods;
MYSQL_ROW row; /* If unbuffered read */
MYSQL_ROW current_row; /* buffer to current row */
MEM_ROOT field_alloc;
unsigned int field_count, current_field;
my_bool eof; /* Used by mysql_fetch_row */
/* mysql_stmt_close() had to cancel this result */
my_bool unbuffered_fetch_cancelled;
void *extension;
} MYSQL_RES;
MYSQL_RES 所包含的內容有二種情形。一種是 bufered, 另一種是 unbuffered 。剛好對應 mysql_store_result(), mysql_use_result()
的儲存策略。
/sql/client.c
繼續觀察 mysql_store_result()
的源碼內容。line: 2802-2844, context of mysql_store_result()
MYSQL_RES * STDCALL mysql_store_result(MYSQL *mysql)
{
MYSQL_RES *result;
DBUG_ENTER("mysql_store_result");
/* read from the actually used connection */
mysql = mysql->last_used_con;
if (!mysql->fields)
DBUG_RETURN(0);
if (mysql->status != MYSQL_STATUS_GET_RESULT)
{
set_mysql_error(mysql, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate);
DBUG_RETURN(0);
}
mysql->status=MYSQL_STATUS_READY; /* server is ready */
if (!(result=(MYSQL_RES*) my_malloc((uint) (sizeof(MYSQL_RES)+
sizeof(ulong) *
mysql->field_count),
MYF(MY_WME | MY_ZEROFILL))))
{
set_mysql_error(mysql, CR_OUT_OF_MEMORY, unknown_sqlstate);
DBUG_RETURN(0);
}
result->methods= mysql->methods;
result->eof=1; /* Marker for buffered */
result->lengths=(ulong*) (result+1);
if (!(result->data=
(*mysql->methods->read_rows)(mysql,mysql->fields,mysql->field_count)))
{
my_free((uchar*) result,MYF(0));
DBUG_RETURN(0);
}
mysql->affected_rows= result->row_count= result->data->rows;
result->data_cursor= result->data->data;
result->fields= mysql->fields;
result->field_alloc= mysql->field_alloc;
result->field_count= mysql->field_count;
/* The rest of result members is bzeroed in malloc */
mysql->fields=0; /* fields is now in result */
clear_alloc_root(&mysql->field_alloc);
/* just in case this was mistakenly called after mysql_stmt_execute() */
mysql->unbuffered_fetch_owner= 0;
DBUG_RETURN(result); /* Data fetched */
}
line: 1309-1312, context of cli_read_rows()
/* Read all rows (fields or data) from server */
MYSQL_DATA *cli_read_rows(MYSQL *mysql,MYSQL_FIELD *mysql_fields,
unsigned int fields)
源碼內容顯示 mysql_store_result()
會註記資料結果為 "buffered",並清除 "unbuffered" 策略的相關內容。藉由函數指標 read_rows
調用 cli_read_rows()
,將資料結果集讀取至 client 。
結論
在前一系列的討論中,我們的出發點是 CakePHP 的資料庫存取行為,因為 CakePHP 使用的是 mysql_query()
,而非 mysql_unbuffered_query()
,所以我們對資料結果集的記憶體配置方式,也是針對 mysql_query()
。事實上,絕大多數的案例中, PHP 程序員使用的都是 mysql_query()
而非 mysql_unbuffered_query()
。
我在稍後的回應中也說明,PHP 的 MySQL 函數,可以區分2種儲存策略 (即mysql_query()
與 mysql_unbuffered_query()
)。不過 FIEND 顯然忽視這點,隻字未提mysql_unbuffered_query()
,始終堅持他的認知: "資料結果應該是儲存在 DB 端"。亦即他認為只有一種策略。
Ok, 也許資料庫管理與規劃書籍介紹的策略只有一種,但那是通則、是理論。但此處討論的卻是 PHP 查詢 MySQL 的實例。我說明的內容,並不是我的個人認知,而是程式碼明擺著的事實。如果 FIEND 還要說誰的 DB觀念不好,在誤導別人,請去對 PHP 和 MySQL 的開發團隊說吧。
樂多舊回應