SQL语句的优化(Mysql)
1.为查询缓存优化你的查询
大多数的MySQL服务器都开启了查询缓存。这是提高性最有效的方法之一,而且这是被MySQL的数据库引擎处理的。当有很多相同的查询被执行了多次的时候,这些查询结果会被放到一个缓存中,这样,后续的相同的查询就不用操作表而直接访问缓存结果了。
这里最主要的问题是,对于程序员来说,这个事情是很容易被忽略的。因为,我们某些查询语句会让MySQL不使用缓存。请看下面的示例:

上面两条SQL语句的差别就是CURDATE(),MySQL的查询缓存对函数不起作用。所以,像NOW()和RAND()或是其它的诸如此类的SQL函数都不会开启查询缓存,因为这些函数的返回是会不定的易变的。所以,你所需要的就是用一个变量来代替MySQL的函数,从而开启缓存。
2.EXPLAIN你的SELECT查询
使用EXPLAIN关键字可以让你知道MySQL是如何处理你的SQL语句的。这可以帮你分析你的查询语句或是表结构的性能瓶颈。
各个属性的含义
id:select查询的序列号
select_type:
select查询的类型,主要是区别普通查询和联合查询、子查询之类的复杂查询。
table:输出的行所引用的表。
type:联合查询所使用的类型。
type显示的是访问类型,是较为重要的一个指标,结果值从好到坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
一般来说,得保证查询至少达到range级别,最好能达到ref。
possible_keys:
指出MySQL能使用哪个索引在该表中找到行。如果是空的,没有相关的索引。
这时要提高性能,可通过检验WHERE子句,看是否引用某些字段,或者检查字段不是适合索引。
key:
显示MySQL实际决定使用的键。如果没有索引被选择,键是NULL。
key_len:
显示MySQL决定使用的键长度。
如果键是NULL,长度就是NULL。文档提示特别注意这个值可以得出一个多重主键里mysql实际使用了哪一部分。
ref:
显示哪个字段或常数与key一起被使用。
rows:
这个数表示mysql要遍历多少数据才能找到,在innodb上是不准确的。
Extra:
如果是Only index,这意味着信息只用索引树中的信息检索出的,这比扫描整个表要快。
如果是where used,就是使用上了where限制。
如果是impossible where 表示用不着where,一般就是没查出来啥。
如果此信息显示Using filesort或者Using temporary的话会很吃力,
WHERE和ORDER BY的索引经常无法兼顾,如果按照WHERE来确定索引,那么在ORDER BY时,
就必然会引起Using filesort,这就要看是先过滤再排序划算,还是先排序再过滤划算。
3.当只要一行数据时使用LIMIT1
当你查询表的有些时候,你已经知道结果只会有一条结果,但因为你可能需要去fetch游标,或是你也许会去检查返回的记录数。
在这种情况下,加上LIMIT 1可以增加性能。这样一样,MySQL数据库引擎会在找到一条数据后停止搜索,而不是继续往后查少下一条符合记录的数据。
下面的示例,只是为了找一下是否有“中国”的用户,很明显,后面的会比前面的更有效率。(请注意,第一条中是Select *,第二条是Select 1)

4.为搜索字段建索引
建立索引的同时也需要知道哪些SQL查询是能触发索引的,哪些是不能的
例如 name like 'zhang%'; 是可以触发索引的
name like '%zhang%'; 是不能触发索引的
5.在Join表的时候使用相当类型的例,并将其索引
如果你的应用程序有很多JOIN查询,你应该确认两个表中Join的字段是被建过索引的。这样,MySQL内部会启动为你优化Join的SQL语句的机制。而且,这些被用来Join的字段,应该是相同的类型的。例如:如果你要把DECIMAL字段和一个INT字段Join在一起,MySQL就无法使用它们的索引。对于那些STRING类型,还需要有相同的字符集才行。(两个表的字符集有可能不一样)程序员站

6.千万不要ORDER BY RAND()
想打乱返回的数据行?随机挑一个数据?真不知道谁发明了这种用法,但很多新手很喜欢这样用。但你确不了解这样做有多么可怕的性能问题。
如果你真的想把返回的数据行打乱了,你有N种方法可以达到这个目的。这样使用只让你的数据库的性能呈指数级的下降。这里的问题是:MySQL会不得不去执行RAND()函数(很耗CPU时间),而且这是为了每一行记录去记行,然后再对其排序。就算是你用了Limit
1也无济于事(因为要排序)
7.避免 SELECT *
从数据库里读出越多的数据,那么查询就会变得越慢。并且,如果你的数据库服务器和WEB服务器是两台独立的服务器的话,这还会增加网络传输的负载。所以,你应该养成一个需要什么就取什么的好的习惯。
8.永远为每张表设置一个ID
我们应该为数据库里的每张表都设置一个ID做为其主键,而且最好的是一个INT型的(推荐使用UNSIGNED),并设置上自动增加的AUTO_INCREMENT标志
不要用varchar,性能会下降很多
9.使用ENUM而不是VARCHAR
ENUM类型是非常快和紧凑的。在实际上,其保存的是TINYINT,但其外表上显示为字符串。这样一来,用这个字段来做一些选项列表变得相当的完美。如果你有一个字段,比如“性别”,“国家”,“民族”,“状态”或“部门”,你知道这些字段的取值是有限而且固定的,那么,你应该使用ENUM而不是VARCHAR。
MySQL也有一个“建议”(见第十条)告诉你怎么去重新组织你的表结构。当你有一个VARCHAR字段时,这个建议会告诉你把其改成ENUM类型。使用PROCEDURE ANALYSE() 你可以得到相关的建议。
10.从PROCEDURE ANALYSE()取得建议
PROCEDURE ANALYSE() 会让MySQL帮你去分析你的字段和其实际的数据,并会给你一些有用的建议。只有表中有实际的数据,这些建议才会变得有用,因为要做一些大的决定是需要有数据作为基础的
select * from sms_web_message procedure analyse();
其中 Optimal_fieldtype 是建议修改的类型
11.尽可能的使用NOT NULL
除非你有一个很特别的原因去使用NULL值,你应该总是让你的字段保持NOT NULL。这看起来好像有点争议,请往下看。
首先,问问你自己“Empty”和“NULL”有多大的区别(如果是INT,那就是0和NULL)?如果你觉得它们之间没有什么区别,那么你就不要使用NULL。(你知道吗?在Oracle里,NULL 和 Empty的字符串是一样的!)
不要以为 NULL 不需要空间,其需要额外的空间,并且,在你进行比较的时候,你的程序会更复杂。当然,这里并不是说你就不能使用NULL了,现实情况是很复杂的,依然会有些情况下,你需要使用NULL值
查询中,使用 is null的查询速度也大大降低
12. Prepared Statements
Prepared Statements很像存储过程,是一种运行在后台的SQL语句集合,我们可以从使用prepared statements获得很多好处,无论是性能问题还是安全问题。Prepared Statements可以检查一些你绑定好的变量,这样可以保护你的程序不会受到“SQL注入式”攻击。实际中应用程序中取代,例如Hibernate等的实现
13.Inner join和左连接,右连接,子查询
A. inner join内连接也叫等值连接是,left/rightjoin是外连接。
SELECT A.id,A.name,B.id,B.name FROM A LEFT JOIN B ON A.id =B.id;
SELECT A.id,A.name,B.id,B.name FROM A RIGHT JOIN ON B A.id= B.id;
SELECT A.id,A.name,B.id,B.name FROM A INNER JOIN ON A.id =B.id;
证实inner join性能比较快,因为inner join是等值连接,或许返回的行数比较少。但是我们要记得有些语句隐形的用到了等值连接,如:
SELECT A.id,A.name,B.id,B.name FROM A,B WHERE A.id = B.id;
推荐:能用inner join连接尽量使用inner join连接
B.子查询的性能又比外连接性能慢,尽量用外连接来替换子查询。
Select* from A where exists (select * from B where id>=3000 and A.uuid=B.uuid);
mysql是先对外表A执行全表查询,然后根据uuid逐次执行子查询,如果外层表是一个很大的表,我们可以想象查询性能会表现比这个更加糟糕。
一种简单的优化就是用innerjoin的方法来代替子查询,查询语句改为:
Select* from A inner join B using(uuid) where b.uuid>=3000;
C.在使用ON 和 WHERE 的时候,记得它们的顺序,如:
SELECT A.id,A.name,B.id,B.name FROM A LEFT JOIN B ON A.id =B.id WHERE B.NAME=’XXX’
执行过程会先执行ON 后面先过滤掉B表的一些行数。然而WHERE是后再过滤他们两个连接产生的记录。
不过在这里提醒一下大家:ON后面的条件只能过滤出B表的条数,但是连接返回的记录的行数还是A表的行数是一样。如:
SELECT A.id,A.name,B.id,B.name FROM A LEFT JOIN B ON A.id =B.id;
返回的记录数是A表的条数,ON后面的条件只起到过滤B表的记录数,而
SELECT A.id,A.name,B.id,B.name FROM A ,B WHERE A.id = B.id
返回的条数,是笛卡尔积后,符合A.id = B.id这个条件的记录
D.使用JOIN时候,应该用小的结果驱动打的结果(left join 左边表结果尽量小,如果有条件应该放到左边先处理,right join同理反向),
尽量把牵涉到多表联合的查询拆分多个query(多个表查询效率低,容易锁表和阻塞)。如:
Select * from A left join B on a.id=B.ref_id where B.ref_id>10;
可以优化为:select * from (select * from A wehre id >10) T1 left join B onT1.id=B.ref_id;
14.把IP地址存成UNSIGNED INT
很多程序员都会创建一个VARCHAR(15)
字段来存放字符串形式的IP而不是整形的IP。如果你用整形来存放,只需要4个字节,并且你可以有定长的字段。而且,这会为你带来查询上的优势,尤其是当你需要使用这样的WHERE条件:IP
between ip1 and ip2。
我们必需要使用UNSIGNED INT,因为IP地址会使用整个32位的无符号整形。
而你的查询,你可以使用 INET_ATON()来把一个字符串IP转成一个整形,并使用INET_NTOA()把一个整形转成一个字符串IP
perz.co
15.固定长度的表会更快
如果表中的所有字段都是“固定长度”的,整个表会被认为是
“static” 或 “fixed-length”。 例如,表中没有如下类型的字段:
VARCHAR,TEXT,BLOB。
固定长度的表会提高性能,因为MySQL搜寻得会更快一些,因为这些固定的长度是很容易计算下一个数据的偏移量的,所以读取的自然也会很快。
而如果字段不是定长的,那么,每一次要找下一条的话,需要程序找到主键。并且,固定长度的表也更容易被缓存和重建。
不过,唯一的副作用是,固定长度的字段会浪费一些空间,因为定长的字段无论你用不用,他都是要分配那么多的空间
16.垂直分割
“垂直分割”是一种把数据库中的表按列变成几张表的方法,这样可以降低表的复杂度和字段的数目,从而达到优化的目的。
示例一:在Users表中有一个字段是家庭地址,这个字段是可选字段,相比起,而且你在数据库操作的时候除了个人信息外,你并不需要经常读取或是改写这个字段。放到另外一张表中,这样会让你的表有更好的性能
对于用户表来说,只有用户ID,用户名,口令,用户角色等会被经常使用。小一点的表总是会有好的性能。
示例二:你有一个叫“last_login”的字段,它会在每次用户登录时被更新。但是,每次更新时会导致该表的查询缓存被清空。所以,你可以把这个字段放到另一个表中,这样就不会影响你对用户ID,用户名,用户角色的不停地读取了,因为查询缓存会帮你增加很多性能。oin他们,不然的话,这样的性能会比不分割时还要差,而且,会是极数级
17.拆分大的DELETE或INSERT语句
如果你需要执行一个大的DELETE或INSERT查询,你需要非常小心,要避免一次性操作大量的数据。
因为这两个操作是会锁表的,表一锁住了,别的操作都进不来了。会阻塞服务器其他操作相应,导致应用停住。
所以,如果你有一个大的处理,你定你一定把其拆分,使用LIMIT条件是一个好的方法。下面是一个示例:

18.越小的列会越快
对于大多数的数据库引擎来说,硬盘操作可能是最重大的瓶颈。所以,把你的数据变得紧凑会对这种情况非常有帮助,因为这减少了对硬盘的访问。参看MySQL的文档Storage Requirements查看所有的数据类型。p程序员站
如果一个表只会有几列罢了(比如说字典表,配置表),那么,我们就没有理由使用INT来做主键,使用MEDIUMINT,SMALLINT或是更小的TINYINT会更经济一些。如果你不需要记录时间,使用DATE要比DATETIME好得多。
当然,你也需要留够足够的扩展空间,不然,你日后来干这个事,由于数据量大,一条alert语句会花费大量时间
19.选择正确的存储引擎
在MySQL中有两个存储引擎MyISAM和InnoDB,每个引擎都有利有弊。
MyISAM适合于一些需要大量查询的应用,但其对于有大量写操作并不是很好。甚至你只是需要update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都无法操作直到读操作完成。
另外,MyISAM对于 SELECT COUNT(*) 这类的计算是超快无比的。InnoDB的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM还慢。他是它支持“行锁” ,于是在写操作比较多的时候,会更优秀。并且,他还支持更多的高级应用,比如:事务。
20.limit千万级分页的时候优化。
A.在我们平时用limit,如:
Select * from A order by id limit 1,10;
这样在表数据很少的时候,看不出什么性能问题,倘若到达千万级,如:
Select * from A order by id limit10000000,10;
虽然都是只查询10记录,但是这个就性能就让人受不了了。所以为什么当表数据很大的时候,我们还继续用持久层框架如hibernate,ibatis就会有一些性能问题,除非持久层框架对这些大数据表做过优化。
B.在遇见上面的情况,我们可以用另外一种语句优化,如:
Select * from A where id>=(Select idfrom a limit 10000000,1) limit 10;
确实这样快了很多,不过前提是,id字段建立了索引。也许这个还不是最优的,其实还可以这样写:
Select * from A where id between 10000000and 10000010;
这样的效率更加高。
7.尽量少排序
A.排序操作会消耗较多的CPU资源,所以减少排序可以在缓存命中率高等
8.尽量少OR
A.当where子句中存在多个条件以“或”并存的时候,Mysql的优化器并没有很好的解决其执行计划优化问题,再加上mysql特有的sql与Storage分层架构方式,
造成了其性能比较地下,很多时候使用union all或者union(必要的时候)的方式代替“or”会得到更好的效果
9.尽量用union all 代替union
A.union和union all的差异主要是前者需要将两个(或者多个)结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加大量的cpu运算,加大资源消耗及延迟。所以当我们可以确认不可能出现重复结果集或者不在乎重复结果集的时候,尽量使用union all而不是union
10.避免类型转换
A.这里所说的“类型转换”是指where子句中出现column字段的类型和传入的参数类型不一致的时候发生的类型转换。人为的上通过转换函数进行转换,直接导致mysql无法使用索引。如果非要转型,应该在传入参数上进行转换
11.不要在列上进行运算
A. 如下面:select * fromusers where YEAR(adddate)<2007;将在每个行进行运算,这些导致索引失效进行全表扫描,因此我们可以改成:
Select * from users where adddate<’2007-01-01’;
12.尽量不要使用NOT IN和<>操作
A. NOT IN和<>操作都不会使用索引,而是将会进行全表扫描。NOT IN可以NOT EXISTS代替,id<>3则可以使用id>3 or id <3;如果NOT EXISTS是子查询,还可以尽量转化为外连接或者等值连接,要看具体sql的业务逻辑。
B.把NOT IN转化为LEFT JOIN如:
SELECT * FROM customerinfo WHERE CustomerIDNOT in (SELECT CustomerID FROM salesinfo );
优化:
SELECT * FROM customerinfo LEFT JOINsalesinfoON customerinfo.CustomerID=salesinfo. CustomerID WHEREsalesinfo.CustomerID IS NULL;
13.使用批量插入节省交互(最好是使用存储过程)
A. 尽量使用insert intousers(username,password) values(‘test1’,’pass1’), (‘test2’,’pass2’), (‘test3’,’pass3’);
15.对多表关联的查询,建立视图
A.对多表的关联可能会有性能上的问题,我们可以对多表建立视图,这样操作简单话,增加数据安全性,通过视图,用户只能查询和修改指定的数据。且提高表的逻辑独立性,视图可以屏蔽原有表结构变化带来的影响

浙公网安备 33010602011771号