晋江原创网,seo怎么做新手入门,重庆网站建设夹夹虫,网站建设设计技术方案模板1.数据库服务器的优化步骤 在数据库调优中#xff0c;我们的目标就是响应时间更快#xff0c;吞吐量更大。利用宏观的监控工具和微观的日志分析可以帮我们快速找到调优的思路和方式 数据库服务器的优化步骤
当我们遇到数据库调优问题的时候#xff0c;该如何思考呢#xf…1.数据库服务器的优化步骤 在数据库调优中我们的目标就是响应时间更快吞吐量更大。利用宏观的监控工具和微观的日志分析可以帮我们快速找到调优的思路和方式 数据库服务器的优化步骤
当我们遇到数据库调优问题的时候该如何思考呢这里把思考的流程整理成下面这张图。
整个流程划分成了 观察Show status 和行动Action两个部分。字母 S 的部分代表观察会使用相应的分析工具字母 A 代表的部分是行动对应分析可以采取的行动 首先在S1部分需要观察服务器的状态是否存在周期性的波动。如果存在周期性波动有可能是周期性节点的原因比如双十一、促销活动等。这样的话可以通过A1这一步骤解决也就是加缓存或者更改缓存失效策略。
如果缓存策略没有解决或者不是周期性波动的原因就需要进一步分析查询延迟和卡顿的原因。接下来进入S2这一步需要开启慢查询。慢查询可以帮我们定位执行慢的SQL语句。可以通过设置long_query_time参数定义“慢的阈值如果SQL执行时间超过了long query_time则会认为是慢查询。当收集上来这些慢查询之后就可以通过分析工具对慢查询日志进行分析。
在S3这一步骤中我们就知道了执行慢的SQL这样就可以针对性地用EXPLAIN查看对应SQL语句的执行计划或者使用show profile查看SQL中每一个步骤的时间成本。这样就可以了解SQL查询慢是因为执行时间长还是等待时间长。
如果是SQL等待时间长就进入A2步骤。在这一步骤中可以调优服务器的参数比如适当增加数据库缓冲池等。如果是SQL执行时间长就进入A3步骤这一步中需要考虑是索引设计的问题?还是查询关联的数据表过多?还是因为数据表的字段设计问题导致了这一现象。然后在这些维度上进行对应的调整。 如果A2和A3都不能解决问题需要考虑数据库自身的SQL查询性能是否已经达到了瓶颈如果确认没有达到性能瓶颈就需要重新检查重复以上的步骤。如果已经达到了性能瓶颈进入A4阶段需要考虑增加服务器采用读写分离的架构或者考虑对数据库进行分库分表比如垂直分库、垂直分表和水平分表等。
以上就是数据库调忧的流程思路。如果发现执行SQL时存在不规则延迟或卡顿的时候就可以采用分析工具帮我们定位有问题的SQL这三种分析工具可以理解是SQL调优的三个步骤:慢查询、EXPLAIN和SHOEW PROFILING
小结 2.查询系统性能参数 在MySQL中SHOW STATUS是一个用于显示服务器状态的命令它可以提供有关服务器运行时的大量信息包括各种计数器和状态值。这些信息对于诊断问题、性能调优和了解服务器的运行情况非常有用。 使用SHOW STATUS时可以指定不同的参数来过滤显示的信息
SHOW STATUS显示所有状态变量。
SHOW [GLOBAL|SESSION] STATUS LIKE 参数;
一些常用的性能参数如下 Connections连接MySQL服务器的次数。UptimeMySQL服务器的上线时间。Slow_queries慢查询的次数。Innodb_rows_readSelect查询返回的行数Innodb_rows_inserted执行INSERT操作插入的行数Innodb_rows_updated执行UPDATE操作更新的行数Innodb_rows_deleted执行DELETE操作删除的行数Com_select查询操作的次数。Com_insert插入操作的次数。对于批量插入的 INSERT 操作只累加一次。Com_update更新操作的次数。Com_delete删除操作的次数。 若查询MySQL服务器的连接次数则可以执行如下语句
SHOW STATUS LIKE Connections;
若查询服务器工作时间则可以执行如下语句
SHOW STATUS LIKE Uptime
若查询MySQL服务器的慢查询次数则可以执行如下语句
SHOW STATUS LIKE Slow_queries
3. 统计SQL的查询成本last_query_cost 一条SQL查询语句在执行前需要确定查询执行计划如果存在多种执行计划的话MySQL会计算每个执行计划所需要的成本从中选择成本最小的一个作为最终执行的执行计划。 如果想要查看某条SQL语句的查询成本可以在执行完这条SQL语句之后通过查看当前会话中的last_query_cost变量值来得到当前查询的成本。它通常也是评价一个查询的执行效率的一个常用指标。这个查询成本对应的是SQL语句所需要读取的页的数量 CREATE TABLE student_info (
id INT(11) NOT NULL AUTO_INCREMENT,
student_id INT NOT NULL ,
name VARCHAR(20) DEFAULT NULL,
course_id INT NOT NULL ,
class_id INT(11) DEFAULT NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) ENGINEINNODB AUTO_INCREMENT1 DEFAULT CHARSETutf8;
如果想要查询 id900001 的记录然后看下查询成本我们可以直接在聚簇索引上进行查找
SELECT student_id, class_id, NAME, create_time FROM student_info
WHERE id 900001;
#运行结果1 条记录运行时间为 0.042s
然后再看下查询优化器的成本实际上我们只需要检索一个页即可
SHOW STATUS LIKE last_query_cost;
/*
---------------------------
| Variable_name | Value |
---------------------------
| Last_query_cost | 1.000000 |
---------------------------
*/
如果要查询 id 在 900001 到 9000100 之间的学生记录呢
SELECT student_id, class_id, NAME, create_time FROM student_info
WHERE id BETWEEN 900001 AND 900100;
运行结果100 条记录运行时间为 0.046s
然后再看下查询优化器的成本这时我们大概需要进行 20 个页的查询 SHOW STATUS LIKE last_query_cost;/*
----------------------------
| Variable_name | Value |
----------------------------
| Last_query_cost | 21.134453 |
----------------------------
*/
能看到页的数量是刚才的 20 倍但是查询的效率并没有明显的变化实际上这两个 SQL 查询的时间基本上一样就是因为采用了顺序读取的方式将页面一次性加载到缓冲池中然后再进行查找。虽然页数量last_query_cost增加了不少 但是通过缓冲池的机制并没有增加多少查询时间 。
使用场景它对于比较开销是非常有用的特别是有好几种查询方式可选的时候 SQL查询是一个动态的过程从页加载的角度来看可以得到以下两点结论: 位置决定效率。如果页就在数据库缓冲池中那么效率是最高的否则还需要从内存或者磁盘中进行读取当然针对单个页的读取来说如果页存在于内存中会比在磁盘中读取效率高很多。批量决定效率。如果从磁盘中对单一页进行随机读那么效率是很低的差不多10ms)而采用顺序读取的方式批主对页进行读取平均一页的读取效率就会提升很多甚至要快于单个页面在内存中的随机读取。 所以说遇到I/O并不用担心方法找对了效率还是很高的。首先要考虑数据存放的位置如果是经常使用的数据就要尽量放到缓冲池中其次可以充分利用磁盘的吞吐能力一次性批量读取数据这样单个页的读取效率也就得到了提升。 4.定位执行慢的 SQL慢查询日志 MySQL的慢查询日志用来记录在MySQL中响应时间超过阈值的语句具体指运行时间超过long_query_time值的SQL则会被记录到慢查询日志中。long_query_time的默认值为10意思是运行10秒以上(不含10秒)的语句认为是超出了最大忍耐时间值。 它的主要作用是帮助我们发现那些执行时间特别长的SQL查询并且有针对性地进行优化从而提高系统的整体效率。当数据库服务器发生阻塞、运行变慢的时候检查一下慢查询日志找到那些慢查询对解决问题很有帮助。比如一条sql执行超过5秒钟就算慢SQL希望能收集超过5秒的sql结合explain进行全面分析。
默认情况下MySQL数据库没有开启慢查询日志需要手动来设置这个参数。如果不是调优需要的话一般不建议启动该参数因为开启慢查询日志会或多或少带来一定的性能影响。
慢查询日志支持将日志记录写入文件。
show variables like slow_query_log;
/*
-----------------------
| Variable_name | Value |
-----------------------
| slow_query_log | OFF |
-----------------------
*/
4.1 开启慢查询日志参数
1.开启slow_query_log
在使用前需要先看下慢查询是否已经开启使用下面这条命令即可:
show variables like slow_query_log;
/*
-----------------------
| Variable_name | Value |
-----------------------
| slow_query_log | OFF |
-----------------------
*/
我们能看到slow_query_logOFF可以把慢查询日志打开注意设置变量值的时候需要使用global否则会报错:
set global slow_query_logON;再来查看下慢查询日志是否开启以及慢查询日志文件的位置
show variables like %slow_query_log%;--------------------------------------------------------------
| Variable_name | Value |
--------------------------------------------------------------
| slow_query_log | ON |
| slow_query_log_file | /var/lib/mysql/centos7-mysql-1-slow.log |
--------------------------------------------------------------
2 rows in set (0.00 sec)能看到这时慢查询分析已经开启同时文件保存在/var/lib/mysql/LSLNO1-slow.log 文件中
2. 修改long_query_time阈值
#测试发现设置global的方式对当前session的long_query_time失效。对新连接的客户端有效。所以可以一并执行下述语句
set global long_query_time 1;
show global variables like %long_query_time%;set long_query_time1;
show variables like %long_query_time%;
/*
----------------------------
| Variable_name | Value |
----------------------------
| long_query_time | 10.000000 |
----------------------------
*/
持久化设置使用set global语句所做的更改在数据库服务器重启之后将不再生效。要使更改持久化你需要讲新的值添加到MySQL的配置文件中my.ini或者my.cnf。
如果不指定存储路径慢查询日志将默认存储到MySQL数据库的数据文件夹下。如果不指定文件名默认文件名为hostname-slow.log
4.2 关闭慢日志查询
关闭慢查询日志通常是为了减少数据库的性能开销因为记录慢查询日志会占用额外的资源。
方式临时性方式
使用SET语句来设置。 1停止MySQL慢查询日志功能具体SQL语句如下
SET GLOBAL slow_query_logoff;SHOW VARIABLES LIKE %slow%;
/*
-----------------------------------------------------------
| Variable_name | Value |
-----------------------------------------------------------
| log_slow_admin_statements | OFF |
| log_slow_extra | OFF |
| log_slow_slave_statements | OFF |
| slow_launch_time | 2 |
| slow_query_log | OFF |
| slow_query_log_file | /var/lib/mysql/LSLNO1-slow.log |
-----------------------------------------------------------
*/
#以及
SHOW VARIABLES LIKE %long_query_time%;
/*
---------------------------
| Variable_name | Value |
---------------------------
| long_query_time | 1.000000 |
---------------------------
*/
使用命令mysqladmin flush-logs 来重新生成查询日志文件具体命令如下执行完毕会在数据目录下重新生成慢查询日志文件
mysqladmin -uroot -p flush-logs slow# 在打开的条件下执行上面的语句才可以看见重置的日志文件
SET GLOBAL slow_query_logon; 提示: 慢查询日志都是使用mysqladmin flush-logs命令来删除重建的。使用时一定要注意一旦执行了这个命令慢查询日志都只存在新的日志文件中如果需要旧的查询日志就必须享先备份。 5. 查看 SQL 执行成本SHOW PROFILE 这个是一个用来显示服务器性能相关统计信息的命令。使用show profile可以查看服务器在执行sql语句时的资源使用情况例如cpu时间块I/O操作上下文切换等。对于性能调优非常有用。 Show Profile是MySQL提供的可以用来分析当前会话中SQL都做了什么、执行的资源消耗情况的工具可用于sql调优的测量。默认情况下处于关闭状态并保存最近15次的运行结果。 show variables like profiling;
/*
----------------------
| Variable_name | Value |
----------------------
| profiling | OFF |
----------------------
*/
通过设profilingON’来开启show profile :
set profiling ON;
show profile使用演示
use atguigudb;select * from student where stuno 343455;
--select * from student where name vyituS;
--show profiles;
列出自从服务器启动以来或从上一次执行reset profiling命令以来所有已经记录查询的性能剖析信息/*
-------------------------------------------------------------------
| Query_ID | Duration | Query |
-------------------------------------------------------------------
| 1 | 0.00174700 | show variables like profiling |
| 2 | 1.52700950 | select * from student where stuno 343455 |
| 3 | 1.20279475 | select * from student where name vyituS |
-------------------------------------------------------------------
3 rows in set, 1 warning (0.00 sec)
*/show profile;
用于显示最近执行的查询性能剖析信息包括了查询周期中的各个阶段所
花费的时间详情例如解析优化执行查询等阶段/*
------------------------------------------
| Status | Duration |
------------------------------------------
| starting | 0.000083 |
| Executing hook on transaction | 0.000004 |
| starting | 0.000009 |
| checking permissions | 0.000006 |
| Opening tables | 0.000044 |
| init | 0.000004 |
| System lock | 0.000008 |
| optimizing | 0.000008 |
| statistics | 0.000019 |
| preparing | 0.000018 |
| executing | 1.202507 |
| end | 0.000024 |
| query end | 0.000005 |
| waiting for handler commit | 0.000010 |
| closing tables | 0.000012 |
| freeing items | 0.000023 |
| cleaning up | 0.000013 |
------------------------------------------
17 rows in set, 1 warning (0.00 sec)*/作用范围 1这个命令只是在本会话内起作用即无法分析本会话外的语句。开启分析功能后所有本会话中的语句都被分析甚至包括执行错误的语句除了SHOW PROFILE和SHOW PROFILES两句本身。2profiling是会话级的当会话结束与之相关的profiling信息也会随之消失。3profiling是针对进程(process)而非线程(threads),因此运行在服务器上的其他服务进程可能会影响分析结果. show profile的常用查询参数: ALL:显示所有的开销信息。BLOCK IO:显示块IO开销。CONTEXT SWITCHES:上下文切换开销。CPU:显示CPU开销信息。IPC:显示发送和接收开销信息MEMORY:显示内存开销信息。PAGE FAULTS:显示页面错误开销信息。SOURCE:显示和Source_functionSource_fileSource_line相关的开销信息。SWAPS:显示交换次数开销信息。 6. 分析查询语句EXPLAIN EXPLAIN是一个常用的sql命令用于获取数据库执行的详细信息它可以帮助开发者了解查询的执行计划包括查询的步骤数据访问路径索引使用情况等这有助于优化查询性能。 有什么用 表的读取顺序数据读取操作的操作类型哪些索引可以使用哪些索引被实际使用表之间的引用每张表有多少行优化器查询 举例
CREATE TABLE employees (id INT AUTO_INCREMENT,first_name VARCHAR(50),last_name VARCHAR(50),email VARCHAR(100),hire_date DATE,PRIMARY KEY (id)
);并且我们想要查询所有在 2000-01-01 后雇佣的员工。我们可以用以下 SQL 查询来实现这个目标SELECT FROM employees WHERE hire_date 2000-01-01; 如果我们想知道这个查询是如何被执行的可以使用 EXPLAIN 命令。下面是 EXPLAIN 的使用方法
EXPLAIN SELECT FROM employees WHERE hire_date 2000-01-01;
假设 employees 表上没有针对 hire_date 的索引那么 EXPLAIN 的输出可能类似于下面这样
-----------------------------------------------------------------------------------------------
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
-----------------------------------------------------------------------------------------------
| 1 | SIMPLE | employees| ALL | NULL | NULL | NULL | NULL | 100000 | 10.00 | Using where |
----------------------------------------------------------------------------------------------- - id: 1 - 查询的选择序列号。- select_type: SIMPLE - 这个查询是最简单的 SELECT 类型。- table: employees - 被查询的表名。- type: ALL - 表明这是一个全表扫描因为没有合适的索引来加速查询。- possible_keys: NULL - 没有可用的索引。- key: NULL - 没有使用任何索引。- key_len: NULL - 因为没有使用索引所以这个值也是 NULL。- ref: NULL - 没有使用索引或者常量来查找行。- rows: 100000 - 预计需要检查的行数假设表中有 100,000 行数据。- filtered: 10.00 - 过滤掉的行百分比这里意味着大约 10% 的行将通过 WHERE 子句的过滤。- Extra: Using where - 表明 WHERE 子句中的条件是在表扫描过程中应用的。 从上面的输出可以看出由于没有针对 hire_date 字段的索引查询需要进行全表扫描这可能会很慢如果表很大。为了优化查询我们可以添加一个索引
ALTER TABLE employees ADD INDEX idx_hire_date (hire_date);再次运行 EXPLAIN 命令这次查询计划可能会变得更高效
EXPLAIN SELECT FROM employees WHERE hire_date 2000-01-01;-----------------------------------------------------------------------------------------------------------
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
-----------------------------------------------------------------------------------------------------------
| 1 | SIMPLE | employees| range | idx_hire_date | idx_hire_date | 5 | NULL | 10000 | 100.00 | Using where; Using index |
-----------------------------------------------------------------------------------------------------------
这次的输出显示 - type: range - 使用了索引范围扫描。- possible_keys: idx_hire_date - 可用的索引。- key: idx_hire_date - 使用的索引。- key_len: 5 - 索引使用的字节数日期类型的索引长度通常是 5 字节。- rows: 10000 - 预计需要检查的行数减少了很多。- Extra: Using where; Using index - 表明 WHERE 子句的条件使用了索引同时也表示查询结果可以直接从索引中获取而不需要访问实际的数据行。 合适的场景使用
EXPLAIN 在多种情况下都非常有用特别是在需要优化查询性能的情况下。以下是几种 EXPLAIN 特别有效的场景 1. 复杂查询 对于包含多个表连接、子查询或联合操作的复杂查询EXPLAIN 可以帮助你理解查询优化器是如何处理这些组件的。你可以查看每个表的连接类型、索引使用情况等从而确定是否有可能进一步优化查询。 2. 性能瓶颈 如果你发现某个查询特别慢使用 EXPLAIN 可以帮助识别问题所在。例如如果 EXPLAIN 显示查询正在执行全表扫描type: ALL那么添加适当的索引可能会显著提高查询速度。 3. 索引评估 当你考虑添加新索引或调整现有索引时EXPLAIN 是一个很好的工具来评估这些变化的效果。通过比较添加索引前后的 EXPLAIN 输出你可以看到索引是否被利用以及查询性能的改进程度。 举例
让我们来看一个具体的例子。假设我们有一个包含两个表的简单数据库orders 和 customers。
CREATE TABLE orders (order_id INT AUTO_INCREMENT,customer_id INT,order_date DATE,PRIMARY KEY (order_id),FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);CREATE TABLE customers (customer_id INT AUTO_INCREMENT,name VARCHAR(50),address VARCHAR(100),PRIMARY KEY (customer_id)
);假设我们需要找出所有在2020年下单的客户的名字和地址。我们可以编写如下的查询
SELECT c.name, c.address
FROM customers AS c
JOIN orders AS o ON c.customer_id o.customer_id
WHERE o.order_date BETWEEN 2020-01-01 AND 2020-12-31;
现在我们使用 EXPLAIN 来查看这个查询的执行计划
EXPLAIN SELECT c.name, c.address
FROM customers AS c
JOIN orders AS o ON c.customer_id o.customer_id
WHERE o.order_date BETWEEN 2020-01-01 AND 2020-12-31;
如果没有针对 order_date 的索引EXPLAIN 输出可能显示全表扫描或低效的连接类型。为了提高性能我们可以为 orders 表添加一个索引
ALTER TABLE orders ADD INDEX idx_order_date (order_date);
6.1 EXPLAIN的进一步使用
这里谈谈EXPLAIN的输出格式。EXPLAIN可以输出四种格式 传统格式 JSON格式 TREE格式 以及可视化输出 。用户可以根据需要选择适用于自己的格式
1.传统格式 传统格式Text Format是 EXPLAIN 命令最常用的输出格式它以文本形式显示查询计划。这种格式提供了查询中各个步骤的简要描述并且通常包含有关操作类型、关联的表、使用的索引等信息。 示例
假设有一个简单的 SQL 查询
SELECT FROM employees WHERE department engineering;
我们可以使用 EXPLAIN 命令查看其执行计划
EXPLAIN SELECT FROM employees WHERE department engineering;
假定数据库返回的输出类似于下面的内容
------------------------------------------------------------------------------------------------------------
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
------------------------------------------------------------------------------------------------------------
| 1 | SIMPLE | employees| range | department | department | 100 | NULL | 1000 | 10.00 | Using where |
------------------------------------------------------------------------------------------------------------ 解释 - id: 1 - 这个查询块是主查询。- select_type: SIMPLE - 这是一个简单的查询。- table: employees - 查询涉及的表。- type: range - 数据库将使用索引进行范围查找。- possible_keys: department - 可能使用的索引是 department。- key: department - 实际上使用了 department 索引。- key_len: 100 - 使用的索引键的最大长度为 100 字节。- ref: NULL - 没有使用引用。- rows: 1000 - 数据库估计需要检查 1000 行。- filtered: 10.00 - 大约只有 10% 的行满足条件。- Extra: Using where - 查询使用了 WHERE 子句来过滤数据。 2.JSON格式 JSON 格式是一种结构化的数据格式常用于网络传输和配置文件中。当使用 EXPLAIN 命令时JSON 格式可以提供一种易于解析的数据结构方便开发人员和自动化工具进一步处理查询计划信息。 JSON 格式的结构
JSON 格式的查询计划通常包含以下主要部分 - query_block: 主查询块的信息包括选择类型、涉及的表及其访问类型等。- table: 关联表的信息如表名、索引使用情况等。- nested loop: 对于涉及子查询的情况可能包含嵌套循环的信息。 示例
假设我们有如下 SQL 查询
SELECT FROM employees WHERE department engineering;
我们可以使用 EXPLAIN 命令并指定 JSON 格式来查看其执行计划
EXPLAIN FORMATJSON SELECT FROM employees WHERE department engineering; 输出示例
下面是该查询的一个可能的 JSON 格式输出
{query_block: {select_id: 1,table: {table_name: employees,access_type: range,possible_keys: [department],key: department,key_len: 100,ref: null,rows_examined: 1000,rows_produced_per_join: 100,filtered: 10.00,Extra: Using where}}
} 解释 - query_block: 主查询块的信息。 - select_id: 1 - 查询块的 ID。 - table: 关联表的信息。 - table_name: employees - 查询涉及的表。 - access_type: range - 数据库将使用索引进行范围查找。 - possible_keys: [department] - 可能使用的索引列表。 - key: department - 实际使用的索引。 - key_len: 100 - 使用的索引键的最大长度。 - ref: null - 没有使用引用。 - rows_examined: 1000 - 数据库估计需要检查的行数。 - rows_produced_per_join: 100 - 每次连接产生的行数。 - filtered: 10.00 - 大约只有 10% 的行满足条件。 - Extra: Using where - 查询使用了 WHERE 子句来过滤数据 3.Tree格式 TREE 格式是 EXPLAIN 命令的一种输出格式它以树状结构来表示查询计划。这种格式对于理解复杂的嵌套查询特别有用因为它能够清晰地展示查询的层次结构。 TREE 格式的结构
TREE 格式的输出通常包含以下元素 - 操作节点: 每个操作节点都代表查询计划中的一个操作如表扫描、索引扫描、嵌套循环连接等。 - 属性: 每个操作节点下面列出与该操作相关的属性如表名、索引、连接条件等。 - 子节点: 复杂的操作可能包含子节点例如嵌套循环连接中的内部和外部表。 SELECT e.name, d.department_name
FROM employees e
JOIN departments d ON e.department_id d.department_id
WHERE e.salary 50000; 我们可以使用 EXPLAIN 命令并指定 TREE 格式来查看其执行计划
EXPLAIN (FORMAT TREE) SELECT e.name, d.department_name
FROM employees e
JOIN departments d ON e.department_id d.department_id
WHERE e.salary 50000;
输出
QUERY PLAN
└─ Nested Loop (cost0.00..10000.00 rows100 width100)├─ Index Scan using idx_salary on employees e (cost0.00..5000.00 rows100 width100)│ ├─ Filter: (e.salary 50000)└─ Index Scan using pk_departments on departments d (cost0.00..100.00 rows1 width100)├─ Index Cond: (d.department_id e.department_id) - QUERY PLAN: 查询计划的根节点。 - Nested Loop: 嵌套循环连接操作表示将两个表连接在一起。 - Index Scan using idx_salary on employees e: 索引扫描操作使用名为 idx_salary 的索引来访问 employees 表。 - Filter: 过滤条件 (e.salary 50000)。 - Index Scan using pk_departments on departments d: 索引扫描操作使用名为 pk_departments 的索引来访问 departments 表。 - Index Cond: 索引条件 (d.department_id e.department_id)。