php 函数 mysql_insert_id() 的一些分析

昨天在群里说起来的,今天记一下.

下面分析了 mysql_insert_id() 的 c 语言实现,同时比较了 mysqli_insert_id() 的内部实现机制。从实现的 c 代码上可以看出,mysqli 比 mysql 改进了许多。回过头来看 Zend Framework,只提供了 mysqli 方式的 db adapter,不是没有道理的。

手册上关于 mysql_insert_id() 有这样一段补充:

警告

mysql_insert_id() 将 MySQL 内部的 C API 函数 mysql_insert_id() 的返回值转换成 long(PHP 中命名为 int)。如果 AUTO_INCREMENT 的列的类型是 BIGINT,则 mysql_insert_id() 返回的值将不正确。可以在 SQL 查询中用 MySQL 内部的 SQL 函数 LAST_INSERT_ID() 来替代。

也就是说当使用 BIGINT 作为 AUTO_INCREMENT 列时,mysql_insert_id() 会超出可处理的范围。

mysql_insert_id() 的 c 代码实现(php-5.2.6\ext\mysql\php_mysql.c 第 1712 行):

/* {{{ proto int mysql_insert_id([int link_identifier])
Gets the ID generated from the previous INSERT operation */
PHP_FUNCTION(mysql_insert_id)
{
zval **mysql_link;
int id;
php_mysql_conn *mysql;

switch(ZEND_NUM_ARGS()) {
case 0:
id = MySG(default_link);
CHECK_LINK(id);
break;
case 1:
if (zend_get_parameters_ex(1, &mysql_link)==FAILURE) {
RETURN_FALSE;
}
id = -1;
break;
default:
WRONG_PARAM_COUNT;
break;
}

ZEND_FETCH_RESOURCE2(mysql, php_mysql_conn *, mysql_link, id, “MySQL-Link”, le_link, le_plink);

/* conversion from int64 to long happing here */
Z_LVAL_P(return_value) = (long) mysql_insert_id(&mysql->conn);
Z_TYPE_P(return_value) = IS_LONG;
}
/* }}} */

注意这里

/* conversion from int64 to long happing here */
Z_LVAL_P(return_value) = (long) mysql_insert_id(&mysql->conn);

是这样转换的。

再来看看 mysqli_insert_id():

注意: 如果数字大于整形的最大值,mysqli_insert_id() 将返回一个字符串。

mysqli_insert_id() 的 c 代码实现如下(php-5.2.6\ext\mysqli\mysql_api.c 第 1121 行):

/* {{{ proto mixed mysqli_insert_id(object link)
Get the ID generated from the previous INSERT operation */
PHP_FUNCTION(mysqli_insert_id)
{
MY_MYSQL *mysql;
my_ulonglong rc;
zval *mysql_link;

if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), “O”, &mysql_link, mysqli_link_class_entry) == FAILURE) {
return;
}
MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL *, &mysql_link, “mysqli_link”, MYSQLI_STATUS_VALID);
rc = mysql_insert_id(mysql->mysql);
MYSQLI_RETURN_LONG_LONG(rc)
}
/* }}} */

这里使用了宏 MYSQLI_RETURN_LONG_LONG(rc) 来返回结果。此宏内容如下(php-5.2.6\ext\mysqli\php_mysqli.h第 241 行):

#define MYSQLI_RETURN_LONG_LONG(__val) \
{ \
if ((__val) < LONG_MAX) { \
RETURN_LONG((long) (__val)); \
} else { \
char *ret; \
/* always used with my_ulonglong -> %llu */ \
int l = spprintf(&ret, 0, “%llu”, (__val)); \
RETURN_STRINGL(ret, l, 0); \
} \
}

可以看到这里判断了值是否小于 LONG 型最大值。如果小于,直接返回;如果大于则转为一个 char * ,然后用 RETURN_STRINGL 返回,是一个字符串。

所以如果有条件的情况下,还是应该使用 mysqli 作为数据库连接方式。mysqli 修正了很多 mysql 无法修正的地方,可以避免很多由于 php 自身陷阱引发的问题。

还有一个遗留问题是 mysql 中内置函数 LAST_INSERT_ID() 和 c lib 的 mysql_insert_id() 实现机制是否一样。不熟悉,也懒得去看 mysql 的代码。谁有兴趣玩玩看吧。

Join the Conversation

3 Comments

Leave a comment

Your email address will not be published. Required fields are marked *