PHP源码之 ext/mysql扩展部分


Posted in PHP onJuly 17, 2009

我写过一个外部模块扩展,现在开始看PHP源码中的mysql扩展,它是可以被集成到PHP内部的,所以应该算是内置的扩展了。
该扩展需要用到mysql数据库提供的一些接口,所以需要安装了mysql,并能够确定mysql.h的位置。
该扩展的位置一般在 PHP-source-code/ext/mysql 下。
在linux下,主要需要注意的文件是: config.m4, php_mysql.c, php_mysql_structs.h。
ps:该目录下有tags文件,所以可以利用ctags的各种特性,直接找到函数、宏定义等。
ps:linux下mysql的启动 sudo mysql-dir/bin/mysqld_safe &
之后会有两个进程运行:

root 5297 0.0 0.0 5920 1416 pts/5 S 11:08 0:00 /bin/sh /usr/local/mysql/bin/mysqld_safe 
mysql 5320 1.4 1.1 202728 23796 pts/5 Sl 11:08 1:47 /usr/local/mysql/libexec/mysqld --basedir=/usr/local/mysql --datadir=/usr/local/mysql/var --user=mysql --pid-file=/usr/local/mysql/var/tj1clnxweb0004.pid --skip-external-locking --port=3306 --socket=/tmp/mysql.sock

----------------------------------------------
以下先记录阅读过程中的一些细节问题:
1、php_mysql_do_query_general函数
该扩展提供的函数mysql_query和mysql_unbuffered_query最后都要用到php_mysql_do_query_general来执行核心功能。
首先看下trace模式:
if (MySG(trace_mode)) { .... }
在php.ini中有配置:
mysql.trace_mode = Off
而如果配置是打开的,那么就会执行if中的句子,而如果执行的句子是select的话,就会在前面加上explain,分析sql句子的性能。
然后看一下mysql_use_result和mysql_store_result的区别:
可以看到,mysql_query使用的是mysql_store_result函数,而mysql_unbuffered_query是用的是mysql_use_result。
参考文章(http://school.cnd8.com/mysql/jiaocheng/25143_8.htm),并总结如下:
mysql_store_result 查询并获取所有的结果集,保存在客户端,准备供客户端使用,这样对于客户端的内存和性能要求较大。
mysql_use_result 仅查询,而将结果获取延迟。相当于是在服务前端维护了一个结果集。
当调用完mysql_store_result ,使用mysql_fetch_row获取结果时,是直接从客户端获取结果,如果返回为NULL,就是没有结果了。
而当调用完mysql_use_result,使用mysql_fetch_row获取结果时,是从服务前端获取结果,如果返回为NULL,那么可能是没用结果了,也可能是网络连接出错等原因。
由于结果集的维护地方不同,mysql_store_result 的结果可以提供更多的处理函数,比如任意的seek、获取总数、非顺序访问等。而mysql_use_result的结果集就不可以。
另外,由于mysql_use_result的结果集是维持在服务器端,那么它就提出一个要求:客户端对结果集中的每一行都必须调用mysql_fetch_row,否则,结果集中剩余的记录就会成为下一个查询结果集中的一部分,并且发生“不同步”的错误。
那么,为什么还要用到mysql_use_result呢?看下这个情况:
mysql 和mysqldump 缺省时,使用mysql_store_result,但是如果指定--quick 选项,则使用mysql_use_result。
那说明mysql_use_result在效率方面占有优势?
看下mysql的帮助手册:
-q, --quick Don't cache result, print it row by row. This may slow
down the server if the output is suspended. Doesn't use
history file.
mysqldump的帮助手册:
-q, --quick Don't buffer query, dump directly to stdout.
那么在我没有彻底弄明白为什么quick对应着mysql_use_result的时候,先搞明白什么时候不要用mysql_use_result吧。由于mysql_use_result的结果集维护在服务器端,那么如果客户端程序可能被挂起,别用它。如果结果集的行与行之间有过多操作,别用它。也就是一句话,如果查询完,不是立马用完结果,free掉,那么就别用mysql_use_result。
为了尝试一下效果,写了以下测试代码:
$sql = sprintf("select * from pet;"); 
$result = mysql_unbuffered_query($sql, $conn); 
$rows = mysql_fetch_row($result); 
var_dump($rows); 
$sql = sprintf("select * from shop"); 
$result = mysql_unbuffered_query($sql, $conn); 
$rows = mysql_fetch_row($result); 
var_dump($rows);

执行的结果是,第二次fetch不会显示第一次的结果,但是php会报notice:
PHP Notice: mysql_unbuffered_query(): Function called without first fetching all rows from a previous unbuffered query in /home/yicheng/test-all/mysqltest/test.php on line 28
修改测试代码:
$i = 1000000; 
while($i--){ 
$sql = sprintf("select * from pet;"); 
$result = mysql_query($sql, $conn); 
#$result = mysql_unbuffered_query($sql, $conn); 
while($rows = mysql_fetch_row($result)){ } 
if ($result){ 
mysql_free_result($result); 
} 
}

使用unbuffered的结果:
:!time ./test.php
real 1m10.220s
user 0m17.853s
sys 0m9.541s
使用mysql_query的结果:
:!time ./test.php
real 1m11.191s
user 0m19.297s
sys 0m10.133s
貌似时间差别也不大嘛
2、一些资源相关的宏定义
#define ZEND_VERIFY_RESOURCE(rsrc) \ 
if (!rsrc) { \ 
RETURN_FALSE; \ 
} 
#define ZEND_FETCH_RESOURCE(rsrc, rsrc_type, passed_id, default_id, resource_type_name, resource_type) \ 
rsrc = (rsrc_type) zend_fetch_resource(passed_id TSRMLS_CC, default_id, resource_type_name, NULL, 1, resource_type); \ 
ZEND_VERIFY_RESOURCE(rsrc); 
#define ZEND_FETCH_RESOURCE2(rsrc, rsrc_type, passed_id, default_id, resource_type_name, resource_type1, resource_type2) \ 
rsrc = (rsrc_type) zend_fetch_resource(passed_id TSRMLS_CC, default_id, resource_type_name, NULL, 2, resource_type1, resource_type2); \ 
ZEND_VERIFY_RESOURCE(rsrc); 
#define ZEND_REGISTER_RESOURCE(rsrc_result, rsrc_pointer, rsrc_type) \ 
zend_register_resource(rsrc_result, rsrc_pointer, rsrc_type); 
#define ZEND_GET_RESOURCE_TYPE_ID(le_id, le_type_name) \ 
if (le_id == 0) { \ 
le_id = zend_fetch_list_dtor_id(le_type_name); \ 
}

我们由mysql_connect函数返回的其实是一个link id(resource(4) of type (mysql link)),通过ZEND_FETCH_RESOURCE和ZEND_FETCH_RESOURCE2宏,可以映射到对应的mysql资源上去。这两个宏都调用了zend_fetch_resource方法,所以下面我们看下这个方法。
ZEND_API void *zend_fetch_resource(zval **passed_id TSRMLS_DC, int default_id, char *resource_type_name, int *found_resource_type, int num_resource_types, ...)
比如在mysql_list_dbs函数中调用ZEND_FETCH_RESOURCE2宏:
ZEND_FETCH_RESOURCE2(mysql, php_mysql_conn *, mysql_link, id, "MySQL-Link", le_link, le_plink);
其中mysql保存了返回的有效资源,php_mysql_conn *定义了返回资源的类型,mysql_link, id分别对应着passed_id 和 default_id(因为很多函数调用,不传入具体的conn,就是使用default值),"MySQL-Link"是resource_type_name,le_link, le_plink是zend_fetch_resource的...部分,它们俩是static int类型的值。
由zend_fetch_resource可以看出,resource(4) of type (mysql link)的value.lval中包含了long型的id。如果default_id为-1,那么就是用passed_id传入的id,否则就使用default_id作为id,利用zend_list_find来寻找其对应的资源。
看了几个函数之后,其实该扩展也就是对mysql提供的c接口的封装而已。但是封装的很规范也很稳定!
如果想了解进一步,那还是得看MYSQL的源代码。下面贴了几个重要的数据结构。
-----------------------------------------
一些对于查错可能有用的php函数:
error_reporting(E_ALL); 
#var_dump(mysql_get_host_info($conn)); 
#var_dump(mysql_get_proto_info($conn)); 
#var_dump(mysql_get_server_info($conn)); 
#var_dump(mysql_stat($conn)); 
#var_dump(mysql_errno($conn)); 
#var_dump(mysql_error($conn)); 
#var_dump(mysql_info($conn));

--------------------------------------------
MYSQL源码中一些有用的struct
typedef struct st_mysql 
{ 
NET net; /* Communication parameters */ 
gptr connector_fd; /* ConnectorFd for SSL */ 
char *host,*user,*passwd,*unix_socket,*server_version,*host_info,*info; 
char *db; 
struct charset_info_st *charset; 
MYSQL_FIELD *fields; 
MEM_ROOT field_alloc; 
my_ulonglong affected_rows; 
my_ulonglong insert_id; /* id if insert on table with NEXTNR */ 
my_ulonglong extra_info; /* Not used */ 
unsigned long thread_id; /* Id for connection in server */ 
unsigned long packet_length; 
unsigned int port; 
unsigned long client_flag,server_capabilities; 
unsigned int protocol_version; 
unsigned int field_count; 
unsigned int server_status; 
unsigned int server_language; 
unsigned int warning_count; 
struct st_mysql_options options; 
enum mysql_status status; 
my_bool free_me; /* If free in mysql_close */ 
my_bool reconnect; /* set to 1 if automatic reconnect */ 
/* session-wide random string */ 
char scramble[SCRAMBLE_LENGTH+1]; 
/* 
Set if this is the original connection, not a master or a slave we have 
added though mysql_rpl_probe() or mysql_set_master()/ mysql_add_slave() 
*/ 
my_bool rpl_pivot; 
/* 
Pointers to the master, and the next slave connections, points to 
itself if lone connection. 
*/ 
struct st_mysql* master, *next_slave; 
struct st_mysql* last_used_slave; /* needed for round-robin slave pick */ 
/* needed for send/read/store/use result to work correctly with replication */ 
struct st_mysql* last_used_con; 
LIST *stmts; /* list of all statements */ 
const struct st_mysql_methods *methods; 
void *thd; 
/* 
Points to boolean flag in MYSQL_RES or MYSQL_STMT. We set this flag 
from mysql_stmt_close if close had to cancel result set of this object. 
*/ 
my_bool *unbuffered_fetch_owner; 
#if defined(EMBEDDED_LIBRARY) || defined(EMBEDDED_LIBRARY_COMPATIBLE) || MYSQL_VERSION_ID >= 50100 
/* needed for embedded server - no net buffer to store the 'info' */ 
char *info_buffer; 
#endif 
} MYSQL; 
typedef struct st_mysql_methods 
{ 
my_bool (*read_query_result)(MYSQL *mysql); 
my_bool (*advanced_command)(MYSQL *mysql, 
enum enum_server_command command, 
const char *header, 
unsigned long header_length, 
const char *arg, 
unsigned long arg_length, 
my_bool skip_check, 
MYSQL_STMT *stmt); 
MYSQL_DATA *(*read_rows)(MYSQL *mysql,MYSQL_FIELD *mysql_fields, 
unsigned int fields); 
MYSQL_RES * (*use_result)(MYSQL *mysql); 
void (*fetch_lengths)(unsigned long *to, 
MYSQL_ROW column, unsigned int field_count); 
void (*flush_use_result)(MYSQL *mysql); 
#if !defined(MYSQL_SERVER) || defined(EMBEDDED_LIBRARY) 
MYSQL_FIELD * (*list_fields)(MYSQL *mysql); 
my_bool (*read_prepare_result)(MYSQL *mysql, MYSQL_STMT *stmt); 
int (*stmt_execute)(MYSQL_STMT *stmt); 
int (*read_binary_rows)(MYSQL_STMT *stmt); 
int (*unbuffered_fetch)(MYSQL *mysql, char **row); 
void (*free_embedded_thd)(MYSQL *mysql); 
const char *(*read_statistics)(MYSQL *mysql); 
my_bool (*next_result)(MYSQL *mysql); 
int (*read_change_user_result)(MYSQL *mysql, char *buff, const char *passwd); 
int (*read_rows_from_cursor)(MYSQL_STMT *stmt); 
#endif 
} MYSQL_METHODS;
PHP 相关文章推荐
用PHP和ACCESS写聊天室(七)
Oct 09 PHP
再次研究下cache_lite
Feb 14 PHP
MySQL授权问题总结
May 06 PHP
php 生成饼图 三维饼图
Sep 28 PHP
PHP中mb_convert_encoding与iconv函数的深入解析
Jun 21 PHP
ThinkPHP表单自动提交验证实例教程
Jul 18 PHP
phpnow php探针环境检测代码
Nov 04 PHP
Yii学习总结之数据访问对象 (DAO)
Feb 22 PHP
Yii扩展组件编写方法实例分析
Jun 29 PHP
PHP+ajax实现获取新闻数据简单示例
May 08 PHP
PHP迭代器和生成器用法实例分析
Sep 28 PHP
Laravel开启跨域请求的方法
Oct 13 PHP
php 小乘法表实现代码
Jul 16 #PHP
php at(@)符号的用法简介
Jul 11 #PHP
php str_pad 函数用法简介
Jul 11 #PHP
php strlen mb_strlen计算中英文混排字符串长度
Jul 10 #PHP
UTF8编码内的繁简转换的PHP类
Jul 09 #PHP
PHP 程序授权验证开发思路
Jul 09 #PHP
php 论坛采集程序 模拟登陆,抓取页面 实现代码
Jul 09 #PHP
You might like
PHP实现的oracle分页函数实例
2016/01/25 PHP
PHP购物车类Cart.class.php定义与用法示例
2016/07/20 PHP
弹出模态框modal的实现方法及实例
2017/09/19 PHP
日期函数扩展类Ver0.1.1
2006/09/07 Javascript
原生JS绑定滑轮滚动事件兼容常见浏览器
2014/06/30 Javascript
jQuery背景插件backstretch使用指南
2015/04/21 Javascript
jquery+html5烂漫爱心表白动画代码分享
2015/08/24 Javascript
javascript实现移动端上的触屏拖拽功能
2016/03/04 Javascript
简述vue路由打开一个新的窗口的方法
2018/11/29 Javascript
详解Vue iview IE浏览器不兼容报错(Iview Bable polyfill)
2019/01/07 Javascript
js使用cookie实现记住用户名功能示例
2019/06/13 Javascript
通过javascript实现扫雷游戏代码实例
2020/02/09 Javascript
[01:08]DOTA2次级职业联赛 - Shield战队宣传片
2014/12/01 DOTA
[57:37]EG vs Mineski 2018国际邀请赛小组赛BO2 第二场 8.16
2018/08/17 DOTA
python静态方法实例
2015/01/14 Python
简单介绍Python下自己编写web框架的一些要点
2015/04/29 Python
简单介绍Python中的floor()方法
2015/05/15 Python
一个基于flask的web应用诞生 使用模板引擎和表单插件(2)
2017/04/11 Python
Python字符串的一些操作方法总结
2019/06/10 Python
解决Django连接db遇到的问题
2019/08/29 Python
Python猴子补丁知识点总结
2020/01/05 Python
python实现PCA降维的示例详解
2020/02/24 Python
利用纯html5绘制出来的一款非常漂亮的时钟
2015/01/04 HTML / CSS
Auchan Direct波兰:欧尚在线杂货店
2016/10/19 全球购物
印度尼西亚值得信赖的第一家网店:Bhinneka
2018/07/16 全球购物
高级方案规划工程师岗位职责
2013/11/29 职场文书
总裁岗位职责
2013/12/04 职场文书
《白鹅》教学反思
2014/04/13 职场文书
大学生社团活动总结
2014/04/26 职场文书
煤矿安全演讲稿
2014/05/09 职场文书
开展党的群众路线教育实践活动领导班子对照检查材料
2014/09/25 职场文书
辩论会主持词
2015/07/03 职场文书
期中考试后的感想
2015/08/07 职场文书
家访教师心得体会
2016/01/23 职场文书
销区经理年终述职报告模板
2019/11/28 职场文书
阿里云服务器Ubuntu 20.04上安装Odoo 15
2022/05/20 Servers