SQL的设计和优化是Java项目非常重要的一个环节,对项目的性能提升也是至关重要的。最近一段时间研究了一下mysql数据库,就mysql语句的优化做了一个简单的整理。mysql的语句优化分为三个方面:查询优化、索引优化、库表结构优化。

一、SQL查询优化

1、任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。

2、应尽量避免在 where 子句中使用!=或<>(不等于)操作符,无论有没有索引都会进行全表扫描。

3、不要使用not in 无论有没有索引会导致全表扫描(个人测试)

用not exists的效率会提高很多例如(图1)

in也要慎用,可能也会导致全表扫描(未验证),可以用exists 或者between进行代替例如:

select num from a where num in(select num from b)

可用下面的语句替换:

select num from a where exists(select 1 from b where num=a.num)

4、全词匹配的模糊查询也要慎用,左模糊查询会导致全表扫描(可以考虑全文检索)

explain select * from table_test where phone like '%157%'; -- 全表扫描

explain select * from table_test where phone like '157%'; --走索引,效率高

5、应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:

select * from table_test where score/10 =3

select * from table_test where score = 10*3

6、应尽量避免在where子句中对字段进行函数操作,这将会导致全表扫描如:

select id from t where substring(name,1,3)='abc'.

select id from t where datediff(day,createdate,'2005-11-30')=0

select id from t where name like 'abc%'

select id from t where createdate>='2005-11-30' and createdate<'2005-12-1'

7、在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。

8、若是复合索引则order by的字段的顺序应该和符合索引的顺序相同

9. 不要使用 count(列名)或 count(常量)来替代 count(*)

count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。 (见图2)

10、当某一列的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为 NULL,因此使用 sum()时需注意 NPE 问题。

正例:可以使用如下方式来避免 sum 的 NPE 问题:

SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM table;

11、使用 ISNULL()来判断是否为 NULL 值。

说明:NULL 与任何值的直接比较都为 NULL。

1) NULL<>NULL 的返回结果是 NULL,而不是 false。

2) NULL=NULL 的返回结果是 NULL,而不是 true。

3) NULL<>1 的返回结果是 NULL,而不是 true。

12、在代码中写分页查询逻辑时,若 count 为 0 应直接返回,避免执行后面的分页语句。

说明:在进行分页查询时,先查count的数据,若count的数据为0,则不再进行分页数据查询,提高效率

二、索引优化

1、索引列上不能使用表达式或函数

说明见(SQL查询优化第六条)

2、在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据 实际文本区分度决定索引长度即可。

说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度 来确定。

3、页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。

说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索 引。(见SQL查询优化第五条)

4、利用覆盖索引来进行查询操作,避免回表查询。

覆盖索引优点:

可以优化缓存、减少磁盘IO操作

可以减少随机IO,变随机IO操作变为顺序IO操作

可以避免对Innodb主键索引的二次查询

5、建组合索引的时候,区分度最高的在最左边。

正例:如果 where a=? and b=? ,a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即 可。

说明:存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如:where a>? and b=? 那么即使 a 的区分度更高,也必须把 b 放在索引的最前列。

6、where字句的顺序问题

图3 name字段没有索引,phone字段有索引,此时索引是起作用的,也就是说MySQL 的查询优化器会自动调整 where 子句的条件顺序以使用适合的索引

7、组合索引解决的查询问题

例如:如果在(a,b,c)三个字段上建立联合索引,那么它相当于创建了(a)单列索引,(a,b)组合索引,(a,b,c)组合索引以及(a,c)组合索引

8、组合索引的最左前缀原则

1、最左边的索引字段必须使用才会走组合索引

2、索引字段的顺序影响不大

如:where字句中先是第二个索引字段,再是第一个索引字段,也会走索引,见图4。

三、库表结构优化

1、尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。

说明:这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。

2、尽可能的使用 varchar/nvarchar 代替 char/nchar

因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。

3、表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint ( 1 表示是,0 表示否)。

说明:任何字段如果为非负数,必须是 unsigned。

正例:表达逻辑删除的字段名 is_deleted,1 表示删除,0 表示未删除。

4、表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只 出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。

说明:MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库 名、表名、字段名,都不允许出现任何大写字母,避免节外生枝。

5. 主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。

6. 小数类型为 decimal,禁止使用 float 和 double。

说明:float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不 正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。

8、表必备三字段:id, gmt_create, gmt_modified。

说明:其中 id 必为主键,类型为 unsigned bigint、单表时自增、步长为 1。gmt_create, gmt_modified 的类型均为 date_time 类型,前者现在时表示主动创建,后者过去分词表示被 动更新。

9. 合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。

正例:如下表,其中无符号值可以避免误存负数,且扩大了表示范围。

年龄区间

表示范围

150 岁之内

unsigned tinyint

无符号值:0 到 255

unsigned smallint

无符号值:0 到 65535

恐龙化石

数千万年

unsigned int

无符号值:0 到约 42.9 亿

unsigned bigint

无符号值:0 到约 10 的 19 次方

10、varchar的适用场景

字符串列的最大长度比平均长度大很多

字符串列很小被更新

使用了多字节字符集存储字符串

varchar的特点:

varchar用于存储变长字符串、只占用必要的存储空间

使用最小的符合需求的长度  varchar(5)和varchar(200)存储‘MySQL’字符串性能不同

11、char类型的适用场景

char类型适合存储长度近似的值(md5值,身份证、手机号)

char类型适合存储短字符串(性别、状态、标志)

char类型适合存储经常更新的字符串列(可避免存储碎片)

char类型的存储特点

char类型是定长的

字符串存储在char类型的列中会删除末尾的空格

char类型的最大宽度为255

参考:《阿里巴巴开发手册》

互联网各路资料及实践