一、字段
1,尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNED;
2,VARCHAR的长度只分配真正需要的空间;
3,使用枚举或整数代替字符串类型;
4,尽量使用TIMESTAMP而非DATETIME;
5,单表不要有太多字段,建议在 20 以内;
6,避免使用 NULL 字段,很难查询优化且占用额外索引空间;
7,用整型来存 IP。
8,如果其他数据需要经常需要查询,而 blob/text 不需要,则将 blob/text 数据域其他数据分离。
9,压缩 text 和 blob 数据类型 — 为了节省空间,减少从磁盘读数据。
10,主键最好使用自增型,保证数据连续性(mysql innodb 主键默认采用b+tree,索引和数据放在同一个btree中),不要使用uuid、hash、md5等
二、索引
1,索引并不是越多越好,要根据查询有针对性的创建,考虑在WHERE和ORDER BY命令上涉及的列建立索引,可根据EXPLAIN来查看是否用了索引还是全表扫描;
2,应尽量避免在WHERE子句中对字段进行NULL值判断,否则将导致引擎放弃使用索引而进行全表扫描;
3,值分布很稀少数据重复即区分度很低的字段不适合建索引,例如 "性别",真假值 这种只有两三个值的字段。区分度的公式是count(distinct col)/count(*)。
4,字符字段只建前缀索引;
5,字符字段最好不要做主键;
6,不用外键,由程序保证约束;
7,尽量不用UNIQUE,由程序保证约束;
8,使用多列索引时主意顺序和查询条件保持一致,同时删除不必要的单列索引。
9,频繁更新的字段不适合建立索引,更多的索引意味更多的维护成本
10,where条件中用不到的字段不适合建立索引,都用不到建立索引没有意义还浪费空间
11,表数据可以确定比较少的不需要建索引
12,参与列计算的列不适合建索引
13,大字段(blob)不要建立索引,查询也不会走索引。
14,当时间存储为时间戳保存的可以建立前缀索引。
15,当一个表中有100万数据,而经常用到的数据只有40万或40万以下,是不用考虑建立索引的,没什么性能提升。过小的表,建索引可能会更慢哦 :(读个2页的宣传手册,你还先去找目录?)
16,在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。创建复合索引,需要注意把区分度最大的放到最前面。如果第一个字段出现 范围符号的查找,那么将不会用到索引,如果我是第二个或者第三个字段使用范围符号的查找,那么他会利用索引。
17,负向条件查询不能使用索引,可以优化为 in 查询。负向条件有:!=、<>、not in、not exists、not like 等。
18,联合索引最左前缀原则(又叫最左侧查询),如果在(a,b,c)三个字段上建立联合索引,那么它能够加快 a | (a,b) | (a,b,c) 三组查询速度。
如果建立了(a,b)联合索引,就不必再单独建立 a 索引。同理,如果建立了(a,b,c)联合索引,就不必再单独建立 a、(a,b) 索引。
19,范围列可以用到索引(联合索引必须是最左前缀)。范围条件有:<、<=、>、>=、between等。范围列可以用到索引(联合索引必须是最左前缀),但是范围列后面的列无法用到索引,索引最多用于一个范围列,如果查询条件中有两个范围列则无法全用到索引。
假如有联合索引 (empno、title、fromdate),那么下面的 SQL 中 emp_no 可以用到索引,而 title 和 from_date 则使用不到索引。
select * fromemployees.titles where emp_no < 10010' and title='Senior Engineer'and from_date between '1986-01-01' and '1986-12-31'
20,如果有 order by、group by 的场景,请注意利用索引的有序性。
order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现file_sort的情况,影响查询性能。
例如对于语句 where a=? and b=? order by c,可以建立联合索引(a,b,c)。
如果索引中有范围查找,那么索引有序性无法利用,如 WHERE a>10 ORDER BY b;,索引(a,b)无法排序。
21,单表索引建议控制在5个以内,单索引字段数不允许超过5个。字段超过5个时,实际已经起不到有效过滤数据的作用了。
22,SQL 性能优化 explain 中的 type:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。
consts:单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
ref:使用普通的索引(Normal Index)。
range:对索引进行范围检索。
当 type=index 时,索引物理文件全扫,速度非常慢。
23,业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的。
另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。
24,建立索引的列,不允许为 null。单列索引不存 null 值,复合索引不存全为 null 的值,如果列允许为 null,可能会得到“不符合预期”的结果集,所以,请使用 not null 约束以及默认值。
只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL。
25,超过三个表最好不要 join。需要 join 的字段,数据类型必须一致,多表关联查询时,保证被关联的字段需要有索引。
26,保证索引简单,不要在同一列上加多个索引。
27,有时,增加列时,先删除索引,之后在加上索引会更快。
28,不得使用外键与级联,一切外键概念必须在应用层解决
29,如何使用索引来排序?
规则:1)ORDER BY子句后的列顺序要与组合索引的列顺序一致,且所有排序列的排序方向(正序/倒序)需一致
2)所查询的字段值需要包含在索引列中,及满足覆盖索引
通过例子分析:
在user_test表上创建一个组合索引
ALTER TABLE user_test ADD INDEX index_user(user_name , city , age);
可以使用到索引排序的案例sql
1、SELECT user_name, city, age FROM user_test ORDER BY user_name;
2、SELECT user_name, city, age FROM user_test ORDER BY user_name, city;
3、SELECT user_name, city, age FROM user_test ORDER BY user_name DESC, city DESC;
4、SELECT user_name, city, age FROM user_test WHERE user_name = 'feinik' ORDER BY city;
注:第4点比较特殊一点,如果where查询条件为索引列的第一列,且为常量条件,那么也可以使用到索引
无法使用索引排序的案例
1)sex不在索引列中
SELECT user_name, city, age FROM user_test ORDER BY user_name, sex;
2)排序列的方向不一致
SELECT user_name, city, age FROM user_test ORDER BY user_name ASC, city DESC;
3)所要查询的字段列sex没有包含在索引列中
SELECT user_name, city, age, sex FROM user_test ORDER BY user_name;
4)where查询条件后的user_name为范围查询,所以无法使用到索引的其他列
SELECT user_name, city, age FROM user_test WHERE user_name LIKE 'feinik%' ORDER BY city;
5、多表连接查询时,只有当ORDER BY后的排序字段都是第一个表中的索引列(需要满足以上索引排序的两个规则)时,方可使用索引排序。如:再创建一个用户的扩展表user_test_ext,并建立uid的索引。
DROP TABLE IF EXISTS user_test_ext;
CREATE TABLE user_test_ext(
id int AUTO_INCREMENT PRIMARY KEY,
uid int NOT NULL,
u_password VARCHAR(64) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE user_test_ext ADD INDEX index_user_ext(uid);
走索引排序
SELECT user_name, city, age FROM user_test u LEFT JOIN user_test_ext ue ON u.id = ue.uid ORDER BY u.user_name;
不走索引排序
SELECT user_name, city, age FROM user_test u LEFT JOIN user_test_ext ue ON u.id = ue.uid ORDER BY ue.uid;
30,总结一条:
选择唯一性索引;
为经常需要排序、分组和联合操作的字段建立索引;
为常作为查询条件的字段建立索引;
限制索引的数目;
尽量使用值小的索引;
尽量使用前缀来索引,如果索引字段的值很长,最好使用值的前缀来索引。例如,TEXT和BLOG类型的字段,进行全文检索会很浪费时间。如果只检索字段的前面的若干个字符,这样可以提高检索速度。
删除不再使用或者很少使用的索引;
最左前缀匹配原则,非常重要的原则;
索引列不能参与计算,保持列“干净”。
尽量的扩展索引,不要新建索引。 比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可
三、查询
1,可通过开启慢查询日志来找出较慢的 SQL;
2,不做列运算:SELECT id WHERE age + 1 = 10,任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边;
3,SQL 语句尽可能简单:一条 SQL 只能在一个 CPU 运算;大语句拆小语句,减少锁时间;一条大 SQL 可以堵死整个库;
4,不用SELECT *;
5,OR改写成IN:OR的效率是 n 级别,IN的效率是 log(n) 级别,in 的个数建议控制在 200 以内;
6,不用函数和触发器,在应用程序实现;
7,避免%xxx式查询;
8,少用JOIN;
9,使用同类型进行比较,比如用'123'和'123'比,123和123比;
10,尽量避免在WHERE子句中使用!= 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描;
11,对于连续数值,使用BETWEEN不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5;
12,列表数据不要拿全表,要使用LIMIT来分页,每页数量也不要太大。
13,使用count统计数据量的时候建议使用count(*)而不是count(列),因为count(*)MySQL是做了优化的。
14,什么时候开MySQL的查询缓存? 交易系统(写多、读少)和SQL优化测试,建议关闭查询缓存;论坛文章类系统(写少、读多),建议开启查询缓存。
15,复杂SQL语句优化的思路:首先考虑在一个表中能不能取到有关的信息,尽量少关联表,关联条件争取都走主键或外键查询条件,能走到对应的索引,https://gw.alicdn.com/tps/TB10M0wLpXXXXaaaXXXXXXXXXXX-109-109.png
争取在满足业务上走小集合数据查找,INNER JOIN 和子查询哪个更快,场景不一致速度也不同
16,where条件多条件一定要按照小结果集排大结果集前面
17,尽量避免大事务操作,提高系统并发能力,有时无法避免,改用定时器延迟处理。
18,使用UNION ALL 替换OR多条件查询并集,尽量用 union all 代替 union。
19,在大数据表删除也是一个问题,避免删除过程数据库奔溃,可以考虑分配删除,一次删1000条,删完后等一会继续删除
20,如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
21,尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
22,尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
23,很多时候用 exists 代替 in 是一个好的选择: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)
24, 二次SQL查询区别不大的时候,不能按照二次执行的时间来判断优化结果,没准第一次查询后又保存缓存数据,导致第二次查询速度比第二次快,很多时候我们看到的都是假象。
25,在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送DONE_IN_PROC 消息。
26,页面搜索严禁左模糊或者全模糊,如果需要可以用搜索引擎来解决。
27,如果明确知道只有一条结果返回,limit 1 能够提高效率。
28,使用 INSERT ON DUPLICATE KEY 或 INSERT IGNORE 来代替 UPDATE,避免 UPDATE 前需要先 SELECT。
29,如果合适,用 GROUP BY 代替 DISTINCT。
30,利用延迟关联或者子查询优化超多分页场景。
MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。
31,使用 ISNULL()来判断是否为 NULL 值,注意,NULL与任何值的直接比较都为 NULL
32,禁止单条SQL语句同时更新多个表。
33,减少与数据库交互次数,尽量采用批量SQL语句。
34,MySQL查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。
那么,如果在firstname、lastname、age这三个列上分别创建单列索引,效果是否和创建一个firstname、lastname、 age的多列索引一样呢?答案是否定的,两者完全不同。当我们执行查询的时候,MySQL只能使用一个索引。如果你有三个单列的索引,MySQL会试图选择一个限制最严格的索引。但是,即使是限制最严格的单列索引,它的限制能力也肯定远远低于firstname、lastname、age这三个列上的多列索引。
35,应尽量避免在where子句中对字段进行null值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
低效:select * from t_credit_detail where Flistid is null ;
可以在Flistid上设置默认值0,确保表中Flistid列没有null值,然后这样查询:
高效:select * from t_credit_detail where Flistid =0;
36,能用DISTINCT的就不用GROUP BY
37,提高GROUP BY 语句的效率, 可以通过将不需要的记录在GROUP BY 之前过滤掉。
低效: SELECT JOB , AVG(SAL) FROM EMP GROUP by JOB HAVING JOB = ‘PRESIDENT' OR JOB = ‘MANAGER'
高效: SELECT JOB , AVG(SAL) FROM EMP WHERE JOB = ‘PRESIDENT' OR JOB = ‘MANAGER’ GROUP by JOB
38,深度分页时,速度会很慢。可以利用最大最小ID临界值查询。
39,离散度更高的索引应该放在联合索引的前面,因为离散度高索引的可选择性高。考虑一种极端的情况,数据表中有100条记录,若INDEX(a,b)中a只有两种情况,而b有100种情况。这样对于查询唯一记录a = …,b = …时,先遍历全部索引看满足a条件的有50个索引节点,接下来还要再一个个遍历这50个索引节点。如果是INDEX(b,a),先遍历全部索引发现满足b条件的索引节点只有一个,再遍历这个节点发现也满足a条件。
40,覆盖索引优化。
当发起一个被索引覆盖的查询时,在explain的Extra列可以看到 Using index的标识。
1) 不是所有类型的索引都可以成为覆盖索引。覆盖索引必须要存储索引的列,而哈希索引、空间索引和全文索引等都不存储索引列的值,所以MySQL只能使用B-Tree索引做覆盖索引。
ALERT TABLE t1 ADD KEY(staff_id); // 添加索引
select sql_no_cache count(staff_id) from t1
无where条件的查询,可以通过索引来实现索引覆盖查询,但前提条件是,查询返回的字段数足够少,更不用说select *之类的了。
2)
explain select sql_no_cache rental_date from t1 where inventory_id<80000\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t1
type: range
possible_keys: inventory_id
key: inventory_id
key_len: 3
ref: NULL
rows: 153734
Extra: Using index condition
row in set (0.00 sec)
Extra:Using index condition 表示使用的索引方式为二级检索,即79999个书签值被用来进行回表查询。可想而知,还是会有一定的性能消耗的。
尝试针对这个SQL建立联合索引,如下:
alter table t1 add key(inventory_id,rental_date);
执行计划:
explain select sql_no_cache rental_date from t1 where inventory_id<80000\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t1
type: range
possible_keys: inventory_id,inventory_id_2
key: inventory_id_2
key_len: 3
ref: NULL
rows: 162884
Extra: Using index
row in set (0.00 sec)
Extra:Using index 表示没有会标查询的过程,实现了索引覆盖
3) 分页查询优化
select tid,return_date from t1 order by inventory_id limit 50000,10;
使用的是全表扫描。加上而外的排序,性能消耗是不低的。
如何通过覆盖索引优化呢?
我们创建一个索引,包含排序列以及返回列,由于tid是主键字段,因此,下面的复合索引就包含了tid的字段值
alter table t1 add index liu(inventory_id,return_date);
使用到了复合索引,并且不需要回表。
回想一下,如果查询只需要扫描索引而无须回表,将带来诸多好处。
(1)索引条目通常远小于数据行大小,如果只读取索引,MySQL就会极大地减少数据访问量。
(2)索引按照列值顺序存储,对于I/O密集的范围查询会比随机从磁盘中读取每一行数据的I/O要少很多。
(3)InnoDB的辅助索引(亦称二级索引)在叶子节点中保存了行的主键值,如果二级索引能够覆盖查询,则可不必对主键索引进行二次查询了。
覆盖索引就是从索引中直接获取查询结果,要使用覆盖索引需要注意select查询列中包含在索引列中;where条件包含索引列或者复合索引的前导列;查询结果的字段长度尽可能少。
41,查询字段和连接字段最好都加上索引!
数据库多表联查时,左连接Left join保证右表字段索引,右连接Right join保证左表字段索引,内连接inner join保证任一表连接字段索引。