数据库的操做愈来愈成为整个应用的性能瓶颈了,这点对于Web应用尤为明显。关于数据库的性能,这并不仅是DBA才须要担忧的事,而这更是我 们程序员须要去关注的事情。当咱们去设计数据库表结构,对操做数据库时(尤为是查表时的SQL语句),php
咱们都须要注意数据操做的性能。这里,咱们不会讲过 多的SQL语句的优化,而只是针对MySQL这一Web应用最多的数据库。但愿下面的这些优化技巧对你有用。mysql
大多数的MySQL服务器都开启了查询缓存。这是提升性最有效的方法之一,并且这是被MySQL的数据库引擎处理的。当有不少相同的查询被执行了屡次的时候,这些查询结果会被放到一个缓存中,这样,后续的相同的查询就不用操做表而直接访问缓存结果了。程序员
这里最主要的问题是,对于程序员来讲,这个事情是很容易被忽略的。由于,咱们某些查询语句会让MySQL不使用缓存。请看下面的示例:sql
1 // 查询缓存不开启 2 $r = mysql_query("SELECT username FROM user WHERE signup_date >= CURDATE()"); 3 4 // 开启查询缓存 5 $today = date("Y-m-d"); 6 $r = mysql_query("SELECT username FROM user WHERE signup_date >= '$today'");
上面两条SQL语句的差异就是 CURDATE() ,MySQL的查询缓存对这个函数不起做用。因此,像 NOW() 和 RAND() 或是其它的诸如此类的SQL函数都不会开启查询缓存,由于这些函数的返回是会不定的易变的。数据库
因此,你所须要的就是用一个变量来代替MySQL的函数,从而 开启缓存。缓存
使用 EXPLAIN 关键字可让你知道MySQL是如何处理你的SQL语句的。这能够帮你分析你的查询语句或是表结构的性能瓶颈。安全
EXPLAIN 的查询结果还会告诉你你的索引主键被如何利用的,你的数据表是如何被搜索和排序的……等等,等等。服务器
挑一个你的SELECT语句(推荐挑选那个最复杂的,有多表联接的),把关键字EXPLAIN加到前面。你可使用phpmyadmin来作这个事。而后,你会看到一张表格。网络
当你查询表的有些时候,你已经知道结果只会有一条结果,但由于你可能须要去fetch游标,或是你也许会去检查返回的记录数。数据结构
在这种状况下,加上 LIMIT 1 能够增长性能。这样同样,MySQL数据库引擎会在找到一条数据后中止搜索,而不是继续日后查少下一条符合记录的数据。
下面的示例,只是为了找一下是否有“中国”的用户,很明显,后面的会比前面的更有效率。(请注意,第一条中是Select *,第二条是Select 1)
1 // 没有效率的: 2 $r = mysql_query("SELECT * FROM user WHERE country = 'China'"); 3 if (mysql_num_rows($r) > 0) { 4 // ... 5 } 6 7 // 有效率的: 8 $r = mysql_query("SELECT 1 FROM user WHERE country = 'China' LIMIT 1"); 9 if (mysql_num_rows($r) > 0) { 10 // ... 11 }
索引并不必定就是给主键或是惟一的字段。若是在你的表中,有某个字段你总要会常常用来作搜索,那么,请为其创建索引吧。
另外,你应该也须要知道什么样的搜索是不能使用正常的索引的。例如,当你须要在一篇大的文章中搜索一个词时,
如: “WHERE post_content LIKE ‘%apple%’”,索引多是没有意义的。你可能须要使用MySQL全文索引 或是本身作一个索引(好比说:搜索关键词或是Tag什么的)
若是你的应用程序有不少 JOIN 查询,你应该确认两个表中Join的字段是被建过索引的。这样,MySQL内部会启动为你优化Join的SQL语句的机制。
并且,这些被用来Join的字段,应该是相同的类型的。例如:若是你要把 DECIMAL 字段和一个 INT 字段Join在一块儿,MySQL就没法使用它们的索引。对于那些STRING类型,还须要有相同的字符集才行。(两个表的字符集有可能不同)
1 // 在state中查找company 2 $r = mysql_query("SELECT company_name FROM users 3 LEFT JOIN companies ON (users.state = companies.state) 4 WHERE users.id = $user_id"); 5 6 // 两个 state 字段应该是被建过索引的,并且应该是至关的类型,相同的字符集。
想打乱返回的数据行?随机挑一个数据?真不知道谁发明了这种用法,但不少新手很喜欢这样用。但你确不了解这样作有多么可怕的性能问题。
若是你真的想把返回的数据行打乱了,你有N种方法能够达到这个目的。这样使用只让你的数据库的性能呈指数级的降低。
这里的问题是:MySQL会不得 不去执行RAND()函数(很耗CPU时间),并且这是为了每一行记录去记行,而后再对其排序。就算是你用了Limit 1也无济于事(由于要排序)
下面的示例是随机挑一条记录
1 // 千万不要这样作: 2 $r = mysql_query("SELECT username FROM user ORDER BY RAND() LIMIT 1"); 3 4 // 这要会更好: 5 $r = mysql_query("SELECT count(*) FROM user"); 6 $d = mysql_fetch_row($r); 7 $rand = mt_rand(0,$d[0] - 1); 8 9 $r = mysql_query("SELECT username FROM user LIMIT $rand, 1");
从数据库里读出越多的数据,那么查询就会变得越慢。而且,若是你的数据库服务器和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作为其主键,并且最好的是一个INT型的(推荐使用UNSIGNED),并设置上自动增长的AUTO_INCREMENT标志。
就算是你 users 表有一个主键叫 “email”的字段,你也别让它成为主键。使用 VARCHAR 类型来当主键会使用得性能降低。另外,在你的程序中,你应该使用表的ID来构造你的数据结构。
并且,在MySQL数据引擎下,还有一些操做须要使用主键,在这些状况下,主键的性能和设置变得很是重要,好比,集群,分区……
在这里,只有一个状况是例外,那就是“关联表”的“外键”,也就是说,这个表的主键,经过若干个别的表的主键构成。咱们把这个状况叫作“外键”。
好比:有一个“学生表”有学生的ID,有一个“课程表”有课程ID,那么,“成绩表”就是“关联表”了,其关联了学生表和课程表,在成绩表中,学生ID和课程ID叫“外键”其共同组成主键。
ENUM 类型是很是快和紧凑的。在实际上,其保存的是 TINYINT,但其外表上显示为字符串。这样一来,用这个字段来作一些选项列表变得至关的完美。
若是你有一个字段,好比“性别”,“国家”,“民族”,“状态”或“部门”,你知道这些字段的取值是有限并且固定的,那么,你应该使用 ENUM 而不是 VARCHAR。
MySQL也有一个“建议”(见第十条)告诉你怎么去从新组织你的表结构。当你有一个 VARCHAR 字段时,这个建议会告诉你把其改为 ENUM 类型。使用 PROCEDURE ANALYSE() 你能够获得相关的建议。
PROCEDURE ANALYSE() 会让 MySQL 帮你去分析你的字段和其实际的数据,并会给你一些有用的建议。只有表中有实际的数据,这些建议才会变得有用,由于要作一些大的决定是须要有数据做为基础的。
例如,若是你建立了一个 INT 字段做为你的主键,然而并无太多的数据,那么,PROCEDURE ANALYSE()会建议你把这个字段的类型改为MEDIUMINT 。
或是你使用了一个 VARCHAR 字段,由于数据很少,你可能会获得一个让你把它改为 ENUM 的建议。这些建议,都是可能由于数据不够多,因此决策作得就不够准。
必定要注意,这些只是建议,只有当你的表里的数据愈来愈多时,这些建议才会变得准确。必定要记住,你才是最终作决定的人。
除非你有一个很特别的缘由去使用 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.”
Prepared Statements很像存储过程,是一种运行在后台的SQL语句集合,咱们能够从使用 prepared statements 得到不少好处,不管是性能问题仍是安全问题。
Prepared Statements 能够检查一些你绑定好的变量,这样能够保护你的程序不会受到“SQL注入式”攻击。固然,你也能够手动地检查你的这些变量,然而,手动的检查容易出问题,并且很常常会被程序员忘了。
当咱们使用一些framework或是ORM的时候,这样的问题会好一些。
在性能方面,当一个相同的查询被使用屡次的时候,这会为你带来可观的性能优点。你能够给这些Prepared Statements定义一些参数,而MySQL只会解析一次。
虽然最新版本的MySQL在传输Prepared Statements是使用二进制形势,因此这会使得网络传输很是有效率。
固然,也有一些状况下,咱们须要避免使用Prepared Statements,由于其不支持查询缓存。但听说版本5.1后支持了。
在PHP中要使用prepared statements,你能够查看其使用手册:mysqli 扩展 或是使用数据库抽象层,如: PDO.
1 // 建立 prepared statement 2 if ($stmt = $mysqli->prepare("SELECT username FROM user WHERE state=?")) { 3 4 // 绑定参数 5 $stmt->bind_param("s", $state); 6 7 // 执行 8 $stmt->execute(); 9 10 // 绑定结果 11 $stmt->bind_result($username); 12 13 // 移动游标 14 $stmt->fetch(); 15 16 printf("%s is from %s\n", $username, $state); 17 18 $stmt->close(); 19 }