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 相关文章推荐
一个多文件上传的例子(原创)
Oct 09 PHP
用PHP实现小型站点广告管理(修正版)
Oct 09 PHP
PHP数字格式化
Dec 06 PHP
PHP 判断变量类型实现代码
Oct 23 PHP
PHP 巧用数组降低程序的时间复杂度
Jan 01 PHP
PHP三元运算符的结合性介绍
Jan 10 PHP
php定时删除文件夹下文件(清理缓存文件)
Jan 23 PHP
PHP register_shutdown_function函数的深入解析
Jun 03 PHP
php结合md5的加密解密算法实例
Sep 30 PHP
老生常谈PHP数组函数array_merge(必看篇)
May 25 PHP
laravel手动创建数组分页的实现代码
Jun 07 PHP
PHP封装的数据库模型Model类完整示例【基于PDO】
Mar 14 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
header导出Excel应用示例
2014/01/24 PHP
使用php的HTTP请求的库Requests实现美女图片墙
2015/02/22 PHP
常用PHP封装分页工具类
2017/01/14 PHP
总结PHP内存释放以及垃圾回收
2018/03/29 PHP
JavaScript 在线压缩和格式化收藏
2009/01/16 Javascript
在jquery中combobox多选的不兼容问题总结
2013/12/24 Javascript
javascript实现切换td中的值
2014/12/05 Javascript
JavaScript移除数组内重复元素的方法
2015/03/18 Javascript
JavaScript中获取HTML元素值的三种方法
2016/06/20 Javascript
jQuery实现订单提交页发送短信功能前端处理方法
2016/07/04 Javascript
js 轮播效果实例分享
2016/12/28 Javascript
微信小程序 图片上传实例详解
2017/05/05 Javascript
微信小程序实现锚点定位楼层跳跃的实例
2017/05/18 Javascript
AngularJs用户登录问题处理(交互及验证、阻止FQ处理)
2017/10/26 Javascript
anime.js 实现带有描边动画效果的复选框(推荐)
2017/12/24 Javascript
vue.js前后端数据交互之提交数据操作详解
2018/04/24 Javascript
vue的keep-alive中使用EventBus的方法
2019/04/23 Javascript
微信小程序实现下滑到底部自动翻页功能
2020/03/07 Javascript
js实现无缝轮播图特效
2020/05/09 Javascript
Windows和Linux下使用Python访问SqlServer的方法介绍
2015/03/10 Python
web.py在SAE中的Session问题解决方法(使用mysql存储)
2015/06/24 Python
Django 登陆验证码和中间件的实现
2018/08/17 Python
Tornado实现多进程/多线程的HTTP服务详解
2019/07/25 Python
python实现的接收邮件功能示例【基于网易POP3服务器】
2019/09/11 Python
Python socket非阻塞模块应用示例
2019/09/12 Python
利用django model save方法对未更改的字段依然进行了保存
2020/03/28 Python
Python fileinput模块如何逐行读取多个文件
2020/10/05 Python
HTML5中FileReader接口使用方法实例详解
2017/08/26 HTML / CSS
For Art’s Sake官网:手工制作的奢华眼镜
2018/12/15 全球购物
物业管理公司实习生自我鉴定
2013/09/19 职场文书
分公司经理任命书
2014/06/05 职场文书
喝酒驾驶检讨书
2014/10/01 职场文书
2015年六一儿童节活动总结
2015/02/11 职场文书
运动会800米赞词
2015/07/22 职场文书
医院中层管理人员培训心得体会
2016/01/11 职场文书
vue2实现provide inject传递响应式
2021/05/21 Vue.js