PHP源码 之 ext/mysql扩展部分

PHP扩展主要分成3种(http://cn.php.net/manual/en/internals2.ze1.zendapi.php):外部模块、内置、zend扩展。

 

我写过一个外部模块扩展,现在开始看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;

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值