温馨提示:PC端浏览体验感最佳~~
-
SQL优化
-
插入数据
-
insert优化
-
批量插入
sqlInsert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry'); -
手动提交事务
sqlstart transaction; insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry'); insert into tb_test values(4,'Tom').(5,'Cat'),(6,'Jerry'); insert into tb_test values(7,'Tom'),(8,'Cat'),(9,'Jerry'); commit; -
主键顺序插入
- 主键乱序插入:8、1、9、21、88、2、4、15、89、5、7、3
- 主键顺序插入:1、2、3、4、5、7、8、9、15、21、88、89
-
-
大批量插入数据
-
如果一次性需要插入大批量数据,使用insert语句插入性能较低,此时可以使用MySQL数据库提供的load指令进行插入。操作如下:
sql#客户端连接服务端时,加上参数 --local-infile mysql --local-infile -u root -p #设置全局参数local_infile为1,开启从本地加载文件导入数据的开关 set global local_infile=1; #执行load指令将准备好的数据,加载到表结构中 load data local infile '/root/sql1.log' into table 表名 fields terminated by ',' lines terminated by '\n';-

-
注意:主键顺序插入性能高于乱序插入。
-
-
-
-
主键优化
-
数据组织方式
- 在InnoDB存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(index organized table IOT)。
- 在InnoDB存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(index organized table IOT)。
-
页分裂
- 页可以为空,也可以填充一半,也可以填充100%。每个页包含了2-N行数据(如果一行数据多大,会行溢出),根据主键排列。
- 主键顺序插入的情况
- 主键乱序插入的情况
- 假如当前数据页的情况如下图:
- 现在我们要插入一条id为50的新数据(当前存在的两个Page都无法再存放下这条新数据),那么就会发生页分裂,页分裂后的结果如下图所示:
- 页分裂过程说明:由于叶子节点是有序的,所以id为50的这条新数据应该插入在47这条数据之后,但是47这条数据所在的Page已经没有足够的空间存放50这条数据,此时就会开启一个新的数据页,然后找到47这条数据所在Page的50%的位置,将超出50%位置的数据移动至新的数据页,并将50这条数据也插入到这个新的数据页,最后重新设置链表指针。
- 假如当前数据页的情况如下图:
- 主键顺序插入的情况
- 页可以为空,也可以填充一半,也可以填充100%。每个页包含了2-N行数据(如果一行数据多大,会行溢出),根据主键排列。
-
页合并
- 当删除一行记录时,实际上记录并没有被物理删除,只是记录被标记(flaged)为删除并且它的空间变得允许被其他记录声明使用。
- 当页中删除的记录达到MERGE_THRESHOLD(默认为页的50%),InnoDB会开始寻找最靠近的页(前或后)看看是否可以将两个页合并以优化空间使用。
- 假设当前数据页的情况如下图(其中id为13、14、15、16的这4条数据都被标记为删除,且这4条数据占用数据页的空间大于等于50%):
- 此时InnoDB发现第二个数据页及其相邻的第三个数据页的剩余空间都大于等于50%(MERGE_THRESHOLD),就会将第三个数据页的数据都迁移至第二个数据页,这个过程就叫做页合并。页合并之后数据页的情况如下图:
- 若现在要新插入一条id为20的新数据,就会直接将数据存放在第三个数据页,如下图:
- MERGE_THRESHOLD:合并页的阈值,默认为页的50%,可以自己设置,在创建表或者创建索引时指定。
- 假设当前数据页的情况如下图(其中id为13、14、15、16的这4条数据都被标记为删除,且这4条数据占用数据页的空间大于等于50%):
-
主键设计原则
- 满足业务需求的情况下,尽量降低主键的长度。
- 说明:二级索引的叶子节点存放主键,且二级索引可以有多个,主键长度越长占用的磁盘空间越大,搜索时也会耗费大量的磁盘IO。
- 插入数据时,尽量选择顺序插入,选择使用AUTO_INCREMENT自增主键。
- 说明:主键乱序插入容易出现页分裂现象。
- 尽量不要使用UUID做主键或者是其他自然主键,如身份证号。
- 说明:UUID、身份证号等自然主键无序且长度较长。
- 业务操作时,避免对主键的修改。
- 说明:修改主键需要同时修改索引结构,代价较大。
- 满足业务需求的情况下,尽量降低主键的长度。
-
-
order by优化(看Explain执行计划中的Extra列)
-
Using filesort:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sort buffer中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫FileSort排序。
-
Using index:通过有序索引顺序扫描直接返回有序数据,这种情况即为using index,不需要额外排序,操作效率高。
-
可以通过类似以下SQL语句进行验证:
sql#没有创建索引时,根据age,phone进行排序(Extra列的信息为Using filesort) explain select id,age,phone from tb_user order by age, phone; #创建索引 create index idx_user_age_phone_aa on tb_user(age,phone); #创建索引后,根据age,phone进行升序排序(Extra列的信息为Using index) explain select id,age,phone from tb_user order by age, phone; #创建索引后,根据age,phone进行降序排序(倒序扫描索引,Extra列的信息为Backward index scan; Using index) explain select id,age,phone from tb_user order by age desc, phone desc; #根据age,phone进行排序,但一个升序,一个降序(Extra列的信息为Using index; Using filesort) explain select id,age,phone from tb_user order by age asc,phone desc; #创建索引 create index idx_user_age_phone_ad on tb_user(age asc,phone desc); #根据age,phone进行排序,但一个升序,一个降序(走idx_user_age_phone_ad索引,Extra列的信息为Using index) explain select id,age,phone from tb_user order by age asc,phone desc;- 注意:以上排序规则都有一个前提条件,使用覆盖索引。若不使用覆盖索引,则需要回表查询数据,然后在数据缓冲区中对数据进行排序。
-
优化总结
- 根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则。
- 尽量使用覆盖索引。
- 多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC / DESC)。
- 如果不可避免的出现filesort,大数据量排序时,可以适当增大排序缓冲区大小sort_buffer_size(默认256k)。
- 说明:可以通过 show variables like 'sort_buffer_size'; 查看排序缓冲区的大小。若排序时排序缓冲区占满了,就会在磁盘文件中进行排序,性能低。
-
-
group by优化
-
执行类似以下SQL语句并观察Extra列的信息:
sql#删除掉目前不用的索引,避免影响 #执行分组操作,根据profession字段分组(Extra列出现了Using temporary,性能较差) explain select profession, count(*) from tb_user group by profession; #创建索引 Create index idx_user_pro_age_sta on tb_user(profession,age,status); #执行分组操作,根据profession字段分组(Extra列没有出现Using temporary,性能较好) explain select profession, count(*) from tb_user group by profession; #执行分组操作,根据profession字段分组(符合最左前缀法则,Extra列没有出现Using temporary,性能较好) explain select profession, count(*) from tb_user group by profession,age; #执行分组操作,先根据profession字段过滤,再根据age字段分组(符合最左前缀法则,Extra列没有出现Using temporary,性能较好) explain select age, count(*) from tb_user where profession='软件工程' group by age; -
优化总结
- 在分组操作时,可以通过索引来提高效率。
- 分组操作时,索引的使用也是满足最左前缀法则的。
-
-
limit优化
-
一个常见又非常头疼的问题就是limit 2000000,10,此时需要MySQL排序前2000010记录,仅仅返回2000000-2000010的记录,其他记录丢弃,查询排序的代价非常大。
-
优化思路:一般分页查询时,通过创建覆盖索引能够比较好地提高性能,可以通过覆盖索引加子查询形式进行优化。例如:
sqlexplain select * from tb_sku t, (select id from tb_sku order by id limit 2000000,10) a where t.id=a.id;
-
-
count优化
sqlexplain select count(*) from tb_user;-
count操作的效率主要取决于存储引擎的处理方式,对于上面的这条SQL语句:
- MylSAM引擎把一个表的总行数存在了磁盘上,因此执行count(*)的时候会直接返回这个数,效率很高;(前提是没有查询条件)
- InnoDB引擎就麻烦了,它执行count(*)的时候,即使没有查询条件,也需要把数据一行一行地从引擎里面读出来,然后累积计数。
-
优化思路:自己计数。(了解即可,实现起来比较麻烦)
-
count的几种用法
- count() 是一个聚合函数,对于返回的结果集,一行行地判断,如果count函数的参数不是NULL,累计值就加1,否则不加,最后返回累计值。
- 用法:count(*)、count(主键)、count(字段)、count(1)
- count(主键)
- InnoDB引擎会遍历整张表,把每一行的主键id值都取出来,返回给服务层。服务层拿到主键后,直接按行进行累加(主键不可能为null)。
- count(字段)
- 没有not null约束:InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为null,不为null,计数累加。
- 有not null约束:InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,直接按行进行累加。
- count(1)
- InnoDB引擎遍历整张表,但不取值。服务层对于返回的每一行,放一个数字"1"进去,直接按行进行累加。
- count(*)
- InnoDB引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加。
- 总结:按照效率排序的话,count(字段) < count(主键id) < count(1) ≈ count(*),所以尽量使用count( * )。(效率主要看是否需要取值)
-
-
update优化
-
InnoDB的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则会从行锁升级为表锁(我们在跟新数据时,最好根据索引字段去更新,且索引不能失效,这样InnoDB只会对符合条件的数据加上行锁,而不会对整张表加锁,并发访问性能较好)。例如以下SQL语句:
sql#假设id为主键(主键索引),name字段没有索引 #id字段有索引,此时只对id为1的这一行数据加锁。并发情况下,即使当前事务还没有提交,其他事务也可以操作其他行的数据 update student set no='2000100100' where id=1; #name字段没有索引,此时会对整张表上锁。并发情况下,在当前事务提交之前,其他事务都不可以对这张表的任何数据进行操作 update student set no='2000100105' where name='韦一笑'; #对name字段建立索引 create index idx_student_name on student(name); #此时name字段有索引,只会对name为"韦一笑"的行数据加锁。并发情况下,即使当前事务还没有提交,其他事务也可以操作其他行的数据 update student set no='2000100105' where name='韦一笑';
-
-






