2011年08月5日

MySQL版本和SSIS不兼容问题分析及解决办法

作者:吴炳锡 来源:http://www.mysqlsupport.cn/ 联系方式: wubingxi#gmail.com 转载请注:译者和出处,并且不能用于商业用途,违者必究.
线上替换Percona版本和SSIS不兼容问题分析及解决办法

现象:
利用SSIS(SQL Server Intelligence Services)访问Percona-Server 5.1 原来的SQL取不到数据。
如果加上limit后就能取到数据。但原来的MySQL版本(MySQL-5.1.43sp1)就可以取到数据。
MySQL版本:Percona-Server-5.1.55 or MySQL-5.1.55后
SSIS 从MySQL中取不到数据。

分析:
从WireShark抓包分SSIS执行SQL的流程:
1. PING 包
2. INIT_DB连接上数据库
3. 发送:set sql_select_limit=0;
4. 执行SSIS中定义的SQL;
5. 发送: set sql_select_limit=-1;
6. 再执行SSIS中定义的SQL;

问题发生在哪呢?

第一次set sql_select_limit=0 是因为在早期的数据库交互中,因为mysql没有SQL编译的及SQL正确否的校验,
所以很多程序员会用set sql_select_limit=0这个然后再执行SQL看有返回的错误不。
从协义上看第一次set sql_select_limit=0在Percona-Server-5.1.55及后面的SQL执行都是正确执行的。
然后第二次调用:set sql_select_limit=-1; 目的是把环境变量sql_select_limit还原成default值。
然后再执行SSIS中的SQL.读取读取不到信息。

猜测:

  在执行set sql_select_limit=-1没能执行成功造成后面SSIS再次执行SQL没返回。

有了以上的信息去验证一下:

mysql> select @@version_comment, @@version;
+--------------------------------------------------------------+--------------------+
| @@version_comment | @@version |
+--------------------------------------------------------------+--------------------+
| Percona Server with XtraDB (GPL), Release 12.6, Revision 200 | 5.1.55-rel12.6-log |
+--------------------------------------------------------------+--------------------+
1 row in set (0.00 sec)
mysql>  select user from mysql.user where user="root";
+------+
| user |
+------+
| root |
| root |
+------+
2 rows in set (0.00 sec)
mysql>  set sql_select_limit=0;
Query OK, 0 rows affected (0.00 sec)
mysql>  select user from mysql.user where user="root";
Empty set (0.00 sec)
mysql> set sql_select_limit=-1;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql>  select user from mysql.user where user="root";
Empty set (0.00 sec)
mysql> set sql_select_limit=default;
Query OK, 0 rows affected (0.00 sec)
mysql> select user from mysql.user where user="root";
+------+
| user |
+------+
| root |
| root |
+------+
2 rows in set (0.00 sec)
mysql>  set sql_select_limit=-1;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> show warnings;
+---------+------+--------------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------------+
| Warning | 1292 | Truncated incorrect sql_select_limit value: '-1' |
+---------+------+--------------------------------------------------+
1 row in set (0.00 sec)

可见在执行:set sql_select_limit=-1; 这个负1是不支持的。从手册上来看,也只是说了一个可以给的最大值,也没说最少值。该参数还是挺鬼疑的。
有兴趣的可以试一下mysql-5.1.54及以下版本或是MySQL-5.1.55以后的版本。
我试了MySQL-5.1.56一样对:set sql_select_limit=-1是不支持。
现在在来看为什么加上limit都可以显示数据呢?
手册里描述:If a SELECT has a LIMIT clause, the LIMIT takes precedence over the value of sql_select_limit.
可见SELECT中的limit运算优先于sql_select_limit。

验证:

mysql>  select user from mysql.user where user="root" limit 10;
+------+
| user |
+------+
| root |
| root |
+------+
2 rows in set (0.00 sec)
mysql>  set sql_select_limit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select user from mysql.user where user="root";
Empty set (0.00 sec)

如何解决这个问题:
原因是set sql_select_limit=-1没正确执行。
可以在SSIS中的SQL前添加set sql_selct_limit=default;该解决方法存在性能问题,但能正确执行了。
最彻底的解决办法:反溃给那MS支持方,SQL调用中的set sql_select_limit=-1改成 set sql_select_limit=default。

影响:
如果该问题不修正,目前这个SSIS只能使用MySQL-5.1.54前的版本了。
参考:

http://dev.mysql.com/doc/refman/5.1/en/server-system-variables.html#sysvar_sql_select_limit

感想:
不是开源的东西,你知道为什么,知道怎么改,也无语啊。

2010年05月12日

案例:一个引号带来的查询性能提升

作者:吴炳锡 来源:http://www.mysqlsupport.cn/ 联系方式: wubingxi#gmail.com 转载请注:译者和出处,并且不能用于商业用途,违者必究.
今天看了一个优化案例觉的挺有代表性,这里记录下来做一个标记,来纪念一下随便的字段定义的问题。

回忆一下,在表的设计中很多人习惯的把表的结构设计成Varchar(64),Varchar(255)之类的,虽然大多数情况只存了5-15个字节.那么我看一下下面这个案例.
查询语句:

	SELECT SQL_NO_CACHE channel, COUNT(channel) AS visitors FROM xxx_sources WHERE client_id = 1301 GROUP BY client_id, channel;

该表(client_id,channel)是一个组合索引.
利用explain,看一下执行计划,对于索引使用上看上非常完美

mysql> explain SELECT SQL_NO_CACHE channel, COUNT(channel) AS visitors FROM xxx_sources WHERE client_id = 1301 GROUP BY client_id, channel;
+----+-------------+-------------+-------+--------------------+--------------------+---------+------+----------+--------------------------+
| id | select_type | table       | type  | possible_keys      | key                | key_len | ref  | rows     | Extra                    |
+----+-------------+-------------+-------+--------------------+--------------------+---------+------+----------+--------------------------+
|  1 | SIMPLE      | xxx_sources | index | idx_client_channel | idx_client_channel | 1032    | NULL | 20207319 | Using where; Using index |
+----+-------------+-------------+-------+--------------------+--------------------+---------+------+----------+--------------------------+
1 row in set (0.00 sec)

看一下实际执行:

mysql> SELECT SQL_NO_CACHE channel, COUNT(channel) AS visitors FROM xxx_sources WHERE client_id = 1301 GROUP BY client_id, channel;
+---------+----------+
| channel | visitors |
+---------+----------+
| NULL    |        0 |
+---------+----------+
1 row in set (11.69 sec)

实际执行的情况非常的糟糕.传通的想法,这个执行从索引上执行计划上看非常完美了,好象和MySQL没什么关系了. 在去看一下表的设计会发现client_id也是设计成了
varchar(255).看到这里不防可以使用下面的方法试一下:

mysql> explain SELECT SQL_NO_CACHE channel, COUNT(channel) AS visitors FROM xxx_sources WHERE client_id = '1301' GROUP BY client_id, channel;
+----+-------------+-------------+------+--------------------+--------------------+---------+-------+--------+--------------------------+
| id | select_type | table       | type | possible_keys      | key                | key_len | ref   | rows   | Extra                    |
+----+-------------+-------------+------+--------------------+--------------------+---------+-------+--------+--------------------------+
|  1 | SIMPLE      | xxx_sources | ref  | idx_client_channel | idx_client_channel | 258     | const | 457184 | Using where; Using index |
+----+-------------+-------------+------+--------------------+--------------------+---------+-------+--------+--------------------------+
1 row in set (0.00 sec)

从执行计划上来看,差不多,但实际差多了.具体上来看key_len从1032降到了258,执行计划变成了const基于等于的查找,行数从原来千万级到了十万级了.不算也能明白IO
节省了很多.
再来看实际执行:

mysql> SELECT SQL_NO_CACHE channel, COUNT(channel) AS visitors FROM xxx_sources WHERE client_id = '1301' GROUP BY client_id, channel;
+---------+----------+
| channel | visitors |
+---------+----------+
| NULL    |        0 |
+---------+----------+
1 row in set (0.25 sec)

哇,从11.69秒变成了0.25秒,这是什么概念,优化了多少倍,算一下吧.

看到这里在想什么呢,记住这个案例,嗯,不错,以后还可以加引号优化一下.那为什么不问一下,能不能在优化了,为什么会这样呢?
我们先来看一下第一个问题:
能不能在优化了?
答案是当然可以了.从索引的长度上来看258还是一个非常大的数据,对于client_id这个字段从名字上来看,也只会存数据型的值,那为什么不用的一个int unsigned去存呢,
索引的长度马上会从258降到4。这样不是又节省了很多吗?
接下来看一下第二个问题,为什么会这样呢?
原因有两点,同时基于一个原则,基于成本的优化器。对于client_id在表的定义时定义成了字符型的值,在查询时传入了数值型的值,需要经过一个数值转换,悲剧的开始,最终
导致MySQL选择了一个完成的索引去扫描。

从这个案例上,我们需要注意什么呢?
合理的选择数据类型,基本工太重要了,就这叫赢在起跑线,一切都不能随便了,别把一个表定义成了降了主建外其它全是Varchar(255)。对数据库的double/float这种字
段做索引时一定要小心。

待思考:
为什么加一个引号后索引长度执行计划变成了258,为什么是258呢,不是别的呢。