MySQL索引及SQL优化
先对索引做个大概回顾,然后我们详细探讨SQL优化
索引
索引的分类
主键索引
设定为主键后数据库会自动建立索引,innodb为聚簇索引
单值索引
即一个索引只包含单个列,一个表可以有多个单列索引【建议不要超过3】
唯一索引
索引列的值必须唯一,但允许有空值
复合索引
又称之为 组合索引、联合索引即一个索引包含多个列
最左原则(此原则只针对:复合索引)
(工号、名称、入职日期) 作为一个组合索引,将会生成的索引目录结构。由接口可以看出,工号是最先需要判断的字段,所以工号这个查询条件必须存在工号判断完,才会判断名称名称判断完才会判断入职日期
也就是说,组合索引查询条件必须得带有最左边的列:对于我们的索引:
- 条件为: (工号,名称,入职日期) 这几种情况都是生效的
- 条件为: (名称)不生效 (名称,入职日期)不生效
- 条件为 (工号) (工号,名称)(工号,入职日期)部分生效
- 条件为 (工号,名称,入职日期)全部生效
索引优缺点
索引的优点
- 建立索引的列可以保证行的唯一性,生成唯一的rowId
- 建立索引可以有效缩短数据的检索时间==【合理的建立索引】合理:user_code 不合理:user_sex ,全部字段都添加索引,数据量过小==
- 建立索引可以加快表与表之间的连接查询 select * from oeder o left join user u on u.id = o.user_id where o.id="1010011"
- 为用来排序或者是分组的字段添加索引可以加快分组和排序速度
索引的缺点
- 创建索引和维护索引需要时间成本,这个成本随着数据量的增加而加大
- 创建索引和维护索引需要空间成本,每一条索引都要占据数据库的物理存储空间,数据量越大,占用空间也越大(数据表占据的是数据库的数据空间)
- 会降低表的增删改的效率,因为每次增删改索引需要进行动态维护,导致时间变长
索引的选择
那些情况下需要创建索引
- 主键自动建立唯一索引,【自增、UUID、雪花算法】
- 频繁作为查询条件且内容差异化大的字段应该创建索引【where 后面的语句】
- 查询中与其它表关联的字段,外键关系建立索引【on条件的字段】
- 单键/组合索引的选择问题,who?【在高并发下倾向创建组合索引】
- 查询中分组、排序的字段,排序字段若通过索引去访问将大大提高排序速度【order by 字段 group by 字段】
- 查询中统计字段 sum(字段)
哪些情况不要创建索引
- 记录比较少,本身数据就少即使加上索引也不会有太大的提升,增删改时还需要对索引维护进行维护,反而增加了工作量
- where条件里用不到的字段不建立索引,选择索引的时候不是越多越好【单表少于3个索引】
- 经常增删改的表,索引提高了查询的速度,同时却会降低更新表的速度,因为建立索引后, 如果对表进行INSERT,UPDATE 和DELETE, MYSQL不仅要保存数据,还要保存一下索引文件
- 数据重复的表字段如果某个数据列包含了许多重复的内容,为它建立索引 就没有太大在的实际效果,比如表中的某一个字段为国籍,性别,数据的差异率不高,这种建立索引就没有太多意义。
SQL优化
为什么要SQL优化
在应用开发的过程中,由于前期数据量少,开发人员编写的SQL语句或者数据库整体解决方案都更重视在功能上的实现, 但是当应用系统正式上线后,随着生成数据量的急剧增长,很多SQL语句和数据库整体方案开始逐渐显露出了性 能问题,对生成的影响也越来越大,此时Mysql数据库的性能问题成为系统应用的瓶颈,因此需要进行Mysql数据库的性能优化。
性能下降的表现:
- 慢查询造成页面无法加载
- 阻塞造成数据无法提交
性能下降的原因:
- 查询语句写的不好,各种连接,各种子查询导致用不上索引或者没有建立索引,胡乱建立索引
-
- 【1、不常用字段 2、内容差别不大的数据字 3、单表索引字段超过3个】
- 建立的索引失效,建立了索引,在真正执行时,没有用上建立的索引
-
select * from order select id,name.age from order
- 关联查询太多join
select id form ordero left join user u on o.user_id = u.id left join user_Info ui on ui.user_id = u.id where u.age >18 and ui.school ='人民大学'
- 服务器调优及配置参数导致,如果设置的不合理,比例不恰当,也会导致性能下降,sql变慢
- 系统架构的问题
通用数据库优化
SQL及索引优化
- 根据需求写出良好的SQL,并创建有效的索引,实现某一种需求可以多种写法,我们就要选择一种效率最高的写法,这个时候就要了解sql优化。【explain】
- sql优化的目的之一就是==减少中间结果集==,降低物理IO
数据库表结构优化
- 根据数据库的范式,设计表结构,表结构设计的好坏直接关系到SQL语句的复杂度。order ==>userName ==>userId==>user ===>userName
- 适当的将表进行拆分,原本需要join的查询只需要一张单表查询就可以了。
- 合理的分库、分表
系统配置优化
内核优化:linux 基础之-高并发内核优化_王道长的编程之路的博客-CSDN博客
- 大多数运行在Linux机器上,如tcp连接数的限制、打开文件数的限制、安全性的限制,因此我们要对这些配置进行相应的优化
硬件优化
- 数据库主机的IO性能是需要最优先考虑的一个因素
- 数据库主机和普通的应用程序服务器相比,资源要相对集中很多,单台主机上所需要进行的计算量自然也就比较多,所以数据库主机的CPU处理能力也是一个重要的因素。
- 数据库主机网络设备(一般指网卡等)的性能也可能会成为系统的瓶颈。
插入数据
如果我们需要一次性往数据库表中插入多条记录,可以从以下三个方面进行优化。
insert
优化方案一
批量插入数据
Insert into tb_test values(1,'Tom'),(2,'Cat'),(3,'Jerry');
优化方案二
手动控制事务
start 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;
优化方案三
主键顺序插入,性能要高于乱序插入。
大批量插入数据
如果一次性需要插入大批量数据(比如: 几百万的记录),使用insert语句插入性能较低,此时可以使 用MySQL数据库提供的load指令进行插入。操作如下:
可以执行如下指令,将数据脚本文件中的数据加载到表结构中:
-- 客户端连接服务端时,加上参数 -–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 tb_user fields terminated by ',' lines terminated by '\n' ;
主键顺序插入性能高于乱序插入
主键优化
为什么主键顺序插入的性能是要高于乱序插入的 ?
数据组织方式
在InnoDB存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表行数据,都是存储在聚集索引的叶子节点上的。而我们之前也讲解过InnoDB的逻辑结构图:
在InnoDB引擎中,数据行是记录在逻辑结构 page 页中的,而每一个页的大小是固定的,默认16K。 那也就意味着, 一个页中所存储的行也是有限的,如果插入的数据行row在该页存储不小,将会存储 到下一个页中,页与页之间会通过指针连接。
页分裂
页可以为空,也可以填充一半,也可以填充100%。每个页包含了2-N行数据(如果一行数据过大,会行溢出),根据主键排列。
主键乱序插入可能会产生页分裂!
页合并
当页中删除的记录达到 MERGE_THRESHOLD(默认为页的50%),InnoDB会开始寻找最靠近的页(前 或后)看看是否可以将两个页合并以优化空间使用。
order by优化
MySQL的排序,有两种方式:
Using filesort : 通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sort buffer中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。
Using index : 通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要 额外排序,操作效率高。
对于以上的两种排序方式,Using index的性能高,而Using filesort的性能低,我们在优化排序操作时,尽量要优化为 Using index。
group by优化
在分组操作中,我们需要通过以下两点进行优化,以提升性能:
A. 在分组操作时,可以通过索引来提高效率。
B. 分组操作时,索引的使用也是满足最左前缀法则的。
limit优化
分页查询时,如果执行 limit 2000000,10 ,此时需要MySQL排序前2000010 记 录,仅仅返回 2000000 - 2000010 的记录,其他记录丢弃,查询排序的代价非常大 。
优化思路: 一般分页查询时,通过创建覆盖索引能够比较好地提高性能,可以通过覆盖索引加子查询形式进行优化。
select * from tb_sku t , (select id from tb_sku order by id
limit 2000000,10) a where t.id = a.id;
count优化
- MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count(*) 的时候会直接返回这个 数,效率很高; 但是如果是带条件的count,MyISAM也慢。
- InnoDB 引擎就麻烦了,它执行 count(*) 的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。
如果说要大幅度提升InnoDB表的count效率,主要的优化思路:自己计数(可以借助于redis这样的数 据库进行,但是如果是带条件的count又比较麻烦了)。
count用法
count() 是一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是 NULL,累计值就加 1,否则不加,最后返回累计值。
用法:count(*)、count(主键)、count(字段)、count(数字)
按照效率排序的话,count(字段) < count(主键 id) < count(1) ≈ count(*),所以尽量使用 count(*)。
update优化
我们主要需要注意一下update语句执行时的注意事项。
update course set name = 'javaEE' where id = 1 ;
当我们在执行删除的SQL语句时,会锁定id为1这一行的数据,然后事务提交之后,行锁释放。
但是当我们在执行如下SQL时。
update course set name = 'SpringBoot' where name = 'PHP' ;
当我们开启多个事务,在执行上述的SQL时,我们发现行锁升级为了表锁。 导致该update语句的性能 大大降低。
InnoDB的行锁是针对索引加的锁,不是针对记录加的锁 ,并且该索引不能失效,否则会从行锁升级为表锁 。
总结
插入数据
insert:批量插入/手动控制事务/主键顺序插入
大批量插入:load data local infile
主键优化
主键长度尽量短/顺序插入
order by优化
using index:直接通过索引返回数据,性能高
using filesort:需要将返回的结果在排序缓冲区排序
group by优化
最好走索引,多字段分组满足最左前缀法则
limit优化
覆盖索引+子查询
count优化
性能:count(字段) < count(主键 id) < count(1) ≈ count(*)
uodate优化
尽量根据主键/索引字段进行数据更新