避免使用 SELECT *
-
SELECT * 会消耗更多的 CPU
-
SELECT * 无用字段增加网络带宽资源消耗,增加数据传输时间,尤其是大字段(如 varchar、blob、text)
-
SELECT * 无法使用 MySQL 优化器覆盖索引的优化
-
SELECT <字段列表> 可减少表结构变更带来的影响
分页优化
普通的分页在数据量小的时候耗费时间还是比较短的。
SELECT `score`,`name` FROM `cus_order` ORDER BY `score` DESC LIMIT 10000, 10;
如果数据量变大,达到百万甚至是千万级别,普通的分页耗费的时间就非常长
SELECT `score`,`name` FROM `cus_order` ORDER BY `score` DESC LIMIT 1000000, 10
SELECT `score`,`name` FROM `cus_order` ORDER BY `score` DESC LIMIT 10, 1000000
优化 可以将上述 SQL 语句修改为子查询。
SELECT `score`,`name` FROM `cus_order` WHERE id >= (SELECT id FROM `cus_order` LIMIT 1000000, 1) LIMIT 10
我们先查询出 limit 第一个参数对应的主键值,再根据这个主键值再去过滤并 limit,这样效率会更快。
不过,子查询的结果会产生一张新表,会影响性能,应该尽量避免大量使用子查询。
除了子查询之外,还以采用延迟查询的方式来优化。
SELECT `score`,`name` FROM `cus_order` a, (SELECT id from `cus_order` ORDER BY `score` DESC LIMIT 1000000, 10) b where a.id = b.id
我们先提取对应的主键,再将这个主键表与原数据表关联。
尽量避免多表做 join
join 的效率比较低,主要原因是因为其使用嵌套循环(Nested Loop)来实现关联查询,三种不同的实现效率都不是很高:
- Simple Nested-Loop Join :没有进过优化,直接使用笛卡尔积实现 join,逐行遍历/全表扫描,效率最低。
- Block Nested-Loop Join :利用 JOIN BUFFER 进行优化,性能受到 JOIN BUFFER 大小的影响,相比于 Simple Nested-Loop Join 性能有所提升。不过,如果两个表的数据过大的话,无论如何优化,Block Nested-Loop Join 对性能的提升都非常有限。
- Index Nested-Loop Join :在必要的字段上增加索引,使 join 的过程中可以使用到这个索引,这样可以让 Block Nested-Loop Join 转换为 Index Nested-Loop Join,性能得到进一步提升。
实际业务场景避免多表 join 常见的做法有两种:
-
单表查询后在内存中自己做关联 :对数据库做单表查询,再根据查询结果进行二次查询,以此类推,最后再进行关联。
-
数据冗余,把一些重要的数据在表中做冗余,尽可能地避免关联查询。很笨的一张做法,表结构比较稳定的情况下才会考虑这种做法。进行冗余设计之前,思考一下自己的表结构设计的是否有问题。
更加推荐第一种,这种在实际项目中的使用率比较高,除了性能不错之外,还有如下优势: -
拆分后的单表查询代码可复用性更高 :join 联表 SQL 基本不太可能被复用。
-
单表查询更利于后续的维护 :不论是后续修改表结构还是进行分库分表,单表查询维护起来都更容易。
选择合适的字段类型
存储字节越小,占用也就空间越小,性能也越好。
a.某些字符串可以转换成数字类型存储比如可以将 IP 地址转换成整形数据。
数字是连续的,性能更好,占用空间也更小。
MySQL 提供了两个方法来处理 ip 地址
- INET_ATON() : 把 ip 转为无符号整型 (4-8 位)
- INET_NTOA() :把整型的 ip 转为地址
插入数据前,先用 INET_ATON() 把 ip 地址转为整型,显示数据时,使用 INET_NTOA() 把整型的 ip 地址转为地址显示即可。
b.对于非负型的数据 (如自增 ID,整型 IP,年龄) 来说,要优先使用无符号整型来存储。
无符号相对于有符号可以多出一倍的存储空间
SIGNED INT -2147483648~2147483647
UNSIGNED INT 0~4294967295
c.小数值类型(比如年龄、状态表示如 0/1)优先使用 TINYINT 类型。
d.对于日期类型来说, DateTime 类型耗费空间更大且没有时区信息,建议使用 Timestamp。
e.金额字段用 decimal,避免精度丢失。
f.尽量使用自增 id 作为主键。
如果主键为自增 id 的话,每次都会将数据加在 B+树尾部(本质是双向链表),时间复杂度为 O(1)。在写满一个数据页的时候,直接申请另一个新数据页接着写就可以了。
如果主键是非自增 id 的话,为了让新加入数据后 B+树的叶子节点还能保持有序,它就需要往叶子结点的中间找,查找过程的时间复杂度是 O(lgn)。如果这个也被写满的话,就需要进行页分裂。页分裂操作需要加悲观锁,想能非常低。
不过, 像分库分表这类场景就不建议使用自增 id 作为主键,应该使用分布式 ID 比如 uuid 。
尽量用 UNION ALL 代替 UNION
UNION 会把两个结果集的所有数据放到临时表中后再进行去重操作,更耗时,更消耗 CPU 资源。
UNION ALL 不会再对结果集进行去重操作,获取到的数据包含重复的项。
不过,如果实际业务场景中不允许产生重复数据的话,还是可以使用 UNION。
批量操作
对于数据库中的数据更新,如果能使用批量操作就要尽量使用,减少请求数据库的次数,提高性能。
# 反例
INSERT INTO `cus_order` (`id`, `score`, `name`) VALUES (1, 426547, 'user1');
INSERT INTO `cus_order` (`id`, `score`, `name`) VALUES (1, 33, 'user2');
INSERT INTO `cus_order` (`id`, `score`, `name`) VALUES (1, 293854, 'user3');
# 正例
INSERT into `cus_order` (`id`, `score`, `name`) values(1, 426547, 'user1'),(1, 33, 'user2'),(1, 293854, 'user3');
被频繁更新的字段应该慎重建立索引
虽然索引能带来查询上的效率,但是维护索引的成本也是不小的。 如果一个字段不被经常查询,反而被经常修改,那么就更不应该在这种字段上建立索引了。
尽可能的考虑建立联合索引而不是单列索引
因为索引是需要占用磁盘空间的,可以简单理解为每个索引都对应着一颗 B+树。如果一个表的字段过多,索引过多,那么当这个表的数据达到一个体量后,索引占用的空间也是很多的,且修改索引时,耗费的时间也是较多的。如果是联合索引,多个字段在一个索引上,那么将会节约很大磁盘空间,且修改数据的操作效率也会提升。
注意避免冗余索引
冗余索引指的是索引的功能相同,能够命中索引(a, b)就肯定能命中索引(a) ,那么索引(a)就是冗余索引。如(name,city )和(name )这两个索引就是冗余索引,能够命中前者的查询肯定是能够命中后者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。