【转】MySQL品质优化的极品经验,随时补给

一,为查询优化你的查询

绝大部分的MySQL服务器都敞开了询问缓存。那是提升性最管用的措施之一,而且那是被MySQL的数据库引擎处理的。当有不少如出一辙的查询被实践了累累的时候,那些查询结果会被置于二个缓存中,那样,后续的如出一辙的查询就不要操作表而直白访问缓存结果了。

此处最首要的标题是,对于程序员来说,那个事情是很不难被忽略的。因为,我们一点查询语句会让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语句的歧异就是 CUPAJERODATE()
,MySQL的询问缓存对这些函数不起功用。所以,像 NOW() 和 RAND() 或是其它的诸如此类的SQL函数都不会开启查询缓存,因为这些函数的返回是会不定的易变的。所以,你所急需的就是用3个变量来代表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类型,还需要有相同的字符集才行(两个表的字符集有可能不一样)

陆,千万不要O奥迪Q7DE瑞虎 BY RAND()

7、避免SELECT *

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

因此,你应当养成2个内需怎样就取什么的好的习惯。

// 不推荐
$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']}";

八,永远为两张表安装1个ID

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

还要,在MySQL数据引擎下,还有一些操作要求使用主键,在这几个情形下,主键的性质和装置变得不得了重大,比如,集群,分区……

9、使用 ENUM 而不是 VARCHAR ?

ENUM 类型是卓殊快和严格的。在其实,其保存的是
TINYINT,但其外表上出示为字符串。那样一来,用那几个字段来做一些精选列表变得一定的八面玲珑。

一经您有多个字段,比如“性别”,“国家”,“民族”,“状态”或“部门”,你通晓那么些字段的取值是少数而且一定的,那么,你应有利用
ENUM 而不是 VAHighlanderCHA奇骏。

十,从 PROCEDURE ANALYSE() 取得指出 ?

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

例如,倘使你创造了多个 INT
字段作为你的主键,不过并从未太多的数码,那么,PROCEDURE
ANALYSE()会提出您把那一个字段的种类改成 MEDIUMINT 。或是你利用了二个VA汉兰达CHALX570 字段,因为数量不多,你大概会得到一个让您把它改成 ENUM
的指出。这个提出,都是唯恐因为数量不够多,所以决定做得就不够准。

1一,尽或然的使用 NOT NULL

除非您有二个很越发的缘由去接纳 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地址会选用所有三十一人的无符号整形

13、固定长度的表会更快

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

一向长度的表会提升品质,因为MySQL搜寻得会更快一些,因为那几个永恒的尺寸是很简单计算下三个数据的偏移量的,所以读取的当然也会很快。而即使字段不是定长的,那么,每便要找下一条的话,须要程序找到主键。

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

1四,垂直细分

“垂直细分”是一种把数据库中的表按列变成几张表的主意,那样可以下降表的复杂度和字段的多少,从而已毕优化的目标。(从前,在银行做过项目,见过一张表有100七个字段,很恐怖)

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

以身作则二: 你有三个叫 “last_login”
的字段,它会在历次用户登录时被更新。但是,每回换代时会导致该表的询问缓存被清空。所以,你可以把那一个字段放到另2个表中,这样就不会潜移默化你对用户ID,用户名,用户剧中人物的不停地读取了,因为查询缓存会帮你增加很多性质。

此外,你须求小心的是,这个被分出去的字段所形成的表,你不会平时性地去Join他们,不然的话,那样的性质会比不分割时还要差,而且,会是极数级的降低。

1伍,拆分大的 DELETE 或 INSE牧马人T 语句

借使您须要在二个在线的网站上去执行一个大的 DELETE 或 INSERubiconT
查询,你需求相当小心,要幸免你的操作让你的漫天网站截至相应。因为那八个操作是会锁表的,表一锁住了,其他操作都进不来了。

Apache
会有那2个的子进度或线程。所以,其行事起来分外有成效,而大家的服务器也不希望有太多的子进程,线程和数据库链接,这是大幅度的占服务器能源的政工,特别是内存。

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

为此,倘诺你有2个大的处理,你定你早晚把其拆分,使用 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 查看所有的数据类型。

若果2个表只会有几列罢了(比如说字典表,配置表),那么,我们就从未理由使用
INT 来做主键,使用 MEDIUMINT, SMALLINT 或是更小的 TINYINT
会更经济部分。如若你不需求记录时间,使用 DATE 要比 DATETIME 好得多。

自然,你也亟需留够充裕的恢弘空间,不然,你之后来干这几个事,你会死的很羞耻,参看Slashdot的例子(二零一零年一月0九日),一个归纳的ALTELacrosseTABLE语句花了3个多钟头,因为中间有一千六百万条数据。

17、选取一个毋庸置疑的存储引擎

在 MySQL 中有八个存储引擎 MyISAM 和
InnoDB,每一个引擎都有利有弊。酷壳从前小说《MySQL: InnoDB 依旧MyISAM?》探讨和这么些工作。

MyISAM
适合于一些亟需大批量询问的应用,但其对于有大气写操作并不是很好。甚至你只是内需update三个字段,整个表都会被锁起来,而别的进度,就终于读进度都不能操作直到读操作完结。别的,MyISAM
对于 SELECT COUNT(*) 那类的乘除是超快无比的。

InnoDB 的趋势会是3个分外复杂的积存引擎,对于有个别小的行使,它会比 MyISAM
还慢。他是它匡助“行锁”
,于是在写操作比较多的时候,会更卓绝。并且,他还扶助越来越多的高等应用,比如:事务。

1捌,小心“永久链接”

“永久链接”的目标是用来压缩重复成立MySQL链接的次数。当二个链接被创设了,它会永远地处连接的情事,就到底数据库操作已经收尾了。而且,自从大家的Apache早先选定它的子进度后——也等于说,下一遍的HTTP请求会引用Apache的子进度,并引用相同的
MySQL 链接。

PHP手册:mysql_pconnect()
在争鸣上的话,那听起来至极的没错。可是从个体经历(也是多数人的)上的话,这么些成效创造出来的小事更加多。因为,你唯有半点的链接数,内存难点,文件句柄数,等等。

同时,Apache
运营在无比并行的条件中,会创制很多居多的了经过。那就是怎么那种“永久链接”的建制工作地不佳的原故。在您控制要选拔“永久链接”在此以前,你须求优异地考虑一下你的整套种类的架构。

Oracle,参考

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

相关文章