【转】MySQL质量优化的特等经验,随时补给

① 、为查询优化你的查询

大多数的MySQL服务器都打开了查询缓存。那是升高性最有效的法门之一,而且那是被MySQL的数据库引擎处理的。当有成都百货上千如出一辙的查询被执行了多次的时候,那么些查询结果会被内置1个缓存中,那样,后续的平等的询问就毫无操作表而直接待上访问缓存结果了。

此地最关键的难点是,对于程序员来说,这几个工作是很不难被忽视的。因为,我们一点查询语句会让MySQL不选择缓存。请看下边包车型地铁以身作则:

// 查询缓存不开启
$r = mysql_query("SELECT username FROM user WHERE   signup_date >= CURDATE()");

// 开启查询缓存
$today = date("Y-m-d");
$r = mysql_query("SELECT username FROM user WHERE signup_date >= '$today'");

上边两条SQL语句的差别便是 CU途乐DATE()
,MySQL的查询缓存对那几个函数不起效率。所以,像 NOW() 和 RAND() 或是其它的诸如此类的SQL函数都不会开启查询缓存,因为这些函数的返回是会不定的易变的。所以,你所急需的正是用一个变量来取代MySQL的函数,从而拉开缓存。

2、EXPLAIN 你的SELECT查询

使用EXPLAIN重中之重字能够让您知道MySQL是怎么着处理你的SQL语句的。

有表关联的询问,如下列:

select username, group_name
from users u
joins groups g on (u.group_id = g.id)

发现查询缓慢,然后在group_id字段上加码索引,则会加快查询

③ 、当只要一行数据时行使LIMIT 1

当您查询表的多少时候,你早就掌握结果只会有一条结果,单因为您可能必要去fetch游标,或是你只怕会去检查重临的记录数。
在那种意况下,加上LIMIT 1 足以追加品质。那样平等,
MySQL数据库引擎会在找到一条数据后终止搜索,而不是持续未来查找下一条适合记录的数码。
下边包车型地铁以身作则,只是为着找一下是或不是有“中国”的用户,很无不侧目,前边的会比前面包车型客车更有成效。(请留意,第①条中是Select
*,第壹条是Select 1)

// 没有效率的:
$r = mysql_query("SELECT * FROM user WHERE country = 'China'");
if (mysql_num_rows($r) > 0) {
   // ...
}

// 有效率的:
$r = mysql_query("SELECT 1 FROM user WHERE country = 'China' LIMIT 1");
if (mysql_num_rows($r) > 0) {
// ...
}

宣传语

历经四个半月的备选,一遍大改版,十五回小改版。le1024算是要和大家晤面了。

le1024天天推荐1~3段,有趣、有爱、有轶闻的录制。

为您做事、学习、生活之余扩充一些欢开心喜的感到。程序员一定要看的欢悦录像网站

四 、为寻找字段建索引

索引并不一定就是给主键或是唯一的字段。如果在你的表中,有某个字段你总要会经常用来做搜索,那么,请为其建立索引吧。

五 、在Join表的时候使用优异类型的列,并将其索引

如果你的应用程序有很多JOIN查询,你应该确认两个表中Join的字段是被建过索引的。这样,MySQL内部会启动为你优化Join的SQL语句的机制。
而且,这些被用来Join的字段,应该是相同的类型的。例如:如果你要把DECIMAL字段和一个INT字段JOIN在一起,MYSQL就无法使用他们的索引。对于那些STRING类型,还需要有相同的字符集才行(两个表的字符集有可能不一样)

陆 、千万不要OEscortDEPRADO BY RAND()

7、避免SELECT *

从数据库里读出越多的数据,那么查询就会变得越慢。并且,如果你的数据库服务器和WEB服务器是两台独立的服务器的话,这还会增加网络传输的负载。

所以,你应当养成一个亟待什么就取什么的好的习惯。

// 不推荐
$r = mysql_query("SELECT * FROM user WHERE user_id = 1");
$d = mysql_fetch_assoc($r);
echo "Welcome {$d['username']}";

// 推荐
$r = mysql_query("SELECT username FROM user WHERE user_id = 1");
$d = mysql_fetch_assoc($r);
echo "Welcome {$d['username']}";

捌 、永远为两张表安装叁个ID

我们应该为数据库里的每张表都设置一个ID作为其主键,而最好的是一个INT型(推荐使用UNSIGNED),并设置上自动增长的AUTO INCREMENT标志。
就算是你 users 表有一个主键叫 “email”的字段,你也别让它成为主键。使用 VARCHAR 类型来当主键会使用得性能下降。另外,在你的程序中,你应该使用表的ID来构造你的数据结构。

再者,在MySQL数据引擎下,还有一对操作须要采纳主键,在这一个情形下,主键的质量和安装变得不得了首要,比如,集群,分区……

9、使用 ENUM 而不是 VARCHAR ?

ENUM 类型是可怜快和严格的。在其实,其保存的是
TINYINT,但其外表上出示为字符串。那样一来,用这些字段来做一些挑选列表变得一定的宏观。

设若您有3个字段,比如“性别”,“国家”,“民族”,“状态”或“部门”,你通晓这一个字段的取值是少数而且一定的,那么,你应当利用
ENUM 而不是 VALX570CHACR-V。

十 、从 PROCEDURE ANALYSE() 取得建议 ?

PROCEDURE ANALYSE() 会让 MySQL
帮你去分析你的字段和其实际的数量,并会给你某个使得的提出。唯有表中有实际的数码,那个建议才会变得有用,因为要做一些大的控制是急需有数据作为基础的。

譬如说,即使你创立了三个 INT
字段作为你的主键,然则并不曾太多的多寡,那么,PROCEDURE
ANALYSE()会建议你把那么些字段的类型改成 MEDIUMINT 。或是你使用了3个VA凯雷德CHAOdyssey 字段,因为数量不多,你恐怕会获得一个让您把它改成 ENUM
的提议。那么些提出,都以大概因为数量不够多,所以决定做得就不够准。

1① 、尽大概的选择 NOT NULL

唯有你有3个很尤其的缘由去行使 NULL 值,你应该总是让你的字段保持 NOT
NULL。那看起来好像某些争议,请往下看。

第②,问问你协调“Empty”和“NULL”有多大的区分(若是是INT,那就是0和NULL)?假使您以为它们中间一向不什么样界别,那么您就毫无选用NULL。(你精通呢?在
Oracle 里,NULL 和 Empty 的字符串是同等的!)

并非觉得 NULL
不供给空间,其须求卓殊的空中,并且,在你进行相比较的时候,你的主次会更复杂。
当然,那里并不是说您就不能够动用NULL了,现实际情情况是很复杂的,还是会稍微处境下,你要求运用NULL值。

上面摘自MySQL本人的文书档案

“NULL columns require additional space in the row to record whether
their values are NULL. For MyISAM tables, each NULL column takes one
bit extra, rounded up to the nearest byte.”

1二 、把IP地址存成 UNSIGNED INT

很多程序员都会创建一个 VARCHAR(15) 字段来存放字符串形式的IP而不是整形的IP。如果你用整形来存放,只需要4个字节,并且你可以有定长的字段。而且,这会为你带来查询上的优势,尤其是当你需要使用这样的WHERE条件:IP between ip1 and ip2。

大家必要求选拔UNSIGNED INT,因为 IP地址会动用全数三十五个人的无符号整形

1叁 、固定长度的表会更快

如果表中的所有字段都是“固定长度”的,整个表会被认为是 “static” 或 “fixed-length”。 例如,表中没有如下类型的字段: VARCHAR,TEXT,BLOB。只要你包括了其中一个这些字段,那么这个表就不是“固定长度静态表”了,这样,MySQL 引擎会用另一种方法来处理。

固定长度的表会提升质量,因为MySQL搜寻得会更快一些,因为这个永恒的长短是很不难总结下四个数额的偏移量的,所以读取的自然也会一点也不慢。而一旦字段不是定长的,那么,每三遍要找下一条的话,需求程序找到主键。

同时,固定长度的表也更便于被缓存和重建。但是,唯一的副效用是,固定长度的字段会浪费一些空间,因为定长的字段无论你用不用,他都以要分配那么多的空中。

1④ 、垂直细分

“垂直细分”是一种把数据库中的表按列变成几张表的章程,那样能够下落表的复杂度和字段的数额,从而完成优化的目标。(以前,在银行做过项目,见过一张表有100两个字段,很恐惧)

演示一:在Users表中有3个字段是家庭地址,这几个字段是可选字段,相比较起,而且你在数据库操作的时候除了个人音信外,你并不供给平时读取或是改写那个字段。那么,为何不把她放到其它一张表中呢?
那样会让您的表有更好的属性,我们想想是否,多量的时候,笔者对此用户表来说,唯有用户ID,用户名,口令,用户角色等会被日常使用。小一些的表总是会有好的习性。

示范二: 你有一个叫 “last_login”
的字段,它会在历次用户登录时被更新。然则,每一回换代时会导致该表的查询缓存被清空。所以,你能够把这些字段放到另三个表中,那样就不会影响你对用户ID,用户名,用户角色的不停地读取了,因为查询缓存会帮你扩展很多性质。

其余,你须求留意的是,那么些被分出来的字段所形成的表,你不会平日性地去Join他们,否则的话,那样的属性会比不分割时还要差,而且,会是极数级的下降。

1伍 、拆分大的 DELETE 或 INSE昂CoraT 语句

要是您须要在三个在线的网站上去执行2个大的 DELETE 或 INSECR-VT
查询,你必要卓殊小心,要制止你的操作让你的凡事网站甘休相应。因为那五个操作是会锁表的,表一锁住了,别的操作都进不来了。

Apache
会有众多的子进程或线程。所以,其行事起来至极有作用,而大家的服务器也不指望有太多的子进度,线程和数据库链接,那是高大的占服务器财富的作业,越发是内部存款和储蓄器。

借使您把您的表锁上一段时间,比如30分钟,那么对于二个有很高访问量的站点来说,那30秒所累积的拜访进度/线程,数据库链接,打开的文件数,或许不仅会让您泊WEB服务Crash,还可能会让你的整台服务器登时掛了。

从而,假若你有二个大的处理,你定你早晚把其拆分,使用 LIMIT
条件是二个好的措施。上面是贰个演示:

while (1) {
//每次只做1000条
mysql_query("DELETE FROM logs WHERE log_date <= '2009-11-01' LIMIT 1000");
if (mysql_affected_rows() == 0) {
    // 没得可删了,退出!
    break;
}
// 每次都要休息一会儿
usleep(50000);

}

1陆 、 越小的列会越快

对于超过二分之一的数据库引擎来说,硬盘操作大概是最重庆大学的瓶颈。所以,把你的多少变得紧密会对那种地方非常有帮带,因为那减弱了对硬盘的访问。

参照 MySQL 的文书档案 Storage Requirements 查看全部的数据类型。

只要一个表只会有几列罢了(比如说字典表,配置表),那么,我们就一直不理由使用
INT 来做主键,使用 MEDIUMINT, SMALLINT 或是更小的 TINYINT
会更划算部分。固然您不须要记录时间,使用 DATE 要比 DATETIME 好得多。

本来,你也亟需留够丰硕的增添空间,不然,你以后来干那些事,你会死的很掉价,参看Slashdot的事例(二〇〇八年11月0八日),三个简单的ALTETiguanTABLE语句花了1个多小时,因为内部有一千第六百货万条数据。

1⑦ 、选用二个科学的积存引擎

在 MySQL 中有三个存款和储蓄引擎 MyISAM 和
InnoDB,每种引擎都有利有弊。酷壳以前作品《MySQL: InnoDB 照旧MyISAM?》切磋和那几个业务。

MyISAM
适合于某些索要多量询问的利用,但其对于有雅量写操作并不是很好。甚至你只是亟需update2个字段,整个表都会被锁起来,而其余进度,即便是读进度都心有余而力不足操作直到读操作完结。其余,MyISAM
对于 SELECT COUNT(*) 那类的总括是超快无比的。

InnoDB 的样子会是叁个至极复杂的存款和储蓄引擎,对于部分小的选择,它会比 MyISAM
还慢。他是它帮忙“行锁”
,于是在写操作相比多的时候,会更能够。并且,他还匡助更加多的高档应用,比如:事务。

1捌 、小心“永久链接”

“永久链接”的指标是用来压缩重复创造MySQL链接的次数。当三个链接被创建了,它会永远处于连接的图景,即就是数据库操作已经结束了。而且,自从大家的Apache初叶选定它的子进程后——也正是说,下三遍的HTTP请求会引用Apache的子进度,并引用相同的
MySQL 链接。

PHP手册:mysql_pconnect()
在答辩上来说,那听起来极度的不错。可是从个人经验(也是绝大部分人的)上来说,那么些成效创制出来的闲事越多。因为,你唯有个别许的链接数,内部存款和储蓄器难点,文件句柄数,等等。

再者,Apache
运转在无比并行的条件中,会创立很多众多的了经过。这正是干吗那种“永久链接”的建制工作地倒霉的缘故。在你说了算要选用“永久链接”在此以前,你必要杰出地考虑一下你的满贯系统的架构。

参考

1⑨ 、当查问较慢的时候,可用Join来改写一下该查询来进行优化

    mysql> select sql_no_cache * from guang_deal_outs where deal_id in (select id from guang_deals where id = 100017151) ;
 Empty set (18.87 sec)

    mysql> select sql_no_cache a.* from guang_deal_outs a inner join guang_deals b on a.deal_id = b.id where b.id = 100017151;
    Empty set (0.01 sec)

原因
mysql> desc select sql_no_cache * from guang_deal_outs where deal_id in (select id from guang_deals where id = 100017151) ;
+----+--------------------+-----------------+-------+---------------+---------+---------+-------+----------+-------------+
| id | select_type        | table           | type  | possible_keys | key     | key_len | ref   | rows     | Extra       |
+----+--------------------+-----------------+-------+---------------+---------  +---------+-------+----------+-------------+
|  1 | PRIMARY            | guang_deal_outs | ALL   | NULL          | NULL    |     NULL    | NULL  | 18633779 | Using where |
|  2 | DEPENDENT SUBQUERY | guang_deals     | const | PRIMARY       | PRIMARY |     4       | const |        1 | Using index |
+----+--------------------+-----------------+-------+---------------+---------  +---------+-------+----------+-------------+
2 rows in set (0.04 sec)

mysql> desc select sql_no_cache a.* from guang_deal_outs a inner join guang_deals b on a.deal_id = b.id where b.id = 100017151;
+----+-------------+-------+-------+----------------------  +----------------------+---------+-------+------+-------------+
| id | select_type | table | type  | possible_keys        | key                     | key_len | ref   | rows | Extra       |
+----+-------------+-------+-------+----------------------  +----------------------+---------+-------+------+-------------+
|  1 | SIMPLE      | b     | const | PRIMARY              | PRIMARY                 | 4       | const |    1 | Using index |
|  1 | SIMPLE      | a     | ref   | idx_guang_dlout_dlid |     idx_guang_dlout_dlid | 4       | const |    1 |             |
+----+-------------+-------+-------+----------------------    +----------------------+---------+-------+------+-------------+  
 2 rows in set (0.05 sec)

其实在 guang_deal_outs 在deal_id 上也是有目录的。
实际上本人想把子查询设置为

    select * from guang_deal_outs where deal_id in (select id from guang_deals where id = 100017151);

化为上面包车型地铁典范

select * from guang_deal_outs where deal_id in (100017151);

但不幸的是,真实情状正好相反。MySQL试图让它和外边的表发生联系来“帮忙”优化查询,它认为上边包车型地铁exists格局更有成效

select * from guang_deal_outs where exists (select * from guang_deals where id = 100017151 and id = guang_deal_outs.deal_id);

那种in子查询的样式,在外部表(比如下面的guang_deals)数据量比较大的时候功效是很差的(要是对于较小的表,不会导致分明地影响)

参考:
http://codingstandards.iteye.com/blog/1344833
http://coolshell.cn/articles/1846.html
http://hi.baidu.com/yzx110/item/74892ab6fc4601a5eaba93e1

相关文章