0301SQL优化-MySQL

文章目录

    • 1 插入数据
    • 2 主键优化
      • 2.1 页分裂
      • 2.2 页合并
      • 2.3 主键设计原则
    • 3 order by 优化
    • 4 group by 优化
    • 5 limit 优化
    • 6 count 优化
    • 7 update 优化
    • 结语

1 插入数据

  • 批量插入

    insert into tb_test values(1, 'tom'),(2,'cat'),(3,'jemi'),...;
    
  • 手动提交事务

    start transaction;
    insert into tb_test values(1, 'tom'),(2,'cat'),(3,'jemi');
    insert into tb_test values(4, 'tom'),(5,'cat'),(6,'jemi');
    insert into tb_test values(7, 'tom'),(8,'cat'),(9,'jemi');
    # ...
    commit;
    
  • 主键顺序插入

    主键顺序插入:1 3 8 22 77 105 ...
    主键乱序插入:8 1 77 105 3 ...
    
    • 主键推荐使用雪花算法或者类似算法
  • 大批量插入数据:如果一次需要大批量插入数据,可以使用mysql提供的load指令进行插入。操作如下:

    # 客户端链接加上参数--local-infile
    mysql --local-infile -uroot -p
    # 查看和设置全局参数local_infile,表示是否开启从本地加载文件导入数据库的开关
    set global local_infile=1;
    # 执行load指令将准备好的数据,加载到表结构中
    load data local infile '/.../xxx' into table 'table-name' fields terminated  by ',' lines terminated by '\n';
    
    • fields terminated by :表示字段分隔符;

    • lines teminated by :表示行分隔符,windows系统’\r\n’,Linux或者类unix系统为`\n’;

    • 示例:

      -- 示例数据 200w条
      1,100000003145001,华为Meta1,87901,9961,100,https://m.360buyimg.com/mobilecms/s720x720_jfs/t5590/64/5811657380/234462/5398e856/5965e173N34179777.jpg!q70.jpg.webp,https://m.360buyimg.com/mobilecms/s720x720_jfs/t5590/64/5811657380/234462/5398e856/5965e173N34179777.jpg!q70.jpg.webp,10,2019-05-01,2019-05-01,真皮包,viney,白色1,39,0,1
      2,100000003145002,华为Meta2,3,9946,100,https://m.360buyimg.com/mobilecms/s720x720_jfs/t23998/350/2363990466/222391/a6e9581d/5b7cba5bN0c18fb4f.jpg!q70.jpg.webp,https://m.360buyimg.com/mobilecms/s720x720_jfs/t23998/350/2363990466/222391/a6e9581d/5b7cba5bN0c18fb4f.jpg!q70.jpg.webp,10,2019-05-01,2019-05-01,拉拉裤,巴布豆,白色2,54,0,1
      --sql
      load data local infile '/Users/gaogzhen/baiduSyncdisk/study/database/tb_sku1.csv' into table tb_sku fields terminated  by ',' lines terminated by '\n';
      

2 主键优化

  • 数组组织方式:在InnoDB存储引擎中,表数据都是根据主键顺序组织存放的,这存储方式的表称为索引组织表(index organized table IOT)。

2.1 页分裂

  • 页分裂:页可以为空,也可以填充一半,也可以填充100%,每个页包含2-N行数据(如果一行数据过大,会导致行溢出),根据主键排列

    主键的顺序插入和乱序插入可以在页分裂方面产生不同的影响。下面将分别讨论这两种情况:

    1. 主键顺序插入:
      • 在主键顺序插入的情况下,新插入的行数据的主键值是递增的,按照顺序依次插入到数据库表中。
      • 这种情况下,页分裂的频率相对较低。因为新插入的行数据总是在已有的页的末尾进行插入,不会引起页内数据的移动或页的分裂。
      • 顺序插入可以减少页分裂的开销,提高插入性能和效率。
    2. 主键乱序插入:
      • 在主键乱序插入的情况下,新插入的行数据的主键值是无序的,随机插入到数据库表中。
      • 这种情况下,页分裂的频率相对较高。因为新插入的行数据可能会在已有页的中间位置插入,导致页内数据的移动或页的分裂。
      • 乱序插入会增加页分裂的开销,导致额外的磁盘 I/O 操作和索引重构。

    为了减少页分裂的影响,以下是一些建议:

    • 使用适当的页大小:选择合适的页大小可以控制每个页中的行数,从而减少页分裂的频率。
    • 预留空间:在页中预留一些空间,避免页填满,可以减少页分裂的频率。
    • 考虑聚集索引:对于主键聚集索引的表,顺序插入可以减少页分裂的概率。聚集索引是按照主键顺序组织数据的。
    • 分析和监测:使用性能监测工具进行分析,监测页分裂的频率和性能影响。根据监测结果,优化表结构、索引设计等因素。

    需要注意的是,页分裂的影响会因数据库引擎的实现和配置参数而有所不同。因此,针对具体的数据库环境,建议进行实际的测试和评估,以确定适合的优化策略和调整参数。

2.2 页合并

  • 页合并过程:
    • 当一个页中的数据量较少时,数据库引擎可以选择与相邻的页进行合并。
    • 合并过程会将两个页中的数据整合到一个新的更大的页中,并且会相应地更新索引。
    • 合并后的新页将保持数据的排序和唯一性。
  • 页合并的优点:
    • 减少页的数量:合并相邻的页可以减少数据库中的页数,降低存储空间的开销。
    • 提高存储效率:较大的页可以容纳更多的行数据,减少随机磁盘访问,提高数据读取的效率。
  • 页合并的注意事项:
    • 合并的触发条件:具体数据库引擎会根据一些策略和算法来判断何时触发页合并操作。这可能与数据库引擎的实现和配置有关,可能考虑数据的增长模式、空闲空间的利用等因素。
    • 影响性能:页合并涉及到数据的重组和索引的更新,可能会引起额外的磁盘 I/O 操作和索引重构,对数据库的性能产生一定影响。因此,合并操作需要在合适的时机和策略下进行,以避免不必要的性能开销。
    • 监测和调优:使用数据库性能监测工具来监测页合并的频率和性能影响。根据监测结果,可以考虑调整数据库的配置参数、优化表结构、页大小等因素,以达到更好的性能。

需要注意的是,页合并的具体实现和策略可能因数据库引擎的不同而有所差异。建议在具体数据库环境中,查阅相关文档或资源,了解特定数据库引擎的页合并机制和最佳实践。

2.3 主键设计原则

  • 满足业务需要的情况下,尽量降低主键的长度;
  • 插入数据时,尽量选择顺序插入,非分布式情况下选择AUTO_INCREMENT主键自增;分布式可以选择雪花算法或类似算法生成主键;
  • 尽量不要使用UUID做主键或者其他自然主键,如省份证,在插入时乱序插入;
  • 业务操作时,避免对主键操作;

3 order by 优化

  1. Using filesort: 当执行ORDER BY查询时,如果MySQL无法使用索引来按照指定的排序方式直接返回结果,则会使用"Using filesort"进行排序。 "Using filesort"意味着MySQL需要在排序缓冲区进行排序操作。这可能会导致性能开销较大,尤其是在处理大量数据时。
  2. Using index: 当执行ORDER BY查询时,如果MySQL能够使用合适的索引按照指定的排序方式直接返回结果,则会使用"Using index"。 "Using index"表示MySQL可以通过索引的有序性来避免进行实际的排序操作,从而提高查询性能。

通常情况下,"Using filesort"是一个较慢的操作,而"Using index"是一个较快的操作。因此,当出现"Using filesort"时,我们可以考虑优化查询或索引以避免使用临时文件进行排序。

以下是一些优化查询的建议:

  1. 确保查询的WHERE条件和ORDER BY子句能够使用到合适的索引。创建适当的索引可以帮助MySQL直接使用索引来排序。
  2. 考虑使用覆盖索引(Covering Index),即创建包含所有查询字段和排序字段的索引。这样MySQL可以直接使用索引来返回结果,而无需访问实际的数据行。
  3. 如果查询中有多个排序字段,请确保创建了适当的复合索引,以便MySQL可以使用这些索引来执行排序。
  4. 避免在大数据集上执行不必要的排序。如果可能的话,可以通过限制返回结果的行数或使用更具体的WHERE条件来减少需要排序的数据量。
  5. 在必要时,可以考虑通过增加系统资源(如排序缓冲区大小sort_buffer_size默认256k)。

需要根据具体的查询和数据情况来优化,以达到更好的性能和效率。使用EXPLAIN语句可以帮助分析查询的执行计划,并确定是否存在潜在的性能问题。

示例:以之前的tb_user1表为例,当前表索引有主键索引、联合索引(profession,age,status)、前缀索引(email)

  • 查询id,age,phone安装年龄排序

    explain select id, age, phone from tb_user1 ORDER BY age;
    -- 查询结果
    1	SIMPLE	tb_user1		ALL					24	100.00	Using filesort
    
  • 创建联合索引,再次排序

    create index idx_user_age_phone on tb_user1(age, phone);
    -- 现根据age排序,在根据phone排序
    explain select id, age, phone from tb_user1 ORDER BY age, phone;
    -- 查询结果
    1	SIMPLE	tb_user1		index		idx_user_age_phone	48		24	100.00	Using index
    
  • 根据age倒序,在根据phone倒序

    explain select id, age, phone from tb_user1 ORDER BY age desc, phone desc;
    -- 查询结果
    1	SIMPLE	tb_user1		index		idx_user_age_phone	48		24	100.00	Backward index scan; Using index
    
    • 建立联合索引默认先age升序,在phone升序;
    • 如果同时倒序查询,需要先进行反向索引扫描,在通过索引查询。
  • 先根据phone排序,在根据age排序

    explain select id, age, phone from tb_user1 ORDER BY phone, age;
    -- 查询结果
    1	SIMPLE	tb_user1		index		idx_user_age_phone	48		24	100.00	Using index; Using filesort
    
    • 违背最左前缀法则
  • 先根据age正序,在根据phone倒序

    explain select id, age, phone from tb_user1 ORDER BY age, phone desc;
    -- 查询结果
    1	SIMPLE	tb_user1		index		idx_user_age_phone	48		24	100.00	Using index; Using filesort
    
    • phone需要额外排序,在我们创建的联合索引中,如下

      tb_user1	1	idx_user_age_phone	1	age	A	19			YES	BTREE			YES	
      tb_user1	1	idx_user_age_phone	2	phone	A	24				BTREE			YES	
      
    • 其中字段collation,A(ascend)表示正序,D(descend)表示倒序;

4 group by 优化

现在清除除主键索引之外的其他索引。

  • 根据profession分组查询

    explain select profession, count(*) from tb_user1  GROUP BY profession;
    -- 查询结果
    1	SIMPLE	tb_user1		ALL					24	100.00	Using temporary
    
    • extra 字段using temporary使用临时表,性能很低。
  • 创建联合索引,在此查询

    -- 创建索引
    create index idx_user_pro_age_sta on tb_user1(profession, age, phone);
    -- 根据profession 分组查询
    explain select profession, count(*) from tb_user1  GROUP BY profession;
    -- 查询结果
    1	SIMPLE	tb_user1		index	idx_user_pro_age_sta	idx_user_pro_age_sta	54		24	100.00	Using index
    
  • 测试根据age分组即加上where

    explain select age, count(*) from tb_user1  GROUP BY age;
    -- 查询结果
    1	SIMPLE	tb_user1		index	idx_user_pro_age_sta	idx_user_pro_age_sta	54		24	100.00	Using index; Using temporary
    
    explain select age, count(*) from tb_user1 WHERE profession='软件工程' GROUP BY age;
    -- 查询结果
    1	SIMPLE	tb_user1		ref	idx_user_pro_age_sta	idx_user_pro_age_sta	47	const	4	100.00	Using index
    
    • 第一个查询不符合最左前缀法则;第二个查询先过滤在分组;
    • 分组操作时,可以通过索引提高效率;
    • 分组操作时,索引使用需满足最左前缀法则。

5 limit 优化

对于大数据集,如果查询`limit 2000000, 10’,此时需要对前2000010条数据排序,然后仅返回2000000-2000010的记录,查询开销很大。

示例

select count(*) from tb_sku;
-- 查询结果
9874653
select * from tb_sku limit 9000000,10;
-- 耗时
7.063s

  • 优化第一步,查询id使用覆盖索引

    select id from tb_sku limit 9000000,10
    
  • 第二步与原表连表查询

     select s.id, s.name
     from  (select id from tb_sku limit 9000000,10) a LEFT JOIN tb_sku s 
     on s.id=a.id;
     -- 或者
       select s.id, s.name
     from  (select id from tb_sku limit 9000000,10) a , tb_sku s 
     where s.id=a.id;
    

6 count 优化

EXPLAIN select count(*) from tb_sku;
-- 查询结果
1	SIMPLE	tb_sku		index		PRIMARY	4		8782587	100.00	Using index
  • MyISAM 引擎把表的总行数存在磁盘上,因此执行count(*)时会直接返回这个数,效率高;
  • InnoDB引擎,执行count(*)时,需要把数据一行一行从引擎里面读出来,然后累计计数。

优化思路:自己计数。

  • count的几种用法
  • count()是一个聚合函数,对于返回的结果,一行一行的判断,如果count对应行不是NULL,累计值加1;否则不加。最后返回累计值。
  • 用法:count(*),count(主键),count(字段),count(1)
    • count(主键):InnoDB引擎会遍历整张表,把每一行的主键id拿出来,返给服务层。服务层拿到主键后,直接进行累加(主键不为空)。
    • count(字段):没有not null字段约束,遍历整张表,把每一行的字段值拿出来,返给服务层。服务层拿到字段值后,判断是否为NULL,不为NULL,进行累加;有not null约束,遍历整张表,把每一行的字段值拿出来,返给服务层。服务层拿到字段值后,直接进行累加。
    • count(1):InnoDB引擎会遍历整张表,但不取值。服务层对于返回的每一行,放一个数字1进去,直接进行累加。
    • count(*):InnoDB并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接进行累加。

按照效率排序:count(字段)

7 update 优化

示例:

 SHOW index from course;
 -- 查询结果
 course	0	PRIMARY	1	id	A	4				BTREE			YES	

-- 开启事务
BEGIN;
update course set `name`='JavaEE' where `name`='Java';
-- 执行成功

-- 开启另外一个事务
 BEGIN;
 update course set `name`='Springboot' where id = 3;
 -- 阻塞

InnoDB引擎默认为行锁,是针对索引加的锁,不是针对记录的锁,并且改索引不能失效,否则从行锁升级为表锁。

结语

如果小伙伴什么问题或者指教,欢迎交流。

❓QQ:806797785

参考链接:

[1]MySQL数据库视频[CP/OL].2020-04-16.p89-96.

你可能感兴趣的:(#,MySQL,mysql,优化)