MySQL数据库优化
一、优化SQL步骤(问题定位方法)
在应用程序开发过程中,由于初期数据量较少,开发人员写SQL语句更加注重功能上的实现,但是当应用系统正式上线之后,随着数据量急速增长,很多SQL语句开始逐渐显露出性能问题,对生产的影响也越来越大,此时这些有问题的SQL语句就成为整个系统性能的瓶颈,因此我们需要对它们进行优化。
当面对一个有SQL性能问题的数据库时,应该如何入手来进行系统的分析,使得能够尽快定位问题SQL并解决问题。
1、查看SQL执行频率
可以通过如下指令查看,在SQL数据库中,执行频率较高的是哪些操作(由此可以确定数据库是以查询为主,还是以插入为主),之后就可以进行有针对性的优化。
SHOW STATUS LIKE "Comm_______";
SHOW STATUS LIKE "Innodb_rows_%";
示例:
2、定位低效率执行SQL
可以通过以下两种方式定位执行效率较低的SQL语句。
- 慢查询日志:通过慢查询日志可以定位那些执行效率较低的SQL语句。日志中包含所有执行时间超过long_query_time秒的SQL语句的日志文件。
- show processlist:慢查询日志在查询结束之后才会进行记录,所以在应用的执行效率出现问题的时候,慢查询日志并不能立刻定位到问题,这时候可以使用show processlist命令查看当前数据库中正在进行的线程,包括线程的状态、是否锁表等,实时查看SQL的执行情况,同时对一些锁表操作进行优化。
3、explain分析执行计划
通过上述方法定位到效率较低的SQL语句之后,可以通过explain或者desc命令获取MySQL如何执行SELECT语句的信息,包括在SELECT语句执行过程中表如何连接和连接的顺序。
字段含义:
id:表示SQL语句执行过程中,表的读取顺序优先级,ID越大表示优先级越高。
select_type:表示查询过程中,SELECT的类型。如果单表查询就是SIMPLE。
table:表示的就是当前查询的数据来自哪张表。
type:表示的是访问类型,比如查询的时候用不用到索引。
possible_keys:表示可能应用在这张表的索引,一个或多个。
key:表示查询过程中实际使用的索引,如果为NULL,表示没有使用索引。
key_len:表示索引的长度(越短越好 )
rows:表示扫描的行数
extra:除前面展示的信息之外的一些额外信息。
4、show profile分析SQL
show profile可以帮助我们分析在查询过程中,各个阶段的耗时。
5、trace分析优化器执行计划
MySQL提供了对SQL的跟踪trace,通过trace文件能够进一步了解为什么优化器选择A计划,而不是选择B计划。
SQL Server中有一个优化器模块,是对我们客户端输入的SQL语句进行优化。
二、数据库优化
1、大批量插入数据
使用load命令通过本地文件导入数据的时候,如果导入的数据量极大(如百万级别的数据),有一些操作可以提高插入的效率。
(1)主键顺序插入
因为Innodb类型的表存储使用B+ Tree的数据结构,按照主键顺序进行保存,所以将导入的数据按照主键顺序进行排列,可以有效地提高导入数据的效率。如果Innodb表没有主键,那么系统会自动默认创建一个内部列作为主键,所以如果可以给表创建一个主键,将可以利用这点,来提高导入数据的效率。
(2)关闭唯一性校验
在导入数据前,执行SET UNIQUE_CHECK = 0,关闭唯一性校验,在导入结束后执行 SET UNIQUE_CHECK = 1,恢复唯一性校验,可以提高导入的效率。
(3)手动提交事务
如果应用使用自动提交的方式,建议在导入之前执行SET AUTOCOMMIT= 0,关闭自动提交,导入结束之后,再执行SET AUTOCOMMIT= 1,打开自动提交,也可以提高导入的效率。
2、优化INSERT语句
当进行数据的insert操作的时候,以下几种方式可以提高insert的效率:
(1)如果需要对一张表插入很多行数据,应该一次性进行插入,而不要写多条insert语句;这样将大大缩减客户端与数据库之前的连接、关闭等消耗。使得效率比分开执行的单个insert语句快。
示例,原始方式为:
insert into table values(1, "ma", 24);
insert into table values(2, "wang", 22);
insert into table values(3, "liu", 23);
优化之后应该写为:
insert into table values(1, "ma", 24), (2, "wang", 22), (3, "liu", 23);
(2)在事务中进行数据插入
关闭事务的自动提交,在事务中进行数据的插入。示例:
start transaction;
insert into table values(1, "ma", 24);
insert into table values(2, "wang", 22);
insert into table values(3, "liu", 23);
commit;
(3)数据有序插入
和上面介绍的load操作类似,在insert数据的时候,最好也按照数据的主键顺序进行insert。
3、优化Order By语句
MySQL数据库中Order By提供两种排序方式。
(1)第一种是通过对返回数据进行排序,也就是通常说的filesort排序,所有不是通过索引直接返回排序结果的排序都叫FilseSort排序。
(2)第二种通过有序索引顺序扫描直接返回有序数据,这种情况即为using index,不需要额外排序,操作效率高。
了解了MySQL的排序方式,优化目标就清晰了:尽量减少额外的排序,通过索引直接返回有序数据。where条件和Order By使用相同的索引,并且Order By的顺序和索引顺序相同,并且Order By的字段都是升序,或者都是降序。否则肯定需要额外的操作,这样就会出现FileSort。
4、优化Group By语句
由于Group By实际上也同样会进行排序操作,而且与Order By相比,Group By主要只是多了排序之后的分组操作。当然,如果分组的时候还使用了其他的一些聚合函数,那么还需要一些聚合函数的计算。所以,在Group By的实现过程中,与Order By一样也可以利用索引。
如果查询包含group by,但是用户想要避免排序结果的消耗,则可以执行order by null禁止排序。
示例如下:
select age, count(*) from emp group by age order by null;
对于group by语句来讲,最优的优化方式还是对需要进行分组的字段建立索引。
5、优化嵌套查询
嵌套查询指的是:查询语句中,包含子查询语句。
例如:select * from A where A.id in (select B.id from B);
上述的查询方式较慢;对于嵌套查询的优化方式为:使用多表联查来代替嵌套查询。
因此对于上面的查询语句,可以修改为如下内容:
select * from A, B where A.id = B.id;
6、优化OR条件
对于包含or的查询子句,如果要利用索引,则OR之间的每个条件列都必须用到索引,而且不能使用到复合索引(只能使用单列索引);如果没有索引,则应该考虑增加索引。
另外一个优化的方式是:使用UNION来替代OR。UNION就是并集的操作。
7、优化分页查询(优化limit语句)
一般分页查询时,通过创建覆盖索引能够比较好地提高性能。一个常见又非常头疼的问题就是 limit 2000000, 10 ,此时需要MySQL排序前2000010条记录,仅仅返回2000000 ——2000010的记录,其他记录丢弃,查询排序的代价非常大。
优化思路:
在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容。
三、数据库结构优化
- 将字段很多的表分解成多个表。因为字段很多的表中存在一些使用频率低的字段,这些字段的存在就会大大影响访问速度;
- 增加一些中间表。对于需要经常联合多个表进行查询时,可以建立一个中间表,将需要通过联合查询的数据插入到中间表,来提高效率;
- 读写分离。在数据库并发大的情况下,最好的做法就是进行横向扩展,增加机器,以提升抗并发能力,而且还兼有数据备份功能。
四、应用程序优化
1、应用优化
上面的内容中,介绍了数据库的优化措施。但是在实际生产环境中,由于数据库本身的性能局限,就必须要对前台的应用进行一些优化,来降低数据库的访问压力。
1.1 使用连接池
对于访问数据库来说,建立连接的代价是比较昂贵的,因为我们频繁的创建关闭连接,是比较耗费资源的,我们有必要建立数据连接池,以提高访问的性能。
1.2 减少对MySQL的访问
(1)避免对数据进行重复检索
(2)增加缓存(cache)层。缓存层的实现方式有多种,例如使用文本方式存储,或者使用框架(Mybatis, Hibernate)提供的一级缓存/二级缓存,或者使用redis数据库来缓存数据。
(3)负载均衡。他的机制就是利用某种均衡算法,将固定的负载量分布到不同的服务器上,以此来降低单台服务器的负载,达到优化的目的。
2、Mysql中查询缓存优化
开启Mysql的查询缓存,当执行完全相同的SQL语句的时候,服务器就会直接从缓存中读取结果,当数据被修改,之前的缓存会失效,修改比较频繁的表不适合做查询缓存。
查询缓存的操作流程如下:
在MySQL8.0中已经删除了查询缓存。
3、Mysql内存管理及优化
内存优化原则:
(1)将尽量多的内存分配给Mysql做缓存,但要给操作系统和其他程序预留足够内存。
(2)MyISAM存储引擎的数据文件读取依赖于操作系统自身的IO缓存,因此,如果有MyISAM表,就要预留更多的内存给操作系统做IO缓存。
(3)排序区、连接区等缓存是分配给每个数据库会话(session)专用的,其默认值的设置要根据最大连接数合理分配,如果设置太大,不但浪费资源,而且在并发连接较高时会导致物理内存耗尽。
参考资料:
1、https://www.bilibili.com/video/BV1UQ4y1P7Xr?p=46
2、https://hillzhang1999.gitee.io/2020/05/29/shu-ju-ku-fu-xi-ji-yu-mysql/#toc-heading-127