2013年6月7日星期五

【转】mysql in 子查询 效率慢 优化

现在的CMS系统、博客系统、BBS等都喜欢使用标签tag作交叉链接,因此我也尝鲜用了下。但用了后发现我想查询某个tag的文章列表时速度很慢,达到5秒之久!百思不解(后来终于解决),我的表结构是下面这样的,文章只有690篇。

文章表article(id,title,content)
标签表tag(tid,tag_name)
标签文章中间表article_tag(id,tag_id,article_id)
其中有个标签的tid是135,我帮查询标签tid是135的文章列表
用以下语句时发现速度好慢,我文章才690篇
select id,title from article where id in(
select article_id from article_tag where tag_id=135
)
其中这条速度很快:select article_id from article_tag where tag_id=135
查询结果是五篇文章,id为428,429,430,431,432
我用写死的方式用下面sql来查文章也很快
select id,title from article where id in(
428,429,430,431,432
)
我在SqlServer中好像不会这样慢,不知MySQL怎样写好点,也想不出慢在哪里。

后来我找到了解决方法:

select id,title from article where id in(
select article_id from (select article_id from article_tag where tag_id=135) as tbt
)

 

 

其它解决方法:(举例)

mysql> select * from abc_number_prop where number_id in (select number_id from abc_number_phone where phone = '82306839');

为了节省篇幅,省略了输出内容,下同。

67 rows in set (12.00 sec)


只有67行数据返回,却花了12秒,而系统中可能同时会有很多这样的查询,系统肯定扛不住。用desc看一下(注:explain也可)


mysql> desc select * from abc_number_prop where number_id in (select number_id from abc_number_phone where phone = '82306839');
+----+--------------------+------------------+--------+-----------------+-------+---------+------------+---------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+------------------+--------+-----------------+-------+---------+------------+---------+--------------------------+
| 1 | PRIMARY | abc_number_prop | ALL | NULL | NULL | NULL | NULL | 2679838 | Using where |
| 2 | DEPENDENT SUBQUERY | abc_number_phone | eq_ref | phone,number_id | phone | 70 | const,func | 1 | Using where; Using index |
+----+--------------------+------------------+--------+-----------------+-------+---------+------------+---------+--------------------------+
2 rows in set (0.00 sec)


从上面的信息可以看出,在执行此查询时会扫描两百多万行,难道是没有创建索引吗,看一下


mysql>show index from abc_number_phone;
+------------------+------------+-------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+------------------+------------+-------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| abc_number_phone | 0 | PRIMARY | 1 | number_phone_id | A | 36879 | NULL | NULL | | BTREE | | |
| abc_number_phone | 0 | phone | 1 | phone | A | 36879 | NULL | NULL | | BTREE | | |
| abc_number_phone | 0 | phone | 2 | number_id | A | 36879 | NULL | NULL | | BTREE | | |
| abc_number_phone | 1 | number_id | 1 | number_id | A | 36879 | NULL | NULL | | BTREE | | |

| abc_number_phone | 1 | created_by | 1 | created_by | A | 36879 | NULL | NULL | | BTREE | | |
| abc_number_phone | 1 | modified_by | 1 | modified_by | A | 36879 | NULL | NULL | YES | BTREE | | |
+------------------+------------+-------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
6 rows in set (0.06 sec)

mysql>show index from abc_number_prop;
+-----------------+------------+-------------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-----------------+------------+-------------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| abc_number_prop | 0 | PRIMARY | 1 | number_prop_id | A | 311268 | NULL | NULL | | BTREE | | |
| abc_number_prop | 1 | number_id | 1 | number_id | A | 311268 | NULL | NULL | | BTREE | | |

| abc_number_prop | 1 | created_by | 1 | created_by | A | 311268 | NULL | NULL | | BTREE | | |
| abc_number_prop | 1 | modified_by | 1 | modified_by | A | 311268 | NULL | NULL | YES | BTREE | | |
+-----------------+------------+-------------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
4 rows in set (0.15 sec)


从上面的输出可以看出,这两张表在number_id字段上创建了索引的。


看看子查询本身有没有问题。

mysql> desc select number_id from abc_number_phone where phone = '82306839';
+----+-------------+------------------+------+---------------+-------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------------+------+---------------+-------+---------+-------+------+--------------------------+
| 1 | SIMPLE | abc_number_phone | ref | phone | phone | 66 | const | 6 | Using where; Using index |
+----+-------------+------------------+------+---------------+-------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)


没有问题,只需要扫描几行数据,索引起作用了。查询出来看看

mysql> select number_id from abc_number_phone where phone = '82306839';
+-----------+
| number_id |
+-----------+
| 8585 |
| 10720 |
| 148644 |
| 151307 |
| 170691 |
| 221897 |
+-----------+
6 rows in set (0.00 sec)


直接把子查询得到的数据放到上面的查询中

mysql> select * from abc_number_prop where number_id in (8585, 10720, 148644, 151307, 170691, 221897);

67 rows in set (0.03 sec)


速度也快,看来MySQL在处理子查询的时候是不够好。我在MySQL 5.1.42 和 MySQL 5.5.19 都进行了尝试,都有这个问题。


搜索了一下网络,发现很多人都遇到过这个问题:

参考资料1:使用连接(JOIN)来代替子查询(Sub-Queries) mysql优化系列记录
http://blog.csdn.net/hongsejiaozhu/article/details/1876181
参考资料2:网站开发日记(14)-MYSQL子查询和嵌套查询优化
http://dodomail.iteye.com/blog/250199

根据网上这些资料的建议,改用join来试试。

修改前:select * from abc_number_prop where number_id in (select number_id from abc_number_phone where phone = '82306839');

修改后:select a.* from abc_number_prop a inner join abc_number_phone b on a.number_id = b.number_id where phone = '82306839';


mysql> select a.* from abc_number_prop a inner join abc_number_phone b on a.number_id = b.number_id where phone = '82306839';

67 rows in set (0.00 sec)


效果不错,查询所用时间几乎为0。看一下MySQL是怎么执行这个查询的


mysql>desc select a.* from abc_number_prop a inner join abc_number_phone b on a.number_id = b.number_id where phone = '82306839';
+----+-------------+-------+------+-----------------+-----------+---------+-----------------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+-----------------+-----------+---------+-----------------+------+--------------------------+
| 1 | SIMPLE | b | ref | phone,number_id | phone | 66 | const | 6 | Using where; Using index |
| 1 | SIMPLE | a | ref | number_id | number_id | 4 | eap.b.number_id | 3 | |
+----+-------------+-------+------+-----------------+-----------+---------+-----------------+------+--------------------------+
2 rows in set (0.00 sec)


小结:当子查询速度慢时,可用JOIN来改写一下该查询来进行优化。


网上也有文章说,使用JOIN语句的查询不一定总比使用子查询的语句快。

参考资料3:改变了对Mysql子查询的看法
http://hi.baidu.com/yzx110/blog/item/e694f536f92075360b55a92b.html

 

 

mysql手册也提到过,具体的原文在mysql文档的这个章节:

I.3. Restrictions on Subqueries

13.2.8. Subquery Syntax

摘抄:

1)关于使用IN的子查询:

Subquery optimization for IN is not as effective as for the = operator or for IN(value_list) constructs.

A typical case for poor IN subquery performance is when the subquery returns a small number of rows but the outer query returns a large number of rows to be compared to the subquery result.

The problem is that, for a statement that uses an IN subquery, the optimizer rewrites it as a correlated subquery. Consider the following statement that uses an uncorrelated subquery:

SELECT ... FROM t1 WHERE t1.a IN (SELECT b FROM t2);

The optimizer rewrites the statement to a correlated subquery:

SELECT ... FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.b = t1.a);

If the inner and outer queries return M and N rows, respectively, the execution time becomes on the order of O(M×N), rather than O(M+N) as it would be for an uncorrelated subquery.

An implication is that an IN subquery can be much slower than a query written using an IN(value_list) construct that lists the same values that the subquery would return.

2)关于把子查询转换成join的:

The optimizer is more mature for joins than for subqueries, so in many cases a statement that uses a subquery can be executed more efficiently if you rewrite it as a join.

An exception occurs for the case where an IN subquery can be rewritten as a SELECT DISTINCT join. Example:

SELECT col FROM t1 WHERE id_col IN (SELECT id_col2 FROM t2 WHERE condition);

That statement can be rewritten as follows:

SELECT DISTINCT col FROM t1, t2 WHERE t1.id_col = t2.id_col AND condition;

But in this case, the join requires an extra DISTINCT operation and is not more efficient than the subquery


摘自:http://www.cnblogs.com/xh831213/archive/2012/05/09/2491272.html

【转】MySQL的mysqldump工具的基本用法

导出要用到MySQL的mysqldump工具,基本用法是:   
shell> mysqldump [OPTIONS] database [tables]   
如果你不给定任何表,整个数据库将被导出。   
通过执行mysqldump --help,你能得到你mysqldump的版本支持的选项表。   
注意,如果你运行mysqldump没有--quick或--opt选项,mysqldump将在导出结果前装载整个结果集到内存中,如果你正在导出一个大的数据库,这将可能是一个问题。   
mysqldump支持下列选项:   
--add-locks   
在每个表导出之前增加LOCK TABLES并且之后UNLOCK TABLE。(为了使得更快地插入到MySQL)。   
--add-drop-table   
在每个create语句之前增加一个drop table。   
--allow-keywords   
允许创建是关键词的列名字。这由表名前缀于每个列名做到。   
-c, --complete-insert   
使用完整的insert语句(用列名字)。   
-C, --compress   
如果客户和服务器均支持压缩,压缩两者间所有的信息。   
--delayed   
用INSERT DELAYED命令插入行。   
-e, --extended-insert   
使用全新多行INSERT语法。(给出更紧缩并且更快的插入语句)   
-#, --debug[=option_string]   
跟踪程序的使用(为了调试)。   
--help   
显示一条帮助消息并且退出。   
--fields-terminated-by=...   
    
--fields-enclosed-by=...   
    
--fields-optionally-enclosed-by=...   
    
--fields-escaped-by=...   
    
--fields-terminated-by=...   
这些选择与-T选择一起使用,并且有相应的LOAD DATA INFILE子句相同的含义。   
LOAD DATA INFILE语法。   
-F, --flush-logs   
在开始导出前,洗掉在MySQL服务器中的日志文件。   
-f, --force,   
即使我们在一个表导出期间得到一个SQL错误,继续。   
-h, --host=..   
从命名的主机上的MySQL服务器导出数据。缺省主机是localhost。   
-l, --lock-tables.   
为开始导出锁定所有表。   
-t, --no-create-info   
不写入表创建信息(CREATE TABLE语句)   
-d, --no-data   
不写入表的任何行信息。如果你只想得到一个表的结构的导出,这是很有用的!   
--opt   
同--quick --add-drop-table --add-locks --extended-insert --lock-tables。   
应该给你为读入一个MySQL服务器的尽可能最快的导出。   
-pyour_pass, --password[=your_pass]   
与服务器连接时使用的口令。如果你不指定“=your_pass”部分,mysqldump需要来自终端的口令。   
-P port_num, --port=port_num   
与一台主机连接时使用的TCP/IP端口号。(这用于连接到localhost以外的主机,因为它使用 Unix套接字。)   
-q, --quick   
不缓冲查询,直接导出至stdout;使用mysql_use_result()做它。   
-S /path/to/socket, --socket=/path/to/socket   
与localhost连接时(它是缺省主机)使用的套接字文件。   
-T, --tab=path-to-some-directory   
对 于每个给定的表,创建一个table_name.sql文件,它包含SQL CREATE 命令,和一个table_name.txt文件,它包含数 据。 注意:这只有在mysqldump运行在mysqld守护进程运行的同一台机器上的时候才工作。.txt文件的格式根据--fields-xxx和 --lines--xxx选项来定。   
-u user_name, --user=user_name   
与服务器连接时,MySQL使用的用户名。缺省值是你的Unix登录名。   
-O var=option, --set-variable var=option设置一个变量的值。可能的变量被列在下面。   
-v, --verbose   
冗长模式。打印出程序所做的更多的信息。   
-V, --version   
打印版本信息并且退出。   
-w, --where='where-condition'   
只导出被选择了的记录;注意引号是强制的!   
"--where=user='jimf'" "-wuserid>1" "-wuserid<1"  
最常见的mysqldump使用可能制作整个数据库的一个备份:  
mysqldump --opt database > backup-file.sql   
但是它对用来自于一个数据库的信息充实另外一个MySQL数据库也是有用的:   
mysqldump --opt database | mysql --host=remote-host -C database   
由于mysqldump导出的是完整的SQL语句,所以用mysql客户程序很容易就能把数据导入了:   
shell> mysqladmin create target_db_name   
shell> mysql target_db_name < backup-file.sql  
就是  
shell> mysql 库名 < 文件名 
================================
几个常用用例:
1.导出整个数据库
 mysqldump -u 用户名 -p 数据库名 > 导出的文件名    
 mysqldump -u wcnc -p smgp_apps_wcnc > wcnc.sql
2.导出一个表
 mysqldump -u 用户名 -p 数据库名 表名> 导出的文件名
 mysqldump -u wcnc -p smgp_apps_wcnc users> wcnc_users.sql
3.导出一个数据库结构
  mysqldump -u wcnc -p -d --add-drop-table smgp_apps_wcnc >d:\wcnc_db.sql
 -d 没有数据 --add-drop-table 在每个create语句之前增加一个drop table 
4.导入数据库
  常用source 命令
  进入mysql数据库控制台,
  如mysql -u root -p 
  
  mysql>use 数据库
  然后使用source命令,后面参数为脚本文件(如这里用到的.sql)
  mysql>source d:\wcnc_db.sql

摘自:http://www.blogjava.net/Alpha/archive/2007/08/10/135694.html

2013年5月31日星期五

【转】php之call_user_func_array的简易用法

今天在群里面,有个叫lewis的在问call_user_func_array的用法,因为之前一直没有用过,也不能说什么,于是看一下手册,发现是这么写的:

call_user_func_array

(PHP 4 >= 4.0.4, PHP 5)

call_user_func_array --  Call a user function given with an array of parameters

Description

mixed call_user_func_array ( callback function, array param_arr )

Call a user defined function given by function, with the parameters in param_arr.
然后还有一个例子:


<?php
function debug($var, $val
{
    
echo "***DEBUGGING\nVARIABLE: $var\nVALUE:";
    
if (is_array($val|| is_object($val|| is_resource($val)) {
        
print_r($val);
    } 
else {
        
echo "\n$val\n";
    }
    
echo "***\n";
}

$c = mysql_connect();
$host = $_SERVER["SERVER_NAME"];

call_user_func_array('debug', array("host", $host));
call_user_func_array('debug', array("c", $c));
call_user_func_array('debug', array("_POST", $_POST));
?> 
相信看了例子之后应该有点明白了吧?
我自己是这么理解这个函数的,如果说的不对,还望各位高手不要耻笑:
     该函数真正的用法有点类似于函数重载,因为他的第一个参数是字符型的,也就是函数的名称,第二个参数是数组,我们可以当成该函数的各个参数,而事实上也就是这么用的,如果你看过我的前一篇文章:PHP的伪重载 ,或许你能够理解,正是因为这个函数的存在,我发现函数重载也可以这样运用:
 1    /**
 2     * 例子写完后,本来认为完事了,结果遇到有人问call_user_func_array(),看了一下手册
 3     * 原来,我上面的那个test函数还可以精简成如下的例子,
 4     */
 5    function otest1 ($a)
 6    {
 7        echo'一个参数' );
 8    }
 9
10    function otest2 ( $a, $b)
11    {
12        echo'二个参数' );
13    }
14
15    function otest3 ( $a ,$b,$c)
16    {
17        echo'三个啦' );
18    }
19
20    function otest ()
21    {
22        $args = func_get_args();
23        $num = func_num_args();
24        call_user_func_array'otest'.$num, $args  );
25    }
26
27    otest(1,2);
28
看到不?而我最初的写法,在PHP的伪重载一文中有所提及,仅作参考。。。。

这些只是call_user_func_array的简易用法,在PHP4下测试过,而手册中还有一些将第一个参数当成数组来传入的例子,我在PHP4下是没有办法运行的,也许PHP5可以吧,但我不用PHP5的,也没有办法解释什么。谢谢各位

摘自:http://www.cnitblog.com/neatstudio/archive/2006/07/21/13990.html

【转】func_get_args的使用

func_get_args是获取方法中参数的数组,返回的是一个数组,与func_num_args搭配使用;
func_num_args一般写在方法中,用于计数;
使用方法如下:
function foo($a='gg',$b='kk'){
 
   $numargs = func_num_args();
     echo "Number of arguments: $numargs<br />\n";
     if ($numargs >= 2) {
         echo "Second argument is: " . func_get_arg(1) . "<br />\n";
     }
    $arg_list = func_get_args();
     for ($i = 0; $i < $numargs; $i++) {
         echo "Argument $i is: " . $arg_list[$i] . "<br />\n";
     }
}

foo(1, 2);=》输出内容为:
Number of arguments: 2
Second argument is: 2
Argument 0 is: 1
Argument 1 is: 2

摘自:http://blog.sina.com.cn/s/blog_816cdc2301014rvt.html

2013年5月27日星期一

【转】mysql 创建索引、修改索引、删除索引的命令

创建和删除索引
索引的创建可以在CREATE TABLE语句中进行,也可以单独用CREATE INDEX或ALTER TABLE来给表增加索引。删除索引可以利用ALTER TABLE或DROP INDEX语句来实现。

(1)使用ALTER TABLE语句创建索引。
语法如下:

  1. alter table table_name add index index_name (column_list) ; 
  2. alter table table_name add unique (column_list) ; 
  3. alter table table_name add primary key (column_list) ; 

其中包括普通索引、UNIQUE索引和PRIMARY KEY索引3种创建索引的格式,table_name是要增加索引的表名,column_list指出对哪些列进行索引,多列时各列之间用逗号分隔。索引 名index_name可选,缺省时,MySQL将根据第一个索引列赋一个名称。另外,ALTER TABLE允许在单个语句中更改多个表,因此可以同时创建多个索引。
创建索引的示例如下:

  1. mysql> use tpsc 
  2. Database changed 
  3. mysql> alter table tpsc add index shili (tpmc ) ; 
  4. Query OK, 2 rows affected (0.08 sec) 
  5. Records: 2 Duplicates: 0 Warnings: 0 

(2)使用CREATE INDEX语句对表增加索引。
能够增加普通索引和UNIQUE索引两种。其格式如下:

  1. create index index_name on table_name (column_list) ; 
  2. create unique index index_name on table_name (column_list) ; 

说明:table_name、index_name和column_list具有与ALTER TABLE语句中相同的含义,索引名不可选。另外,不能用CREATE INDEX语句创建PRIMARY KEY索引。

(3)删除索引。
删除索引可以使用ALTER TABLE或DROP INDEX语句来实现。DROP INDEX可以在ALTER TABLE内部作为一条语句处理,其格式如下:

  1. drop index index_name on table_name ; 
  2. alter table table_name drop index index_name ; 
  3. alter table table_name drop primary key ; 

其中,在前面的两条语句中,都删除了table_name中的索引index_name。而在最后一条语句中,只在删除PRIMARY KEY索引中使用,因为一个表只可能有一个PRIMARY KEY索引,因此不需要指定索引名。如果没有创建PRIMARY KEY索引,但表具有一个或多个UNIQUE索引,则MySQL将删除第一个UNIQUE索引。
如果从表中删除某列,则索引会受影响。对于多列组合的索引,如果删除其中的某列,则该列也会从索引中删除。如果删除组成索引的所有列,则整个索引将被删除。
删除索引的操作,如下面的代码:

  1. mysql> drop index shili on tpsc ; 
  2. Query OK, 2 rows affected (0.08 sec) 
  3. Records: 2 Duplicates: 0 Warnings: 0 

该语句删除了前面创建的名称为“shili”的索引。

摘自:http://www.beijibear.com/index.php?aid=544

【转】mysql 中alter语句中change和modify的区别

以下摘自mysql5手册 


您可以使用CHANGE old_col_namecolumn_definition子句对列进行重命名。重命名时,需给定旧的和新的列名称和列当前的类型。例如:要把一个INTEGER列的名称从a变更到b,您需要如下操作:

・                mysql> ALTER TABLE t1 CHANGE a b INTEGER;

如果您想要更改列的类型而不是名称, CHANGE语法仍然要求旧的和新的列名称,即使旧的和新的列名称是一样的。例如:

mysql> ALTER TABLE t1 CHANGE b b BIGINT NOT NULL;

您也可以使用MODIFY来改变列的类型,此时不需要重命名:

mysql> ALTER TABLE t1 MODIFY b BIGINT NOT NULL;




mysql alter 语句用法,添加、修改、删除字段等


//主键549830479

   alter table tabelname add new_field_id int(5) unsigned default 0 not null auto_increment ,add primary key (new_field_id);
//增加一个新列549830479

   alter table t2 add d timestamp;
alter table infos add ex tinyint not null default '0';
//删除列549830479

   alter table t2 drop column c;
//重命名列549830479

   alter table t1 change a b integer;

//改变列的类型549830479

   alter table t1 change b b bigint not null;
alter table infos change list list tinyint not null default '0';

//重命名表549830479

   alter table t1 rename t2;
加索引549830479

   mysql> alter table tablename change depno depno int(5) not null;
mysql> alter table tablename add index 索引名 (字段名1[,字段名2 …]);
mysql> alter table tablename add index emp_name (name);
加主关键字的索引549830479

mysql> alter table tablename add primary key(id);
加唯一限制条件的索引549830479

  mysql> alter table tablename add unique emp_name2(cardnumber);
删除某个索引549830479

   mysql>alter table tablename drop index emp_name;
修改表:549830479

增加字段:549830479

   mysql> ALTER TABLE table_name ADD field_name field_type;
修改原字段名称及类型:549830479

   mysql> ALTER TABLE table_name CHANGE old_field_name new_field_name field_type;
删除字段:549830479

   mysql> ALTER TABLE table_name DROP field_name;

删除主键: alter table Employees drop primary key;

摘自:http://hi.baidu.com/software_one/item/3da0360ba7a32ce6ff240de1

【转】MySQL之alter语句用法总结

1:删除列

ALTER TABLE 【表名字】 DROP 【列名称】

2:增加列

ALTER TABLE 【表名字】 ADD 【列名称】 INT NOT NULL  COMMENT '注释说明'

3:修改列的类型信息

ALTER TABLE 【表名字】 CHANGE 【列名称】【新列名称(这里可以用和原来列同名即可)】 BIGINT NOT NULL  COMMENT '注释说明'

4:重命名列

ALTER TABLE 【表名字】 CHANGE 【列名称】【新列名称】 BIGINT NOT NULL  COMMENT '注释说明'

5:重命名表

ALTER TABLE 【表名字】 RENAME 【表新名字】

6:删除表中主键

Alter TABLE 【表名字】 drop primary key

7:添加主键

ALTER TABLE sj_resource_charges ADD CONSTRAINT PK_SJ_RESOURCE_CHARGES PRIMARY KEY (resid,resfromid)

8:添加索引

ALTER TABLE sj_resource_charges add index INDEX_NAME (name);

9: 添加唯一限制条件索引

ALTER TABLE sj_resource_charges add unique emp_name2(cardnumber);

10: 删除索引

alter table tablename drop index emp_name;

 摘自:http://www.cnblogs.com/aspnethot/articles/1397130.html