【MySQL索引与优化篇】索引优化与查询优化

news2024/12/28 18:04:33

索引优化与查询优化

文章目录

  • 索引优化与查询优化
    • 1. 概述
    • 2. 索引失效案例
    • 3. 关联查询优化
      • 3.1 Join语句原理
      • 3.2 Simple Nested-Loop Join(简单嵌套循环连接)
      • 3.3 Index Nested-Loop Join(索引嵌套循环连接)
      • 3.4 Block Nested-Loop Join(块嵌套循环连接)
      • 3.5 Hash Join
      • 3.6 小结
    • 4. 子查询优化
    • 5. 排序优化
    • 6. GROUP BY 优化
    • 7. 分页查询优化
    • 8. 字符串的前缀索引
    • 9. 索引下推
    • 10. 普通索引和唯一索引
      • 10.1 更新过程
      • 10.2 使用场景
    • 11. 其它查询优化策略
      • 11.1 EXISTS 和 IN 的区分
      • 11.2 count(具体字段)的使用
      • 11.3 关于select *
      • 11.4 多使用commit
    • 12. 推荐的主键策略

1. 概述

虽然 SQL 查询优化的技术有很多,但是大方向上完全可以分成 物理查询优化逻辑查询优化 两大块

  • 物理查询优化是通过 索引表连接方式 等技术来进行优化,这里重点需要掌握索引的使用
  • 逻辑查询优化就是通过 SQL 等价变换 提升查询效率,直白一点就是说,换一种查询写法执行效率可能更高

2. 索引失效案例

MySQL 提升性能最有效的方式就是 设计合理的索引,但是否用索引都由优化器决定

优化器是基于 开销(CostBaseOptimizer),它不是基于 规则(Rule-BasedOptimizer),也不是基于 语义。怎么开销小就怎么来。另外,SQL语句是否使用索引,跟数据库版本、数据量、数据选择度都有关系

索引失效情况:

  1. 最佳左前缀法则:对于MySQL多列索引,过滤条件要使用索引必须按照索引建立时的顺序,依次满足,一旦跳过某个字段,索引后面的字段都无法被使用
  2. 计算、函数、类型转换(自动或手动)都会导致索引失效
  3. 范围条件右边的列索引失效,多列索引中,过滤条件中索引左边有范围查询的话,则对应列右边的列索引将失效
    • 应用开发中,金额和日期查询往往是范围查询,建立联合索引时务必把涉及范围查询的字段放在索引后面
  4. 不等于(!= 或者 <>)索引失效
  5. is null可以使用索引,is not null无法使用索引
    • 最好在设计表的时候就将字段设置为not null约束,并给定默认值,int为0,字符串为’’
  6. like 以通配符 %开头将无法使用索引
  7. or 前后存在非索引的列,索引失效
  8. 数据库和表的字符集统一,不同字符集进行比较前需要进行 转换会造成索引失效

一般性建议:

  • 对于单列索引,尽量选择针对当前query过滤性更好的索引
  • 在选择组合索引的时候,当前query中过滤性最好的字段在索引字段顺序中,位置越靠前越好
  • 在选择组合索引的时候,尽量选择能够包含当前query中的where子句中更多字段的索引
  • 在选择组合索引的时候,如果某个字段可能出现范围查询时,尽量把这个字段放在索引次序的最后面

3. 关联查询优化

3.1 Join语句原理

驱动表就是主表,被驱动表就是从表、非驱动表。Join连接时,无索引时,需遍历主表,逐个与被驱动表比对,嵌套循环遍历被驱动表。MySQL5.5后的版本引入了BNLJ算法来优化嵌套执行。MySQL8.0.18版本前,在被驱动表有索引的情况下运用了INLJ算法无索引时采用BNLJ算法。从MySQL的8.0.20版本开始将废弃BNLJ,因为从MySQL8.0.18版本开始就加入了hash join,默认都会使用hash join

3.2 Simple Nested-Loop Join(简单嵌套循环连接)

算法:从驱动表A中取出一条数据1,遍历被驱动表B,将匹配到的数据放入result…以此类推,驱动表A中的每一条记录与驱动表B的记录进行判断,算法复杂度为O(A * B),效率很低。MySQL当然不会这么粗暴的进行表的连接。

3.3 Index Nested-Loop Join(索引嵌套循环连接)

Index Nested-Loop Join优化的主要思路是 减少被驱动表数据的匹配次数,所以要求被驱动表上必须 有索引才行。通过驱动表匹配条件直接与被驱动表索引进行匹配,避免和被驱动表的每条记录比较,使得算法复杂度为O(A * LogB),其中因为索引的结构是B+树,LogB的大小一般不会超过4。如果在B上的索引不是主键,还需要进行回表操作,所以如果被驱动表的索引是主键索引的话效率会更高

3.4 Block Nested-Loop Join(块嵌套循环连接)

该算法不是按SNLJ算法一样逐条获取驱动表的数据,而是一块一块的获取,引入了 join buffer缓冲区,将驱动表join相关的部分数据列(大小受join buffer的限制)缓存到join buffer中,然后全表扫描被驱动表,被驱动表的每一条记录一次性和join buffer缓冲区中的所有驱动表记录进行匹配(内存中操作),将简单嵌套循环中的多次比较合并为一次,降低了被驱动表的访问频率。

注意:这里缓存的不只是关联表的列,select 后面的列也会缓存起来

在一个有N个join关联的sql中会分配N-1个join buffer,所以查询的时候尽量减少不必要的字段,可以让join buffer中可以存放更多的列

在这里插入图片描述

参数设置:

  • block_nested_loop
    • 通过 show variables like '%optimizer_switch%'查看block_nested_loop状态。默认是开启的
  • join_buffer_size:默认值:256k

3.5 Hash Join

  • Nested Loop:对于被连接的数据子集较小的情况,嵌套循环还是个较好的选择
  • Hash Join是做 大数据集连接 时的常用方式,优化器使用两个表中较小(相对较小)的表利用 Join Key 在内存中建立 散列表,然后扫描较大的表并探测散列表,找出与Hash表匹配的行,内存设置还是join_buffer_size
    • 这种方式适用于较小的表完全可以放于内存中的情况,这样总成本就是访问两个表的成本之和
    • 在表很大的情况下并不能完全放入内存,这时优化器会将它分割成 若干不同的分区,不能放入内存的部分就把该分区写入磁盘的临时段,此时要求有较大的临时段从而尽量提高l/O 的性能
    • 它能够很好的工作于没有索引的大表和并行查询的环境中,并提供最好的性能。大多数人都说它是Join的重型升降机。Hash Join只能应用于等值连接(如WHERE A.COL1= B.COL2),这是由Hash的特点决定的

3.6 小结

  1. 整体效率:INLJ > BNLJ > SNLJ

  2. 永远用小结果集驱动大结果集(其本质就是减少外层循环的数据数量) (小的度量单位指的是 表行数 * 每行大小,而不是单纯指行数)

    -- STRAIGHT_JOIN就是在内连接中使用,而强制使用左表来当驱动表,所以这个特性可以用于一些调优,强制改变mysql的优化器选择的执行计划
    
    select t1.b,t2.* from t1 straight_join t2 on (t1.b=t2.b) where t2.id<=100; #推荐,t2取了所有字段,相对来说join buffer里能存的数据更多
    
    select t1.b,t2.* from t2 straight_join t1 on (t1.b=t2.b) where t2.id<=100; #不推荐
    
  3. 为被驱动表匹配的条件增加索引(减少被驱动表的循环匹配次数)

  4. 增大join_buffer_size的大小 (一次缓存的数据越多,那么被驱动表的扫表次数就越少)

  5. 减少驱动表不必要的字段查询 (字段越少,join buffer 所缓存的数据行数就越多)

4. 子查询优化

执行子查询时需要建立撤销临时表,且临时表还是磁盘都不会有索引,在MySQL中建议使用Join查询来替代子查询,尽量不用not in 或in 函数

select a.* from
(
    select a.* ,b.field 
    from tabname  as a left outer join tabname as b on a.field =b.field
) x where field is null

5. 排序优化

在MySQL 中,支持两种排序方式,分别是 FileSortIndex 排序。

  • Index 排序中,索引可以保证数据的有序性,不需要再进行排序,效率更高
  • FileSort 排序则一般在 内存中 进行排序,占用 CPU 较多。如果待排结果较大,会产生临时文件 /0 到磁盘进行排序的情况,效率较低

优化建议:

  1. FileSort 排序不一定比索引慢。但总的来说,我们还是要避免,以提高查询效率
  2. 尽量使用Index 完成ORDER BY 排序。如果 WHERE 和 ORDER BY 后面是相同的列就使用单索引列;如果不同就使用联合索引
  3. 如果靠过滤条件能过滤掉大部分数据,则使用 FileSort也能接受,order by 在数据过滤后剩余数据量还比较大的时候才会考虑使用索引,否则会优先使用成本低的FileSort
  4. 无法使用Index 时考虑对 FileSort 方式进行调优

filesort的两种排序方式:

  • 双路排序:从磁盘取排序列,排序完再从磁盘取出对应数据输出
  • 单路排序:从磁盘读取查询需要的所有列,排序后输出;需要更大内存空间,如果待排数据量很大,每次只能取sort_fuffer的容量,进行排序再合并,排完再取会导致大量的I/O操作,反而得不偿失

FileSort 优化策略

  1. 提高 sort_buffer_size,提升排序时可用的内存容量大小,innodb存储引擎默认值是1MB
  2. 尝试提高 max_length_for_sort_data,如果需要返回的列的总长度大于max_length_for_sort_data,使用双路算法,否则使用单路算法,1024~8192字节之间调整。默认1024字节;即提高后会增加使用单路算法的阈值

注意

order by 时 select * 是大忌,最好只取需要的字段,原因:

  • 当查询的字段大小总和小于 max_length_for_sort_data时,而且排序字段不是TEXT|BLOB类型时,会用改进后的排序——单路排序,否则使用多路排序
  • 两种算法的数据都有可能超过 sort_buffer_size 的容量,超出之后,会创建tmp文件进行合并排序,导致多次I/O,但是用单路排序算法的风险会更大一些,所以要提高 sort_buffer_size

6. GROUP BY 优化

  • where效率高于having,能写在where限定的条件就不要写在having中
  • 减少使用order by,和业务沟通能不排序就不排序,或将排序放到程序端去做。Order by、 group by、distinct这些语句较为耗费CPU,数据库的CPU资源是极其宝贵的
  • 包含了order by、group by、distinct这些查询的语句,where条件过滤出来的结果集请保持在1000行以内,否则SQL会很慢

7. 分页查询优化

优化思路一:在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容

SELECT * FROM student t,(SELECT id FRON student ORDER BY id LIMIT 2000000,10) aWHERE t.id = a.id;

优化思路二:该方案适用于主键自增的表,可以把Limit 查询转换成某个位置的查询

SELECT * FROM student WHERE d > 2000000 LIMIT 10:

优化思路三:游标方式,适合单调递增的数据,每次查询时传入上次查询的末尾的值,需与前端一起配合,和方案二类似,但无法跳页查询

8. 字符串的前缀索引

如果不指定索引长度,则索引会包含整个字符串 index1,指定长度则为前缀索引 index2

mysql> alter table teacher add index index1(email);
#或
mysql> alter table teacher add index index2(email(6));

如果使用的是index1(即email整个字符串的索引结构),执行顺序是这样的:

  1. 从index1索引树找到满足索引值是’ zhangssxyz@xxx.com ’的这条记录,取得ID2的值;
  2. 到主键上查到主键值是ID2的行,判断email的值是正确的,将这行记录加入结果集;
  3. 取index1索引树上刚刚查到的位置的下一条记录,发现已经不满足email=’ zhangssxyz@xxx.com ’的
    条件了,循环结束。

这个过程中,只需要回主键索引取一次数据,所以系统认为只扫描了一行。

如果使用的是index2(即email(6)索引结构),执行顺序是这样的:

  1. 从index2索引树找到满足索引值是’zhangs’的记录,找到的第一个是ID1;
  2. 到主键上查到主键值是ID1的行,判断出email的值不是’ zhangssxyz@xxx.com ’,这行记录丢弃;
  3. 取index2上刚刚查到的位置的下一条记录,发现仍然是’zhangs’,取出ID2,再到ID索引上取整行然
    后判断,这次值对了,将这行记录加入结果集;
  4. 重复上一步,直到在idxe2上取到的值不是’zhangs’时,循环结束。

也就是说使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本。前面已经讲过区分度,区分度越高越好。因为区分度越高,意味着重复的键值越少。

注意:使用前缀索引就用不上覆盖索引对查询性能的优化了,这也是在选择是否使用前缀索引时需要考虑的一个因素

9. 索引下推

在二级索引或联合索引中,判断条件中如果还有对应索引中字段,则先判断再回表

select * from student where name > 'a' and sno like '%20'; -- index(name, sno)

上述查询会通过联合索引找到 name > 'a’的数据的同时判断 sno like ‘%20’,之后再进行回表;如无icp(索引下推),则找到 name > 'a’的数据就会回表,查出数据后再判断sno like ‘%20’

10. 普通索引和唯一索引

普通索引和唯一索引应该怎么选择?其实,这两类索引在查询能力上是没差别的,主要考虑的是对 更新性能 的影响。所以,建议 尽量选择普通索引,但要考虑业务正确性优先

10.1 更新过程

为了说明普通索引和唯一索引对更新语句性能的影响这个问题,介绍一下change buffer。

当需要更新一个数据页时,如果数据页在内存中就直接更新,而如果这个数据页还没有在内存中的话,在不影响数据一致性的前提下, InooDB会将这些更新操作缓存在change buffer中 ,这样就不需要从磁盘中读入这个数据页了。在下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行change buffer中与这个页有关的操作。通过这种方式就能保证这个数据逻辑的正确性。

将change buffer中的操作应用到原数据页,得到最新结果的过程称为 merge 。除了 访问这个数据页 会触发merge外,系统有 后台线程会定期 merge。在 数据库正常关闭(shutdown) 的过程中,也会执行merge操作。

如果能够将更新操作先记录在change buffer,减少读磁盘 ,语句的执行速度会得到明显的提升。而且,数据读入内存是需要占用 buffer pool 的,所以这种方式还能够 避免占用内存 ,提高内存利用率。唯一索引的更新就不能使用change buffer ,实际上也只有普通索引可以使用

10.2 使用场景

首先, 业务正确性优先 。我们的前提是“业务代码已经保证不会写入重复数据”的情况下,讨论性能问题。如果业务不能保证,或者业务就是要求数据库来做约束,那么没得选,必须创建唯一索引。

然后,在一些“ 归档库 ”的场景。比如,线上数据只需要保留半年,然后历史数据保存在归档库。这时候,归档数据已经是确保没有唯一键冲突了。要提高归档效率,可以考虑把表里面的唯一索引改成普通索引

  • innodb_change_buffer_max_size:Change Buffer的最大大小,默认为25%的缓冲池大小;
  • innodb_change_buffering:Change Buffer的开启状态,默认为all,表示所有插入、更新和删除操作都会使用Change Buffer;

11. 其它查询优化策略

11.1 EXISTS 和 IN 的区分

遵守小表驱动大表

select * from a where cc in (select cc from b) -- a表比b表大

select * from a where exist(select cc from b where b.cc = a.cc) -- a表比b表小

11.2 count(具体字段)的使用

使用innodb存储引擎的情况下,如果使用count(具体字段)要尽量选择二级索引,因为主键是聚簇索引,需要把所有数据加载到内存中。

11.3 关于select *

  1. MySQL在解析的过程中,会通过 查询数据字典*按序转换为列名
  2. 无法使用 覆盖索引

11.4 多使用commit

commit所释放的资源:

  • 回滚段上用于恢复数据的信息
  • 被程序语句获得的锁
  • redo / undo log buffer 中的空间
  • 管理上述3种资源中的内部花费

12. 推荐的主键策略

非核心业务:对应表的主键自增ID,如警告、日志、监控等信息

核心业务:主键设计至少应该是全局唯一且是单调递增

最简单的主键设计:有序的UUID,将UUID时间高位与低位交换,且去除无意义的“-”字符串

UUID = 时间 + UUID版本(16字节) - 时钟序列(4字节) - MAC地址(12字节)

MySQL8.0提供的uuid_to_bin函数实现上述功能

select uuid_to_bin(uuid(), true);

在当今的互联网环境中,非常不推荐自增ID作为主键的数据库设计。更推荐类似有序UUID的全局唯一的实现。

另外在真实的业务系统中,主键还可以加入业务和系统属性,如用户的尾号,机房的信息等。这样的主键设计就更为考验架构师的水平了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1156006.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

发布不到一月的4+经典单细胞+预后模型生信思路,可复现可升级

今天给同学们分享一篇单细胞预后模型的生信文章“Integrating single-cell and bulk RNA sequencing to predict prognosis and immunotherapy response in prostate cancer”&#xff0c;这篇文章于2023年9月20日发表在Scientific Reports期刊上&#xff0c;影响因子为4.6。 前…

数藏平台纷纷停运 用户手中数字藏品成了一张图

2021年8月2日&#xff0c;腾讯上线幻核数藏平台&#xff0c;随后QQ音乐、腾讯动漫、腾讯视频、腾讯新闻、起点读书等腾讯系应用都植入了数字藏品板块&#xff0c;强势进入国内数藏市场。国内数藏市场也迎来了起飞时刻。2022年底&#xff0c;数藏市场进入寒冬&#xff0c;曾经人…

数字展厅搭建平台要具备哪些功能,如何选择数字展厅搭建平台

引言: 数字展厅搭建平什台是现代营销中不可或缺的重要工具之一。它可以帮助企业打造个性化、多媒体、互动性强的展示空间&#xff0c;吸引、引导和留住目标用户。在选择数字展厅搭建平台时&#xff0c;我们需要考虑各方面的功能和性能&#xff0c;以确保能够满足企业的需求并取…

建筑模板材质-不同材质建筑模板优缺点分析

建筑模板是施工过程中不可或缺的重要材料&#xff0c;不同材质的建筑模板各有其优缺点。在众多材质中&#xff0c;广西桉木芯建筑模板以其独特的性能和优势备受青睐&#xff0c;下面将从不同材质的建筑模板入手&#xff0c;重点推荐广西桉木芯建筑模板。一、胶合板建筑模板胶合…

前端小程序 实现文字加载效果 文字跳动

效果 主要看充电中...的效果 ![1 实现 <view v-else class"status-working"><text class"letter letter1">充</text><text class"letter letter2">电</text><text class"letter letter3">中&l…

二十二、Arcpy批量波段组合——结合Landat数据城市建成区提取

一、前言 其实波段组合和GIS中栅格计算有点类似,实质上就是对每个像素点对应的DN值进行数学计算,也就是可以进行运算表达式是三个或多个变量相加、相减……每一个变量对应于一个图像数据,对这三个或多个图像数据求值并输出结果图像。 二、具体操作 1、实验具体目标 将202…

Android WMS——WMS窗口添加(十)

Android 的 WMS&#xff08;Window Manager Service&#xff09;是一个关键组件&#xff0c;负责管理窗口的创建、显示、布局和交互等。Window 的操作有两大部分&#xff0c;一部分是 WindowManager 来处理&#xff0c;一部分是 WMS 来处理&#xff0c;如下图所示&#xff1a; …

验收测试的关键步骤是怎样的?

验收测试是项目管理中的一个关键步骤&#xff0c;旨在确保项目交付物(通常是软件、产品或服务)符合预期的质量标准和需求。这个过程有助于验证项目的可交付成果是否满足客户或利益相关者的期望&#xff0c;同时也为项目团队提供了机会来修复可能存在的问题和改进之前的工作。 一…

一个方法,教你快速监测蓄电池!

随着电力需求的不断增长和可再生能源的快速发展&#xff0c;蓄电池技术已经成为能源存储领域的重要组成部分。 蓄电池不仅在家庭和工业应用中发挥着重要作用&#xff0c;还在电网稳定性和可持续能源集成方面具有关键地位。然而&#xff0c;蓄电池的有效监控和管理对于确保其可靠…

【Qt控件之QMessageBox】详解

Qt控件之QMessageBox 描述基于属性的API富文本和文本格式属性严重程度以及图标和Pixmap属性静态函数API 高级用法默认按钮和退出按钮示例使用场景 描述 QMessageBox类提供了一个模态对话框&#xff0c;用于通知用户或向用户提问并接收答案。 消息框显示一个主要文本以提醒用户…

软件测试 —— 冒烟测试(Smoke Test,ST)

1. 核心 冒烟测试就是完成一个新版本的开发后&#xff0c;对该版本最基本的功能进行测试&#xff0c;保证基本的功能和流程能走通。 如果不通过&#xff0c;则打回开发那边重新开发&#xff1b; 如果通过测试&#xff0c;才会进行下一步的测试(功能测试&#xff0c;集成测试&a…

SQLyog连接数据库报plugin caching_sha2_password could not be loaded......解决方案

问题描述 问题分析 因为MySQL新版默认使用caching_sha2_password作为身份验证的插件&#xff0c;而旧版本使用的是mysql_native_password。当出现plugin caching_sha2_password could not be loaded报错&#xff0c;我们更换为旧版本 如何解决 先使用cmd命令登录MySQL&a…

从零开始的LINUX(四)

1.yum&#xff1a; 功能&#xff1a;软件包管理器&#xff0c;功能类似与手机上的应用商店。通过yum可以获取指令的下载地址&#xff0c;然后一键式安装指令。由于yum中的地址一般都是外网的&#xff0c;所以需要镜像源&#xff08;即国内的下载地址&#xff09;。 相关指令&…

2023最网最全软件测试基础知识【建议收藏】

​对于一个软件来说&#xff0c;总会存在各种各样的软件缺陷。因此我们需要通过软件测试来检查软件中存在的各种问题。 在下面的这篇文章中&#xff0c;将讲解软件测试的基础知识&#xff0c;让我们一起来了解一下吧 一、 软件缺陷的概述 1、什么是软件缺陷 ​ 软件缺陷就是…

博弈论学习笔记(3)——完全信息动态博弈

前言 在这个部分&#xff0c;我们学习的是完全信息动态博弈。主要内容包括扩展式博弈、子博弈精炼Nash均衡、重复博弈和子博弈精炼Nash均衡的应用。 一、扩展式博弈 1、扩展式博弈 1&#xff09;扩展式博弈是什么 扩展式博弈是博弈问题的一种规范性描述&#xff0c;扩展式博…

【MATLAB源码-第64期】matlab基于DWA算法的机器人局部路径规划包含动态障碍物和静态障碍物。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 动态窗口法&#xff08;Dynamic Window Approach&#xff0c;DWA&#xff09;是一种局部路径规划算法&#xff0c;常用于移动机器人的导航和避障。这种方法能够考虑机器人的动态约束&#xff0c;帮助机器人在复杂环境中安全、…

java面向对象编程高级

1、static修饰符 1.1static修饰成员变量 static叫静态&#xff0c;可以修饰成员变量、成员方法 成员变量按照有无static修饰&#xff0c;分为两种 类变量 : 有static修饰&#xff0c;属于类&#xff0c;在计算机里只有一份&#xff0c;会被类的全部对象共享 在开发中&#…

处理固定资产折旧报错 AFAB “根据记帐循环, 您必须接下来对期间 001记帐”

会计在运用进行固定资产折旧时&#xff0c;发现有个报错“根据记帐循环, 您必须接下来对期间 001记帐”&#xff0c; 根据记帐循环, 您必须接下来对期间 001记帐 消息编号 AA683 诊断 不可以在指定的期间过帐折旧&#xff0c;因为此操作会遗漏过帐期间。 系统响应 该期间不能进…

Nginx域名重定向(如何访问的域名和实际的数据请求路径不同,可解决前端跨域)

感情需要被抑制&#xff0c;不能泛滥… 当需要将一个域名重定向到另一个域名并且用户仍然看到原始域名时&#xff0c;Nginx是一个强大的工具。这种场景通常涉及到反向代理或重写URL的技巧。在本篇博客中&#xff0c;我们将详细介绍如何使用Nginx来实现这个目标&#xff0c;以及…

SPSS两独立样本t检验

前言&#xff1a; 本专栏参考教材为《SPSS22.0从入门到精通》&#xff0c;由于软件版本原因&#xff0c;部分内容有所改变&#xff0c;为适应软件版本的变化&#xff0c;特此创作此专栏便于大家学习。本专栏使用软件为&#xff1a;SPSS25.0 本专栏所有的数据文件请点击此链接下…