1、MySQL性能优化必备25条今天,数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显。关于数据库的性能,这并不只是DBA才需要担心的事,而这更是我们程序员 需要去关注的事情。当我们去设计数据库表结构,对操作数据库时(尤其是查表时的SQL语句),我们都需要注意数据操作的性能。这里,我们不会讲过多的 SQL语句的优化,而只是针对MySQL这一Web应用最多的数据库。希望下面的这些优化技巧对你有用。1. 为查询缓存优化你的查询 22. EXPLAIN 你的 SELECT 查询 23. 当只要一行数据时使用 LIMIT 1 34. 存储引擎优化 35. 在Join表的时候使用相当类
2、型的例,并将其索引 66. 千万不要 ORDER BY RAND() 67. 避免 SELECT * 68. 永远为每张表设置一个ID 79. 使用 ENUM 而不是 VARCHAR 710. 从 PROCEDURE ANALYSE() 取得建议 711. 尽可能的使用 NOT NULL 812. Prepared Statements 813. 无缓冲的查询 914. 把IP地址存成 UNSIGNED INT 915. 固定长度的表会更快 916. 垂直分割 917. 拆分大的 DELETE 或 INSERT 语句 1018. 越小的列会越快 1019. 选择正确的存储引擎 1020. 使用
3、一个对象关系映射器(Object Relational Mapper) 1121. 小心“永久链接” 1122、分库分表 1123、不停机修改mysql表结构 1124、MySQL性能优化的参数 1225. 为搜索字段建索引 131. 为查询缓存优化你的查询大多数的MySQL服务器都开启了查询缓存。这是提高性最有效的方法之一,而且这是被MySQL的数据库引擎处理的。当有很多相同的查询被执行了多次的时候,这些查询结果会被放到一个缓存中,这样,后续的相同的查询就不用操作表而直接访问缓存结果了。这里最主要的问题是,对于程序员来说,这个事情是很容易被忽略的。因为,我们某些查询语句会让MySQL不使用缓
4、存。请看下面的示例:/ 查询缓存不开启$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函数都不会开启查询缓存,因为这些函数的返回是会不定的易变的
5、。所以,你所需要的就是用一个变量来代替MySQL的函数,从而开启缓存。2. EXPLAIN 你的 SELECT 查询使用EXPLAIN关键字可以让你知道MySQL是如何处理你的SQL语句的。这可以帮你分析你的查询语句或是表结构的性能瓶颈。EXPLAIN 的查询结果还会告诉你,你的索引主键被如何利用的,你的数据表是如何被搜索和排序的等等。挑一个你的SELECT语句(推荐挑选那个最复杂的,有多表联接的),把关键字EXPLAIN加到前面。你可以使用phpmyadmin来做这个事。然后,你会看到一张表格。下面的这个示例中,我们忘记加上了group_id索引,并且有表联接:当我们为 group_id 字
6、段加上索引后:我们可以看到,前一个结果显示搜索了 7883 行,而后一个只是搜索了两个表的 9 和 16 行。查看rows列可以让我们找到潜在的性能问题。3. 当只要一行数据时使用 LIMIT 1当你查询表的有些时候,你已经知道结果只会有一条结果,但因为你可能需要去fetch游标,或是你也许会去检查返回的记录数。在这种情况下,加上 LIMIT 1 可以增加性能。这样一样,MySQL数据库引擎会在找到一条数据后停止搜索,而不是继续往后查少下一条符合记录的数据。下面的示例,只是为了找一下是否有“中国”的用户,很明显,后面的会比前面的更有效率。(请注意,第一条中是Select *,第二条是Selec
7、t 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) / .4. 存储引擎优化MySQL支持不同的存储引擎,主要使用的有MyISAM和InnoDB。4.1 MyISAMMyISAM管理非事务表。它提供高速存储和检索,以及全文搜索能力。MyISAM在所有M
8、ySQL配置里被支持,它是默认的存储引擎,除非配置MySQL默认使用另外一个引擎。4.1.1 MyISAM特性4.1.1.1 MyISAM Properties1) 不支持事务,宕机会破坏表2) 使用较小的内存和磁盘空间3) 基于表的锁,并发更新数据会出现严重性能问题4) MySQL只缓存Index,数据由OS缓存4.1.1.2 Typical MyISAM usages1) 日志系统2) 只读或者绝大部分是读操作的应用3) 全表扫描4) 批量导入数据5) 没有事务的低并发读/写4.1.2 MyISAM优化要点1) 声明列为NOT NULL,可以减少磁盘存储。2) 使用optimize tab
9、le做碎片整理,回收空闲空间。注意仅仅在非常大的数据变化后运行。3) Deleting/updating/adding大量数据的时候禁止使用index。使用ALTER TABLE t DISABLE KEYS。4) 设置myisam_max_extra_sort_file_size足够大,可以显著提高repair table的速度。4.1.3 MyISAM Table Locks1) 避免并发insert,update。2) 可以使用insert delayed,但是有可能丢失数据。3) 优化查询语句。4) 水平分区。5) 垂直分区。6) 如果都不起作用,使用InnoDB。4.1.4 MyIS
10、AM Key Cache1) 设置key_buffer_size variable。MyISAN最主要的cache设置,用于缓存MyISAM表格的index数据,该参数只对MyISAM有影响。通常在只使用 MyISAM的Server中设置25-33%的内存大小。2) 可以使用几个不同的Key Caches(对一些hot data)。a) SET GLOBAL test.key_buffer_size=512*1024;b) CACHE INDEX t1.i1, t2.i1, t3 IN test;2) Preload index到Cache中可以提高查询速度。因为preloading inde
11、x是顺序的,所以非常快。a) LOAD INDEX INTO CACHE t1, t2 IGNORE LEAVES;4.2 InnoDBInnoDB 给MySQL提供了具有提交,回滚和崩溃恢复能力的事务安全(ACID兼容)存储引擎。InnoDB提供row level lock,并且也在SELECT语句提供一个Oracle风格一致的非锁定读。这些特色增加了多用户部署和性能。没有在InnoDB中扩大锁定的需要,因 为在InnoDB中row level lock适合非常小的空间。InnoDB也支持FOREIGN KEY约束。在SQL查询中,你可以自由地将InnoDB类型的表与其它MySQL的表的类型
12、混合起来,甚至在同一个查询中也可以混合。InnoDB 是为在处理巨大数据量时获得最大性能而设计的。它的CPU使用效率非常高。InnoDB存储引擎已经完全与MySQL服务器整合,InnoDB存储引擎为在内存中缓存数据和索引而维持它自己的缓冲池。 InnoDB存储它的表&索引在一个表空间中,表空间可以包含数个文件(或原始磁盘分区)。这与MyISAM表不同,比如在MyISAM表中每个 表被存在分离的文件中。InnoDB 表可以是任何大小,即使在文件尺寸被限制为2GB的操作系统上。许多需要高性能的大型数据库站点上使用了 InnoDB引擎。著名的Internet新闻站点Slashdot.org运行在In
13、noDB上。 Mytrix, Inc.在InnoDB上存储超过1TB的数据,还有一些其它站点在InnoDB上处理平均每秒800次插入/更新的负荷。4.2.1 InnoDB特性4.2.1.1 InnoDB Properties1) 支持事务,ACID,外键。2) Row level locks。3) 支持不同的隔离级别。4) 和MyISAM相比需要较多的内存和磁盘空间。5) 没有键压缩。6) 数据和索引都缓存在内存hash表中。4.2.1.2 InnoDB Good For1) 需要事务的应用。2) 高并发的应用。3) 自动恢复。4) 较快速的基于主键的操作。4.2.2 InnoDB优化要点1)
14、 尽量使用short,integer的主键。2) Load/Insert数据时按主键顺序。如果数据没有按主键排序,先排序然后再进行数据库操作。3) 在Load数据是为设置SET UNIQUE_CHECKS=0,SET FOREIGN_KEY_CHECKS=0,可以避免外键和唯一性约束检查的开销。4) 使用prefix keys。因为InnoDB没有key压缩功能。4.2.3 InnoDB服务器端设定innodb_buffer_pool_size:这是InnoDB最重要的设置,对InnoDB性能有决定性的影响。默认的设置只有8M,所 以默认的数据库设置下面InnoDB性能很差。在只有InnoDB
15、存储引擎的数据库服务器上面,可以设置60-80%的内存。更精确一点,在内存容量允许 的情况下面设置比InnoDB tablespaces大10%的内存大小。innodb_data_file_path:指定表数据和索引存储的空间,可以是一个或者多个文件。最后一个数据文件必须是自动扩充的,也只 有最后一个文件允许自动扩充。这样,当空间用完后,自动扩充数据文件就会自动增长(以8MB为单位)以容纳额外的数据。例如: innodb_data_file_path=/disk1/ibdata1:900M;/disk2/ibdata2:50M:autoextend两 个数据文件放在不同的磁盘上。数据首先放在i
16、bdata1中,当达到900M以后,数据就放在ibdata2中。一旦达到50MB,ibdata2将以 8MB为单位自动增长。如果磁盘满了,需要在另外的磁盘上面增加一个数据文件。innodb_autoextend_increment: 默认是8M, 如果一次insert数据量比较多的话, 可以适当增加.innodb_data_home_dir:放置表空间数据的目录,默认在mysql的数据目录,设置到和MySQL安装文件不同的分区可以提高性能。innodb_log_file_size:该参数决定了recovery speed。太大的话recovery就会比较慢,太小了影响查询性能,一般取256M可
17、以兼顾性能和recovery的速度。innodb_log_buffer_size:磁盘速度是很慢的,直接将log写道磁盘会影响InnoDB的性能,该参数设定了log buffer的大小,一般4M。如果有大的blob操作,可以适当增大。innodb_flush_logs_at_trx_commit=2: 该参数设定了事务提交时内存中log信息的处理。1) =1时,在每个事务提交时,日志缓冲被写到日志文件,对日志文件做到磁盘操作的刷新。Truly ACID。速度慢。2) =2时,在每个事务提交时,日志缓冲被写到文件,但不对日志文件做到磁盘操作的刷新。只有操作系统崩溃或掉电才会删除最后一秒的事务,不
18、然不会丢失事务。3) =0时, 日志缓冲每秒一次地被写到日志文件,并且对日志文件做到磁盘操作的刷新。任何mysqld进程的崩溃会删除崩溃前最后一秒的事务innodb_file_per_table:可以存储每个InnoDB表和它的索引在它自己的文件中。transaction-isolation=READ-COMITTED: 如果应用程序可以运行在READ-COMMITED隔离级别,做此设定会有一定的性能提升。innodb_flush_method: 设置InnoDB同步IO的方式:1) Default 使用fsync()。2) O_SYNC 以sync模式打开文件,通常比较慢。3) O_DIRE
19、CT,在Linux上使用Direct IO。可以显著提高速度,特别是在RAID系统上。避免额外的数据复制和double buffering(mysql buffering 和OS buffering)。innodb_thread_concurrency: InnoDB kernel最大的线程数。1) 最少设置为(num_disks+num_cpus)*2。2) 可以通过设置成1000来禁止这个限制5. 在Join表的时候使用相当类型的例,并将其索引如果你的应用程序有很多 JOIN 查询,你应该确认两个表中Join的字段是被建过索引的。这样,MySQL内部会启动为你优化Join的SQL语句的机制
20、。而且,这些被用来Join的字段,应该是相同的类型的。例如:如果你要把 DECIMAL 字段和一个 INT 字段Join在一起,MySQL就无法使用它们的索引。对于那些STRING类型,还需要有相同的字符集才行。(两个表的字符集有可能不一样)/ 在state中查找company$r = mysql_query(SELECT company_name FROM users LEFT JOIN companies ON (users.state = companies.state) WHERE users.id = $user_id);/ 两个 state 字段应该是被建过索引的,而且应该是相当的
21、类型,相同的字符集。6. 千万不要 ORDER BY RAND()想打乱返回的数据行?随机挑一个数据?真不知道谁发明了这种用法,但很多新手很喜欢这样用。但你却不了解这样做有多么可怕的性能问题。如果你真的想把返回的数据行打乱了,你有N种方法可以达到这个目的。这样使用只让你的数据库的性能呈指数级的下降。这里的问题是:MySQL会不得不去执行 RAND()函数(很耗CPU时间),而且这是为了每一行记录去记行,然后再对其排序。就算是你用了Limit 1也无济于事(因为要排序)下面的示例是随机挑一条记录/ 千万不要这样做:$r = mysql_query(SELECT username FROM use
22、r ORDER BY RAND() LIMIT 1);/ 这要会更好:$r = mysql_query(SELECT count(*) FROM user);$d = mysql_fetch_row($r);$rand = mt_rand(0,$d0 - 1);$r = mysql_query(SELECT username FROM user LIMIT $rand, 1);7. 避免 SELECT *从数据库里读出越多的数据,那么查询就会变得越慢。并且,如果你的数据库服务器和WEB服务器是两台独立的服务器的话,这还会增加网络传输的负载。所以,你应该养成一个需要什么就取什么的好的习惯。/ 不
23、推荐$r = mysql_query(SELECT * FROM user WHERE user_id = 1);$d = mysql_fetch_assoc($r);echo Welcome $dusername;/ 推荐$r = mysql_query(SELECT username FROM user WHERE user_id = 1);$d = mysql_fetch_assoc($r);echo Welcome $dusername;8. 永远为每张表设置一个ID我们应该为数据库里的每张表都设置一个ID做为其主键,而且最好的是一个INT型的(推荐使用UNSIGNED),并设置上自动
24、增加的AUTO_INCREMENT标志。就算是你 users 表有一个主键叫 “email”的字段,你也别让它成为主键。使用 VARCHAR 类型来当主键会使用得性能下降。另外,在你的程序中,你应该使用表的ID来构造你的数据结构。而且,在MySQL数据引擎下,还有一些操作需要使用主键,在这些情况下,主键的性能和设置变得非常重要,比如,集群,分区在这里,只有一个情况是例外,那就是“关联表”的“外键”,也就是说,这个表的主键,通过若干个别的表的主键构成。我们把这个情况叫做“外键”。比如:有一个“学生表”有学生的ID,有一个“课程表”有课程ID,那么,“成绩表”就是“关联表”了,其关联了学生表和课程
25、表,在成绩表中,学生ID和课程ID叫 “外键”其共同组成主键。9. 使用 ENUM 而不是 VARCHARENUM类型是非常快和紧凑的。实际上,其保存的是 TINYINT,但其外表上显示为字符串。这样一来,用这个字段来做一些选项列表变得相当的完美。如果你有一个字段,比如“性别”,“国家”,“民族”,“状态”或“部门”,你知道这些字段的取值是有限而且固定的,那么,你应该使用 ENUM 而不是 VARCHAR。MySQL也有一个“建议”(见第十条)告诉你怎么去重新组织你的表结构。当你有一个 VARCHAR 字段时,这个建议会告诉你把其改成 ENUM 类型。使用 PROCEDURE ANALYSE(
26、) 你可以得到相关的建议。10. 从 PROCEDURE ANALYSE() 取得建议PROCEDURE ANALYSE()会让 MySQL 帮你去分析你的字段和其实际的数据,并会给你一些有用的建议。只有表中有实际的数据,这些建议才会变得有用,因为要做一些大的决定是需要有数据作为基础的。例如,如果你创建了一个 INT 字段作为你的主键,然而并没有太多的数据,那么,PROCEDURE ANALYSE()会建议你把这个字段的类型改成 MEDIUMINT 。或是你使用了一个 VARCHAR 字段,因为数据不多,你可能会得到一个让你把它改成 ENUM 的建议。这些建议,都是可能因为数据不够多,所以决策
27、做得就不够准。在phpmyadmin里,你可以在查看表时,点击 “Propose table structure” 来查看这些建议一定要注意,这些只是建议,只有当你的表里的数据越来越多时,这些建议才会变得准确。一定要记住,你才是最终做决定的人。11. 尽可能的使用 NOT NULL除非你有一个很特别的原因去使用 NULL 值,你应该总是让你的字段保持 NOT NULL。这看起来好像有点争议,请往下看。首先,问问你自己“Empty”和“NULL”有多大的区别(如果是INT,那就是0和NULL)?如果你觉得它们之间没有什么区别,那么你就不要使用NULL。(你知道吗?在 Oracle 里,NULL
28、和 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 nea
29、rest byte.”12. Prepared StatementsPrepared Statements很像存储过程,是一种运行在后台的SQL语句集合,我们可以从使用 prepared statements 获得很多好处,无论是性能问题还是安全问题。Prepared Statements 可以检查一些你绑定好的变量,这样可以保护你的程序不会受到“SQL注入式”攻击。当然,你也可以手动地检查你的这些变量,然而,手动的检查容易出问题, 而且很经常会被程序员忘了。当我们使用一些framework或是ORM的时候,这样的问题会好一些。在性能方面,当一个相同的查询被使用多次的时候,这会为你带来可观的性
30、能优势。你可以给这些Prepared Statements定义一些参数,而MySQL只会解析一次。虽然最新版本的MySQL在传输Prepared Statements是使用二进制形势,所以这会使得网络传输非常有效率。当然,也有一些情况下,我们需要避免使用Prepared Statements,因为其不支持查询缓存。但据说版本5.1后支持了。在PHP中要使用prepared statements,你可以查看其使用手册:mysqli 扩展或是使用数据库抽象层,如:PDO./ 创建 prepared statementif ($stmt = $mysqli-prepare(SELECT userna
31、me FROM user WHERE state=?) / 绑定参数 $stmt-bind_param(s, $state); / 执行 $stmt-execute(); / 绑定结果 $stmt-bind_result($username); / 移动游标 $stmt-fetch(); printf(%s is from %sn, $username, $state); $stmt-close();13. 无缓冲的查询正常的情况下,当你在当你在你的脚本中执行一个SQL语句的时候,你的程序会停在那里直到没这个SQL语句返回,然后你的程序再往下继续执行。你可以使用无缓冲查询来改变这个行为。关于这个事情,在PHP的文档中有一个非常
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1