干了三年的Java,你居然还不会MySQL性能优化!

前言

Mysql性能优化就算经过合理安排资源,调整系统参数使MYSQL运行更快,更节省资源。MYSQL性能优化包括查询速度优化,更新速度优化,mysql服务器优化等等。此处,介绍如下几个优化。包含,性能优化的介绍,查询优化,数据库结构优化,mysql服务器优化。mysql

Mysql优化,一方面是找出系统的瓶颈,提升mysql数据库总体的性能,另一个方面须要合理的结构设计和参数调整,以提升用户操做响应的速度。同时还要尽量节省系统资源,以便系统能够提供更大负荷的服务。mysql数据库优化是多方面的,原则是减小系统的瓶颈,减小资源的占用,增长系统反应的速度。小编这里总结了一份详细MySQL的思惟导图以及MySQL笔记500多页资料集锦 关注公众号:麒麟改bug。程序员

一、为查询优化你的查询

大多数的MySQL服务器都开启了查询缓存。这是提升性最有效的方法之一,并且这是被MySQL的数据库引擎处理的。当有不少相同的查询被执行了屡次的时候,这些查询结果会被放到一个缓存中,这样,后续的相同的查询就不用操做表而直接访问缓存结果了。面试

这里最主要的问题是,对于程序员来讲,这个事情是很容易被忽略的。由于,咱们某些查询语句会让MySQL不使用缓存。请看下面的示例:spring

// 查询缓存不开启$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语句的差异就是 CURDATE() ,MySQL的查询缓存对这个函数不起做用。因此,像 NOW() 和 RAND() 或是其它的诸如此类的SQL函数都不会开启查询缓存,由于这些函数的返回是会不定的易变的。因此,你所须要的就是用一个变量来代替MySQL的函数,从而开启缓存。sql

二、EXPLAIN 你的SELECT查询

使用EXPLAIN关键字可让你知道MySQL是如何处理你的SQL语句的。数据库

有表关联的查询,以下列:编程

select username, group_namefrom users ujoins 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) {// ...}

四、为搜索字段建索引

索引并不必定就是给主键或是惟一的字段。若是在你的表中,有某个字段你总要会常常用来作搜索,那么,请为其创建索引吧。服务器

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

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

六、千万不要ORDER BY RAND()

七、避免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数据引擎下,还有一些操做须要使用主键,在这些状况下,主键的性能和设置变得很是重要,好比,集群,分区……

九、使用 ENUM 而不是 VARCHAR ?

ENUM 类型是很是快和紧凑的。在实际上,其保存的是 TINYINT,但其外表上显示为字符串。这样一来,用这个字段来作一些选项列表变得至关的完美。

若是你有一个字段,好比“性别”,“国家”,“民族”,“状态”或“部门”,你知道这些字段的取值是有限并且固定的,那么,你应该使用 ENUM 而不是 VARCHAR。

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

PROCEDURE ANALYSE() 会让 MySQL 帮你去分析你的字段和其实际的数据,并会给你一些有用的建议。只有表中有实际的数据,这些建议才会变得有用,由于要作一些大的决定是须要有数据做为基础的。

例如,若是你建立了一个 INT 字段做为你的主键,然而并无太多的数据,那么,PROCEDURE ANALYSE()会建议你把这个字段的类型改为 MEDIUMINT 。或是你使用了一个 VARCHAR 字段,由于数据很少,你可能会获得一个让你把它改为 ENUM 的建议。这些建议,都是可能由于数据不够多,因此决策作得就不够准。

十一、尽量的使用 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.”

十二、把IP地址存成 UNSIGNED INT

不少程序员都会建立一个 VARCHAR(15) 字段来存放字符串形式的IP而不是整形的IP。若是你用整形来存放,只须要4个字节,而且你能够有定长的字段。并且,这会为你带来查询上的优点,尤为是当你须要使用这样的WHERE条件:IP between ip1 and ip2。

咱们必须要使用UNSIGNED INT,由于 IP地址会使用整个32位的无符号整形

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

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

固定长度的表会提升性能,由于MySQL搜寻得会更快一些,由于这些固定的长度是很容易计算下一个数据的偏移量的,因此读取的天然也会很快。而若是字段不是定长的,那么,每一次要找下一条的话,须要程序找到主键。

而且,固定长度的表也更容易被缓存和重建。不过,惟一的反作用是,固定长度的字段会浪费一些空间,由于定长的字段不管你用不用,他都是要分配那么多的空间。

1四、垂直分割

“垂直分割”是一种把数据库中的表按列变成几张表的方法,这样能够下降表的复杂度和字段的数目,从而达到优化的目的。(之前,在银行作过项目,见过一张表有100多个字段,很恐怖)

示例一:在Users表中有一个字段是家庭地址,这个字段是可选字段,相比起,并且你在数据库操做的时候除了我的信息外,你并不须要常常读取或是改写这个字段。那么,为何不把他放到另一张表中呢? 这样会让你的表有更好的性能,你们想一想是否是,大量的时候,我对于用户表来讲,只有用户ID,用户名,口令,用户角色等会被常用。小一点的表老是会有好的性能。

示例二: 你有一个叫 “last_login” 的字段,它会在每次用户登陆时被更新。可是,每次更新时会致使该表的查询缓存被清空。因此,你能够把这个字段放到另外一个表中,这样就不会影响你对用户ID,用户名,用户角色的不停地读取了,由于查询缓存会帮你增长不少性能。

另外,你须要注意的是,这些被分出去的字段所造成的表,你不会常常性地去Join他们,否则的话,这样的性能会比不分割时还要差,并且,会是极数级的降低。

1五、拆分大的 DELETE 或 INSERT 语句

若是你须要在一个在线的网站上去执行一个大的 DELETE 或 INSERT 查询,你须要很是当心,要避免你的操做让你的整个网站中止相应。由于这两个操做是会锁表的,表一锁住了,别的操做都进不来了。

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的例子(2009年11月06日),一个简单的ALTER TABLE语句花了3个多小时,由于里面有一千六百万条数据。

1七、选择一个正确的存储引擎

在 MySQL 中有两个存储引擎 MyISAM 和 InnoDB,每一个引擎都有利有弊。酷壳之前文章《MySQL: InnoDB 仍是 MyISAM?》讨论和这个事情。

MyISAM 适合于一些须要大量查询的应用,但其对于有大量写操做并非很好。甚至你只是须要update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都没法操做直到读操做完成。另外,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)数据量比较大的时候效率是不好的(若是对于较小的表,不会形成显著地影响)

文章到这里就结束了!

小编这里总结了 MySQL笔记500多页资料集锦+1000道互联网大厂Java工程师面试题、spring、mybatis、jvm,Zookeeper,spring 关注公众号:麒麟改bug ,编程的世界永远向全部热爱编程的人开放,这是一个自由,平等,共享的世界,我始终是这样坚信的。

相关文章
相关标签/搜索