[遅い SQL パフォーマンスの最適化] SQL のライフサイクル


1. MySQLにおける単純なSQLの実行処理

MySQL アーキテクチャのコンポーネントとコンポーネント間の関係を簡単な図で示します。次に、SQL ステートメントを使用してそれを分析します。

たとえば、次の SQL ステートメント

  
  
  
  
  
SELECT department_id FROM employee WHERE name = 'Lucy' AND age > 18 GROUP BY department_id

名前を指標に時系列に分析してみましょう。

1. クライアント: MySQL コマンド ライン ツール、Navicat、DBeaver、または SQL クエリを MySQL サーバーに送信するその他のアプリケーションなど。

2. コネクタ: クライアントとの接続の確立、接続の管理、および接続の維持を担当します。クライアントがMySQLサーバーに接続すると、コネクタはクライアントのユーザー名とパスワードを検証し、クライアントのリクエストを処理するためのスレッドを割り当てます。

3. クエリ キャッシュ : クエリ キャッシュは、以前に実行されたクエリとその結果をキャッシュするために使用されます。新しいクエリ リクエストを受信すると、MySQL はまず同じクエリとその結果がクエリ キャッシュにすでに存在するかどうかを確認します。クエリ キャッシュに一致するクエリ結果がある場合、MySQL はクエリを再度実行せずに、キャッシュされた結果を直接返します。ただし、クエリ キャッシュに一致するクエリ結果がない場合、MySQL はクエリの実行を継続します。
4.アナライザー :
  • クエリ ステートメントを解析し、構文を確認します。
  • テーブル名と列名が正しいことを確認してください。
  • クエリツリーを生成します。
5.オプティマイザー : クエリ ツリーを分析し、さまざまな実行計画を検討し、さまざまな実行計画のコストを見積もり、最適な実行計画を選択します。この例では、名前がインデックス列であるため、オプティマイザはクエリに名前インデックスを使用することを選択できます。
6. Executor : オプティマイザによって選択された実行プランに従って、条件を満たすデータ行を取得するリクエストをストレージ エンジンに送信します。
7. ストレージ エンジン ( InnoDBなど) :
  • 従業員テーブルの名前インデックスに対して同等のクエリを実行するなど、実際のインデックス スキャンの実行を担当します。すべての列をクエリするには、テーブルに戻ってディスクにアクセスする必要があります。
  • ディスクにアクセスする前に、まず必要なデータ ページが InnoDB バッファ プール (バッファ プール) に存在するかどうかを確認します。バッファー プール内に修飾されたデータ ページがある場合、キャッシュされたデータが直接使用されます。必要なデータ ページがバッファ プールにない場合は、データ ページをディスクからバッファ プールにロードします。
8.アクチュエーター:
  • 見つかったレコードごとに、そのレコードがインデクス条件名を満たすかどうかを再判定します。これは、インデックス条件に基づいてメモリにロードされたデータ ページには、インデックス条件を満たしていないレコードも含まれている可能性があるため、名前条件を再度判定する必要があり、名前条件が満たされている場合、年齢 > 18 のフィルター条件が適用されます。裁かれ続けます。
  • 条件を満たすレコードを、Department_id に基づいてグループ化します。
  • エグゼキュータは、処理された結果セットをクライアントに返します。
クエリの実行全体を通じて、これらのコンポーネントは連携してクエリを効率的に実行します。クライアントはクエリの送信を担当し、コネクタはクライアント接続を管理し、クエリ キャッシュは以前のクエリ結果の再利用を試み、パーサーはクエリの解析を担当し、オプティマイザは最適な実行プランを選択し、エグゼキュータは選択されたプランを実行します。オプティマイザー、ストレージ エンジン (InnoDB など) データのストレージとアクセスの管理を担当します。これらのコンポーネントの相乗効果により、MySQL は効率的にクエリを実行し、結果セットを返すことができます。
インデックス列フィルター条件に従ってインデックス データ ページをメモリにロードする操作は、ストレージ エンジンによって実行されます。メモリにロードされた後、エグゼキュータはインデックス列と非インデックス列のフィルター条件を判断します。

2. SQLキーワードの実行順序を問い合わせる

実行シーケンスは次のとおりです。
1. ストレージエンジンの動作
(1) FROM: SQL のクエリに使用されるデータ テーブル。エグゼキュータは、オプティマイザが選択した実行プランに従って、ストレージ エンジンから関連テーブルのデータを取得します。
(2) ON: JOIN と組み合わせて接続条件を指定します。executor は、ON で指定された条件に従って、ストレージ エンジンから条件に一致するレコードを取得します。結合条件にインデックス付き列が含まれる場合、ストレージ エンジンは最適化のためにインデックスを使用します。
(3) JOIN: テーブル間の接続方法(INNER JOIN、LEFT JOIN など)を指定します。エグゼキュータは、オプティマイザによって選択された実行プランに従って、ストレージ エンジンから接続テーブル データを取得します。次に、エグゼキュータは JOIN 接続タイプと ON 接続条件に基づいてデータ接続を処理します。
(4) WHERE: エグゼキュータはストレージ エンジンから返されたデータをフィルタリングし、WHERE 句の条件を満たすレコードのみを保持します。フィルター条件にインデックスがある場合、ストレージ エンジン層はインデックスを介してフィルター条件をフィルターし、それを返します。
2. 返された結果セットに対する操作
(5) GROUP BY: 実行プログラムは、GROUP BY で指定された列に従って、WHERE 条件を満たすレコードをグループ化します。
(6) HAVING: グループ化の実行後、実行プログラムはグループ化されたレコードを HAVING 条件に従って再度フィルタリングします。
(7) SELECT: エグゼキューターは、オプティマイザーによって選択された実行計画と指定された列に基づいてクエリ結果を取得します。
(8) DISTINCT: エグゼキューターはクエリ結果の重複を排除し、一意のレコードのみを返します。
(9) ORDER BY: 実行プログラムは、ORDER BY 句で指定された列に従ってクエリ結果をソートします。
(10) LIMIT: エグゼキュータは、LIMIT 句で指定された制限に従ってクエリ結果を切り捨て、レコードの一部のみを返します。
3. MySQLにおけるテーブル関連付けクエリSQLの実行処理
  
  
  
  
  
SELECT s.id, s.name, s.age, es.subject, es.score FROM employee s JOIN employee_score es ON s.id = es.employee_id WHERE s.age >18 AND es.subject_id =3 AND es.score >80;
この例では、subject_id とscore が結合インデックスであり、age がインデックスです。時系列で分析してみましょう
1. コネクタ : クライアントが MySQL サーバーに接続するとき、コネクタは接続の確立と管理を担当します。 クライアントから提供されたユーザー名とパスワードを検証し、クライアントに適切な権限があるかどうかを判断して、接続を確立します。
2. クエリ キャッシュ : MySQL サーバーはクエリを処理する前にクエリ キャッシュをチェックします。 結果セットがクエリ キャッシュにすでに存在する場合、サーバーはキャッシュ内の結果を直接返します。
3. パーサー : SQL構文を 解析して 正確さをチェックします。パーサーはクエリ ステートメントをテーブル、列、条件などの構成要素に分割します。この例では、パーサーは、関連するテーブル ( employeeおよびemployee_score ) と必要な列 ( id、name、age、subject、score ) を識別します。
4.优化器 根据解析器提供的信息生成执行计划。 优化器会分析多种可能的执行策略,并选择成本最低的策略。 在这个示例中,优化器会选择 age 索引和 subject_id score 的联合索引。 对于连接操作,优化器还要决定连接策略,例如是否使用 Nested-Loop Join Hash Join 等一些连接策略。 优化器还会根据表的大小、索引、查询条件和统计信息来决定哪张表作为驱动表,以及选择最佳的连接策略。 例如,如果两个表的大小差异很大, Nested-Loop Join 可能是一个好的选择,而对于大小相似的两个表, Hash Join Sort-Merge Join 可能更加高效。
5.执行器 根据优化器生成的执行计划执行查询,向存储引擎发送请求,获取满足条件的数据行。
6.存储引擎(如InnoDB 管理数据存储和检索。 存储引擎首先接收来自执行器的请求,该请求可能是基于优化器的执行计划。
  • 存储引擎首先接收来自执行器的请求。请求可能包括获取满足查询条件的数据行,以及使用哪种扫描方法(如全表扫描或索引扫描)。
  • 假设执行器已经决定使用索引扫描。在这个示例中,存储引擎可能会先对employee表进行索引扫描(使用age索引),然后对employee_score表进行索引扫描(使用subject_id和score的联合索引)。
  • 存储引擎会根据请求查询相应的索引。在employee索引中会找到满足age > 18条件的记录。在employee_score索引中找到满足subject_id = 3 AND score > 80条件的记录。
  • 一旦找到了满足条件的记录,存储引擎需要将这些记录所在的数据页从磁盘加载到内存中。存储引擎首先检查缓冲池(InnoDB Buffer Pool),看这些数据页是否已经存在于内存中。如果已经存在,则无需再次从磁盘加载。如果不存在,存储引擎会将这些数据页从磁盘加载到缓冲池中。
  • 加载到缓冲池中的记录可以被多个查询共享,这有助于提高查询效率。
7.执行器 :处理连接、排序、聚合、过滤等操作。
  • 在内存中执行连接操作,将employee表和employee_score表的数据行连接起来。
  • 对连接后的结果集进行过滤,只保留满足查询条件(age > 18、subject_id = 3、score > 80)的数据行。
  • 将过滤后的数据行作为查询结果返回给客户端。

前面说过,根据存储引擎根据索引条件加载到内存的数据页有多数据,可能有不满足索引条件的数据,如果执行器不再次进行索引条件判断, 则无法判断哪些记录满足索引条件的,虽然在存储引擎判断过了,但是在执行器还是会有索引条件age > 18、subject_id = 3、score > 80的判断。

我们再以 全局视野 来分析 一下
1.确定驱动表 : 首先, MySQL 优化器会选择一个表作为"驱动表"。 通常,返回记录数较少的表会被选为驱动表。 假设 employee_score 表中满足 subject_id = 3 AND score > 80 条件的记录数量较少,那么这张表可能被选为驱动表。 这是优化器的工作,它预估哪个表作为驱动表更为高效,制定执行计划。 虽然驱动表的选择很大程度上是基于预估的返回记录数,但实际选择还会受其他因素影响,例如表之间的连接类型、可用的索引等。
2.使用驱动表的索引进行筛选 : 优化器会首先对驱动表进行筛选。 如果 employee_score 是驱动表,优化器会使用 subject_id score 的联合索引来筛选出 subject_id = 3 AND score > 80 的记录。 这是执行器按照优化器的计划向存储引擎发出请求,获取需要的数据。 存储引擎负责访问索引,并根据索引定位到实际的数据页,从而获取数据行。
3.连接操作 : 执行器会基于上一步从驱动表中筛选出的记录对另一个表(即 employee 表)进行连接。 这时,执行器会使用 employee 表上的索引(如 id 索引)来高效地找到匹配的记录。
4.一步的筛选 : 在连接的过程中,执行器会考虑 employee 表的其他筛选条件,如 age > 18 ,通常连接后才过滤筛选,这也是执行器的工作,执行器在连接过程中或之后,根据优化器制定的计划进一步筛选结果集。 但是这里 employee 表的 age 索引其叶子节点包含 age 和主键 id 信息,在进行连接时,可以直接按照 age 范围扫描该索引,利用其叶子节点中的 id 信息进行高效的 JOIN 操作,因此在连接时就完成筛选,这个过程由 MySQL 优化器自动完成。 从上面可以看到,当存在可以被利用的索引时, MySQL 可以在连接过程中执行这些过滤操作。
5.返回结果 : 这是执行器最后的步骤,返回最终的查询结果。
四、总结
本文采用一张简单的架构图说明了MySQL查询中使用的组件和组件间关系。
解析了一条sql语句从客户端请求mysql服务器到返回给客户端的整个生命周期流程。
列举了单表sql、关联表sql 两种不同SQL在整个生命周期中的执行顺序以及内部组件逻辑关系。
通过如上案例的解析可以让开发者们掌握到单表sql、关联表sql的底层sql知识,为理解慢sql的产生和优化鉴定基础。
-end-

本文分享自微信公众号 - 京东云开发者(JDT_Developers)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

阿里云严重故障,全线产品受影响(已恢复) 俄罗斯操作系统 Aurora OS 5.0 全新 UI 亮相 汤不热 (Tumblr) 凉了 多家互联网公司急招鸿蒙程序员 .NET 8 正式 GA,最新 LTS 版本 UNIX 时间即将进入 17 亿纪元(已进入) 小米官宣 Xiaomi Vela 全面开源,底层内核为 NuttX Linux 上的 .NET 8 独立体积减少 50% FFmpeg 6.1 "Heaviside" 发布 微软推出全新“Windows App”
{{o.name}}
{{m.name}}

おすすめ

転載: my.oschina.net/u/4090830/blog/10143833