这是一篇吐槽贴
吐槽的对象是MySQL
吐槽的点是它计算length的算法
结论是:这代码写得,真是想到哪写到哪,千疮百孔,漏洞百出。
以substr为例:
mysql> select substr('1234', 4, 9);
Field 1: `substr('1234', 4, 9)`
Catalog: `def`
Database: ``
Table: ``
Org_table: ``
Type: VAR_STRING
Collation: utf8_general_ci (33)
Length: 27
Max_length: 1
Decimals: 31
Flags:
+----------------------+
| substr('1234', 4, 9) |
+----------------------+
| 4 |
+----------------------+
1 row in set (0.00 sec)
可以看到,substr的length输出为27。可是,就算把1234全部输出出来,也不可能为27呀!那么,27是怎么来的呢? 看代码吧。代码我加了一些注释,可以看到它是如何计算length的。
void Item_func_substr::fix_length_and_dec()
{
// 例子:select substr('1234', 4, 9);
// note:max_length对应的就是上面meta data中的length。
max_length=args[0]->max_length; // max_length = 12 = 4 * 3 = 4 * mbmaxlen
agg_arg_charsets_for_string_result(collation, args, 1);
DBUG_ASSERT(collation.collation != NULL);
if (args[1]->const_item())
{
int32 start= (int32) args[1]->val_int();
if (args[1]->null_value)
goto end;
if (start < 0)
max_length= ((uint)(-start) > max_length) ? 0 : (uint)(-start);
else
max_length-= min((uint)(start - 1), max_length); // max_length = 12 - 3 = 9
}
if (arg_count == 3 && args[2]->const_item())
{
int32 length= (int32) args[2]->val_int();
if (args[2]->null_value)
goto end;
if (length <= 0)
max_length=0;
else
set_if_smaller(max_length,(uint) length); // max_length = min(9, 9) = 9,
// 这里啰嗦一句,当第三个参数大于9的时候,max_length总是保持在27这个值上,就是这一句min作祟。
}
end:
max_length*= collation.collation->mbmaxlen; // max_length = 9 * 3 = 27
}
看完上面的分析,就知道27是怎么算出来的了:collation->mbmaxlen会被乘多次。满满的一碗bug,来,干了!
MySQL开发者为什么写出这样的代码来呢?其实它在某种情况下计算结果是正确的,例如:select substr(1234, 4, 9); 详见本文评论。
这并不是个案,MySQL中计算length很随意,几乎就是实现定义的。那么问题来了,我要兼容这种length bug算法吗?如果不兼容,那么我们是否应该有一套规范?如果兼容,你来写。
附,额外思考题:
如果不是用常量,而是用变量来代入第二个第三个参数呢?例如:
substr('1234', col\_start, col\_length);
从代码上看,输出length应该是12,因为col_start和col_length在fix_length_and_dec()阶段都无法确定具体值,所以只能使用最保守的长度了。
这种做法,价值何在?
补记:http://blog.csdn.net/maray/article/details/49891793 本文弄明白了价值何在。