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初学者头疼问题总结
Jul 08 PHP
PHP 和 MySQL 基础教程(二)
Oct 09 PHP
模拟OICQ的实现思路和核心程序(三)
Oct 09 PHP
用PHP制作静态网站的模板框架(四)
Oct 09 PHP
php获得当前的脚本网址
Dec 10 PHP
php 代码优化的42条建议 推荐
Sep 25 PHP
ThinkPHP访问不存在的模块跳转到404页面的方法
Jun 19 PHP
ThinkPHP基本的增删查改操作实例教程
Aug 22 PHP
php在数组中查找指定值的方法
Mar 17 PHP
PHP Opcache安装和配置方法介绍
May 28 PHP
CI框架的安全性分析
May 18 PHP
ubutu 16.04环境下,PHP与mysql数据库,网页登录验证实例讲解
Jul 20 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
我的论坛源代码(四)
2006/10/09 PHP
二级域名转向类
2006/11/09 Javascript
jQuery 顺便学习下CSS选择器 奇偶匹配nth-child(even)
2010/05/24 Javascript
扩展jquery实现客户端表格的分页、排序功能代码
2011/03/16 Javascript
jquery实现简单易懂的图片展示小例子
2013/11/21 Javascript
jQuery实现的导航条切换可显示隐藏
2014/10/22 Javascript
Underscore.js常用方法总结
2015/02/28 Javascript
JavaScript通过字符串调用函数的实现方法
2015/03/18 Javascript
javascript实现五星评分功能
2015/11/10 Javascript
JavaScript setTimeout使用闭包功能实现定时打印数值
2015/12/18 Javascript
浅析JS异步加载进度条
2016/05/05 Javascript
js实现可键盘控制的简单抽奖程序
2016/07/13 Javascript
js 定位到某个锚点的方法
2016/11/19 Javascript
Bootstrap CSS组件之按钮下拉菜单
2016/12/17 Javascript
详解如何用webpack4从零开始构建react开发环境
2019/01/27 Javascript
稍微学一下Vue的数据响应式(Vue2及Vue3区别)
2019/11/21 Javascript
Vue常用传值方式、父传子、子传父及非父子实例分析
2020/02/24 Javascript
vue学习笔记之slot插槽用法实例分析
2020/02/29 Javascript
vue data变量相互赋值后被实时同步的解决步骤
2020/08/05 Javascript
基于Vant UI框架实现时间段选择器
2020/12/24 Javascript
[01:30:54]《加油DOTA》 第三期
2014/08/18 DOTA
基于anaconda下强大的conda命令介绍
2018/06/11 Python
python函数的万能参数传参详解
2019/07/26 Python
django基础学习之send_mail功能
2019/08/07 Python
python实现单目标、多目标、多尺度、自定义特征的KCF跟踪算法(实例代码)
2020/01/08 Python
Python 保存加载mat格式文件的示例代码
2020/08/04 Python
意大利领先的奢侈品在线时装零售商:MCLABELS
2020/10/13 全球购物
给女朋友的道歉信
2014/01/10 职场文书
给女儿的表扬信
2014/01/18 职场文书
中学生差生评语
2014/01/30 职场文书
大学毕业感言100字
2014/02/03 职场文书
2014年四风问题个人对照自查剖析材料
2014/09/15 职场文书
2014县政府领导班子三严三实对照检查材料思想汇报
2014/09/26 职场文书
战友聚会致辞
2015/07/28 职场文书
2016基督教会圣诞节开幕词
2016/03/04 职场文书
 分享一个Python 遇到数据库超好用的模块
2022/04/06 Python