MySQL 学習ノート (19) - MySQL トランザクション ログ

1 はじめに

  • トランザクションには、原子性、一貫性、分離性、耐久性の 4 つの特性があります。では、トランザクションの 4 つの特徴はどのようなメカニズムに基づいているのでしょうか。
  • ==トランザクションの分離は、ロック機構==によって実現されます
  • ==トランザクションの原子性、一貫性、耐久性は、トランザクションの再実行ログと元に戻すログ== によって保証されます。
    • REDO LOG はREDO ログと呼ばれ、書き換え操作を提供し、コミットされたトランザクションによって変更されたページ操作を復元します。トランザクションの耐久性を確保するために使用されます
    • UNDO LOG はロールバック ログと呼ばれ、特定のバージョンにロールバック行が記録されます。トランザクションの原子性と一貫性を確保するために使用されます
  • 一部の DBA は、UNDO は REDO の逆のプロセスだと考えるかもしれませんが、そうではありません。REDO と UNDO はどちらも回復操作と見なすことができますが、
    • redo log :ストレージ エンジン層 (innodb) によって生成されるログで、 「物理レベル」でのページ変更操作を 記録します。
      • たとえば、ページ番号 xxx とオフセット yyy には「zzz」データが書き込まれています。主にデータの信頼性を確保するため。
    • undo log: ストレージエンジン層(innodb)が生成するログで論理的な操作ログを記録したものです
      • たとえば、データ行に対して INSERT ステートメント操作が実行された場合、元に戻すログには、それと反対の DELETE 操作が記録されます。
      • これは主に、トランザクションのロールバック (元に戻すログが各変更操作の逆の操作を記録する) と一貫性のある非ロック読み取り(元に戻すログのロールバック行が特定のバージョン (MVCC、つまり複数バージョンの同時実行制御) に記録される) に使用されます。

2.やり直しログ

InnoDB ストレージエンジンは、ストレージ スペースをページ単位で管理します。ページに実際にアクセスする前に、ディスク上のページをメモリ内のバッファ プールにキャッシュしてからアクセスできるようにする必要があります。すべての変更は、最初にバッファプール必要があります。次に、バッファ プール内のダーティ ページが特定の頻度でディスクにフラッシュされます (チェックポイント メカニズム)。バッファ プールは、CPU と CPU の間のギャップを最適化するために使用されます。これにより、全体的なパフォーマンスが急激に低下しないようにすることができます。

REDO ログが必要な理由

データベース管理システムでは、REDO ログはデータの永続性を確保するために使用されるメカニズムですデータベースが書き込み操作を実行すると、これらの操作の詳細な情報が REDO ログに記録され、システム クラッシュなどの異常な状態が発生した場合でも、データベースは復旧時にこれらの操作を正しく復元できるようになります。

具体的には、データベースが書き込み操作を実行する場合、まずこれらの操作を REDO ログに書き込み、次にディスクに書き込みますこれは、ディスクへの書き込み前にシステム クラッシュが発生した場合でも、データベースは REDO ログの情報を使用して書き込み操作を再開できることを意味します。

さらに、REDO ログを使用して、トランザクションの耐久性と一貫性を実現することもできます。トランザクションがコミットされる前に、すべての変更操作が REDO ログに記録され、ディスクに書き込まれます。トランザクションがコミットされる前にシステム クラッシュなどの異常な状況が発生した場合、データベースは REDO ログを使用してコミットされていないトランザクションを復元し、データの一貫性と整合性を確保できます。

したがって、REDO ログはデータベースのセキュリティと信頼性にとって非常に重要であり、データベース システムの不可欠な部分です。

REDO ログの利点と特徴

利点

  • REDO ログはフラッシュの頻度を減らします
  • REDO ログはほとんどスペースを占有しません

テーブルスペース ID、ページ番号、オフセット、および更新が必要な値を格納するために必要なストレージ領域は非常に小さく、ディスクはすぐに更新されます。

特徴

  • REDO ログは順次ディスクに書き込まれます
    • トランザクションを実行するプロセスでは、ステートメントが実行されるたびに、いくつかの REDO ログが生成される場合があります.これらのログは、生成された順序でディスクに書き込まれます.つまり、シーケンシャル IO が使用され、効率はより高速です.ランダムIO。
  • トランザクションの実行中、REDO ログは記録を続けます
    • redo ログと bin ログの違いは、redo ログはストレージ エンジン層によって生成されるのに対し、bin ログはデータベース層によって生成されることです
    • トランザクションが 100,000 行のレコードをテーブルに挿入すると仮定します. このプロセスの間、REDO ログに連続して記録されますが、bin ログには記録されません. トランザクションがコミットされるまで、bin ログ ファイルには書き込まれません. . .

やり直しの構成

REDO ログは、単純に次の 2 つの部分に分けることができます。

  • メモリに格納されているREDO ログ バッファ (REDO ログ バッファ) は揮発性です。

    • パラメータ設定: innodb_log_buffer_size:

      • REDO ログのバッファ サイズ。デフォルトは 16M、最大値は 4096M、最小値は 1M です。
      mysql> show variables like '%innodb_log_buffer_size%';
      +------------------------+----------+
      | Variable_name | Value |
      +------------------------+----------+
      | innodb_log_buffer_size | 16777216 |
      +------------------------+----------+
      
  • ハードディスクに保存されたREDO ログ ファイル (REDO ログ ファイル) は永続的です。

REDO ログ ファイルを図に示します。ib_logfile0ib_logfile1REDO ログです。

画像-20230324202944695

やり直しの全体の流れ

更新トランザクションを例にとると、REDO ログ フロー プロセスを次の図に示します。

画像-20230324203215616

  • ステップ 1: 最初に元のデータをディスクからメモリに読み込み、データのメモリ コピーを変更します。
  • ステップ 2: REDO ログを生成し、データの変更された値を記録する REDO ログ バッファに書き込みます。
  • ステップ 3: トランザクションがコミットされたら、REDO ログ バッファの内容を REDO ログ ファイルに更新し、REDO ログ ファイルに追加で書き込みます。
  • ステップ 4: メモリ内の変更されたデータをディスクに定期的に更新する

REDO ログのフラッシュ戦略

Redo ログの書き込みは、ディスクに直接書き込まれるわけではありません. InnoDB エンジンは、redo ログを書き込むときに最初に redo ログ バッファーを書き込み次に特定の頻度で実際の redo ログ ファイルにフラッシュします.

画像-20230324205058590

REDO ログ バッファを REDO ログ ファイルにフラッシュするプロセスは、実際にはディスクにフラッシュするわけではないことに注意してください。ファイル システム キャッシュ (ページ キャッシュ) にフラッシュするだけです。(これは、ファイルの書き込み効率を向上させるために最新のオペレーティング システムによって行われた最適化です)。実際の書き込みは、システムが勝手に決める(たとえば、ページ キャッシュは十分な大きさです)次に、InnoDB の問題で、同期のためにシステムに引き渡された場合、システムがダウンした場合、データも失われます (システム全体がダウンする可能性はまだ比較的小さいですが)。

この状況に対応して、InnoDB はinnodb_flush_log_at_trx_commitパラメータを提供します。これは、コミットがトランザクションをコミットするときに、 REDO ログ バッファ内のログをREDO ログ ファイルにフラッシュする方法を制御します次の 3 つの戦略をサポートしています。

  • 0: **を意味しますトランザクションがコミットされると、REDO ログはディスクにフラッシュされませんが、フラッシュされていない REDO ログは毎秒バッチでディスクにフラッシュされます。**。これは ==最もパフォーマンスの高いフラッシュ戦略== ですが、MySQL プロセスがクラッシュしたり、マシンの電源が落ちたりすると、1 秒間のデータが失われる可能性があります。

    画像-20230324205703774

  • 1 (デフォルト):特急**トランザクションがコミットされるたびに REDO ログをディスクに同期的に書き込む. これは最も安全な更新戦略** ですが、各トランザクションのコミットを待機するときに Redo ログをディスクに書き込む必要があるため、パフォーマンスが低下します。

    画像-20230324205723417

  • 2: ** を意味しますトランザクションがコミットされるたびにシステム キャッシュに REDO ログを書き込む(ディスクではありません)、システムはキャッシュ内の REDO ログを毎秒バッチでディスクにフラッシュします**。これは、パフォーマンスとセキュリティのバランスです.これにより、REDO ログが少なくとも毎秒ディスクにフラッシュされ、同時に、各トランザクションがコミットされるときにディスクへの書き込みによるパフォーマンスの低下が回避されます。

    画像-20230324205812517

注: バックグラウンド スレッドは、1 秒ごとに REDO ログ バッファの内容をページ キャッシュに書き込み、次に fsync を呼び出してディスクをフラッシュします。

3.取り消しログ

REDO ログはトランザクションの永続性を保証し、UNDO ログはトランザクションの原子性を保証します. トランザクションでデータを更新する前の操作は、実際には最初にundo ログを書き込むことです

元に戻すログを理解する方法

トランザクションは原子性。つまり、トランザクション内の操作が完了するか、何も行われないかのいずれかです。ただし、トランザクションの実行中に次のような状況が発生することがあります。

  • 状況 1: トランザクションの実行中に、サーバー、オペレーティング システムのエラー、または突然の電源障害によるエラーなど、さまざまなエラーが発生する可能性があります。

  • ケース 2: プログラマーは、トランザクションの実行中にROLLBACKステートメントを手動で入力して、現在のトランザクションの実行を終了できます。

上記の状況が発生した場合、データを元の状態に戻す必要があります.このプロセスは呼び出されますロールバック、誤った印象を与える可能性があります。このトランザクションは何もしていないように見えるため、原子性の要件を満たしています。

レコードに変更を加えたいときはいつでも (ここでの変更はINSERT、DELETE、UPDATEを参照できます)、ロールバックに必要なものを書き留める必要があります。たとえば:

  • レコードを挿入するときは、少なくともこのレコードの主キーの値を書き留めておき、ロールバックするときは、主キーの値に対応するレコードを削除するだけで済みます。( INSERT ごとに、 InnoDB ストレージ エンジンは DELETE を完了します)
  • レコードを削除する場合は、少なくともこのレコードの内容を書き留めておく必要があります。これにより、後でロールバックしたときに、これらの内容で構成されるレコードをテーブルに挿入できます。( DELETE ごとに、 InnoDB ストレージ エンジンは INSERT を実行します)
  • レコードを変更する場合は、後でロールバックしたときにこのレコードを古い値に更新できるように、少なくともこのレコードを変更する前に古い値を記録する必要があります。( UPDATE ごとに、 InnoDB ストレージ エンジンは逆の UPDATE を実行し、行を変更前に戻します)

MysQL は、ロールバック用に記録されたこれらのコンテンツを呼び出します **元に戻すログまたはロールバック ログ (例: 元に戻すログ)**。

クエリ操作 ( SELECT ) はユーザー レコードを変更しないため、クエリ操作の実行時に対応する取り消しログを記録する必要はありません

さらに、元に戻すログは再実行ログを生成します。つまり、元に戻すログの生成には、再実行ログの生成が伴います。元に戻すログも永続的な保護が必要

undo ログの役割

  • 機能 1:ロールバック データ
    • undo は論理ログであるため、データベースを配置するだけです論理的に元に戻す. すべての変更は論理的に元に戻されますが、データ構造とページ自体はロールバック後に大きく異なる場合があります。ステートメントまたはトランザクションが実行される前の状態にデータベースを物理的に復元しません。
  • 機能 2:MVCC
    • 元に戻すのもう 1 つの役割は MVCC です。つまり、InnoDB ストレージ エンジンでの MVCC の実装は元に戻すことによって行われます。ユーザーがレコードの行を読み取るときに、そのレコードが他のトランザクションによって既に占有されている場合、現在のトランザクションは元に戻すことで前の行のバージョン情報を読み取って、非ロック読み取りを実現できます。

ストレージ構造を元に戻す

ロールバック セグメントと元に戻すページ

InnoDB はセグメント化されたアプローチを使用してログ管理を元に戻します、これはロールバック セグメント (ロールバック セグメント)です。各ロールバック セグメントは1024 個の取り消しログ セグメントを記録し、各取り消しログ セグメントの取り消しページに適用されます。元に戻すページは、トランザクション処理中に変更されたデータを格納するために使用されるメモリ キャッシュ領域です

  • InnoDB1.1以前のバージョン(1.1バージョンを除く)は、ロールバックセグメントが1つしかないため、同時にサポートされるトランザクションの制限は1024です。ほとんどのアプリケーションでは十分ですが。
  • バージョン 1.1 以降、 InnoDB は最大 128 のロールバック セグメントをサポートするため、同時オンライン トランザクションの制限は128*1024に増加します。
mysql> show variables like 'innodb_undo_logs '; # 可以看出 value 为 128

InnoDB1.1 は 128 のロールバック セグメントをサポートしますが、これらのロールバック セグメントはすべて共有テーブルスペース ibdata に格納されます。InnoDB1.2 以降では、ロールバック セグメントをパラメータでさらに設定できます。これらのパラメータは次のとおりです。

  • innodb_undo_directory : ロールバック セグメント ファイルが配置されているパスを設定します。これは、ロールバック セグメントを共有テーブルスペース以外の場所に格納できる、つまり、独立したテーブルスペースとして設定できることを意味します。このパラメーターのデフォルト値は「.times」で、現在の InnoDB ストレージ エンジンのディレクトリを示します。
  • Innodb_undo_logs : ロールバック セグメントの数を設定します。デフォルト値は 128 です。InnpDB1.2 バージョンでは、このパラメーターは、以前のバージョンのパラメーター innodb_rollback_segments を置き換えるために使用されます。
  • innodb_undo_tablespaces : ロールバック セグメントを構成するファイルの数を設定して、ロールバック セグメントを複数のファイル間でより均等に分散できるようにします。このパラメータを設定すると、パス innodb_undo_directory に undo というプレフィックスが付いたファイルが表示されます。これは、ロールバック セグメント ファイルを表します。

通常、元に戻すログ関連のパラメーターはめったに変更されません

元に戻すページの再利用

トランザクションを開始し、元に戻すログを書き込む必要がある場合、最初に元に戻すログ セグメントに移動して空き位置を見つけ、空きがある場合に元に戻すページを申請し、適用された元に戻すページの書き込みに対して元に戻すログを実行します。mysql のページのデフォルト サイズは 16k です。

アプリケーションの TPS (1 秒あたりに処理されるトランザクションの数) が 1000 であると仮定すると、トランザクションごとにページを割り当てるのは非常に無駄です (トランザクションが非常に長い場合を除きます)。 1 分間に 1G のストレージが必要です。これが続くと、MySQL が非常に入念にクリーンアップしない限り、時間の経過とともにディスク領域が急速に拡大し、多くの領域が無駄になります。

そのため、元に戻すページは再利用できるように設計されており、トランザクションがコミットされても、元に戻すページはすぐには削除されません。再利用のため、この元に戻すページは他のトランザクションの元に戻すログと混在する場合があります。undo ログがコミットされた後、リンクされたリストに入れられundo ページの使用済みスペースが 3/4 未満であるかどうかが判断されます.3/4 未満の場合は、現在の undo ページが再利用できるため、使用されません リサイクル、他のトランザクションの取り消しログは、現在の取り消しページの後ろに記録できます。undo ログは個別であるため、対応するディスク領域をクリーンアップするのは効率的ではありません。

ロールバック セグメントとトランザクション

  1. 各トランザクションは 1 つのロールバック セグメントのみを使用し、1 つのロールバック セグメントが同時に複数のトランザクションを処理する場合があります。

  2. トランザクションが開始されると、ロールバック セグメントが作成されます。トランザクション中にデータが変更されると、元のデータがロールバック セグメントにコピーされます。

  3. ロールバック セグメントでは、トランザクションは、トランザクションが終了するか、すべての領域が使い果たされるまで、エクステントを満たし続けます。現在のエクステントが十分でない場合、トランザクションはセグメント内の次のエクステントの拡張を要求します。割り当てられたすべてのエクステントが使い果たされると、トランザクションは元のエクステントを上書きするか、ロールバック セグメントで許可されている場合は新しいエクステントを拡張します。使用するパネル。

  4. ロールバック セグメントが UNDO テーブルスペースに存在します。データベースには複数の UNDO テーブルスペースが存在できますが、一度に使用できる UNDO テーブルスペースは 1 つだけです。

mysql> show variables like 'innodb_undo_tablespaces ' ;

#undo log的数量,最少为2,undo log的truncate操作有purge协调线程发起。在truncate某个undo log表空间的过程中,保证有一个可用的undo log可用。
  1. トランザクションがコミットされると、 InnoDB ストレージ エンジンは次の 2 つのことを行います。
    • 後で消去操作を行うために、元に戻すログをリストに入れます
      • パージ操作は実際には MySQL の MVCC をサポートするためのものです。そのため、トランザクションがコミットされたときにレコードをすぐに処理することはできません。他のトランザクションがこの行を使用している可能性があるため、InnoDB ストレージ エンジンは以前のバージョンのレコードを保存する必要があります。

      • 行レコードが他のトランザクションによって参照されなくなった場合、実際の削除操作を実行できるため、パージ操作は現在のトランザクションを処理するための削除操作ではなく、以前の削除と更新をクリアする操作であることが理解できます。実行された操作は削除のみで、前の行に記録されたバージョンはクリーンアップされます

    • 次のトランザクションに割り当てることができる場合、元に戻すログが配置されているページを再利用できるかどうかを判断します

ロールバック セグメントのデータ分類

  1. コミットされていないロールバック データ (コミットされていない元に戻す情報) : このデータに関連付けられているトランザクションはコミットされていません。これは、読み取りの一貫性を実現するために使用されます。したがって、このデータは他のトランザクションからのデータで上書きできません。

  2. コミットされたが有効期限が切れていないコミットされた元に戻す情報: このデータに関連付けられたトランザクションはコミットされていますが、元に戻す保持パラメーターの保持時間の影響を受けています。

  3. トランザクションが送信され、有効期限が切れたデータ (期限切れの取り消し情報) : トランザクションが送信され、トランザクションの保存時間が期限切れデータに属する取り消し保持パラメーターで指定された時間を超えました。ロールバック セグメントがいっぱいになると、「トランザクションがコミットされ、期限切れになったデータ」が優先的に上書きされます。

トランザクションがコミットされた後、元に戻すログと元に戻すログが配置されているページをすぐに削除することはできません。これは、元に戻すログを介して行レコードの以前のバージョンを取得する必要がある他のトランザクションが存在する可能性があるためです。トランザクションの送信時に、undo ログをリンク リストに入れます。undo ログが最終的に削除できるかどうか、および undo ログが配置されているページは、purge スレッドによって判断されます。

元に戻すタイプ

  • 元に戻すログを挿入
    • 挿入取り消しログは、挿入操作中に生成された取り消しログを参照します。挿入操作の記録は、トランザクション自体にのみ表示され、他のトランザクションには表示されないため (これはトランザクション分離の要件です)、トランザクションがコミットされた直後に取り消しログを削除できます。パージ操作は必要ありません
  • アンドゥログを更新
    • 更新取り消しログには、削除および更新操作で生成された取り消しログが記録されます。undoログは MVCC メカニズムを提供する必要がある場合があるため、トランザクションがコミットされたときに削除できません送信時に元に戻すログのリンクされたリストに入れ、パージ スレッドが最終的な削除を実行するのを待ちます

undo ログのライフサイクル

簡単な生成プロセス

以下は、undo+redo トランザクションの単純化されたプロセスです.
A=1 と B=2 の 2 つの値があると仮定し、A を 3 に、B を 4 に変更します。

1. start transaction ;
2.记录 A=1到undo log;
3. update A = 3;
4.记录A=3 到redo log;
5.记录B=2到undo log;
6. update B = 4;
7.记录B =4到redo log;
8.将redo log刷新到磁盘;
9. commit
  • 手順 1 ~ 8 のいずれかでシステムがダウンし、トランザクションがコミットされていない場合、トランザクションはディスク上のデータに影響を与えません。
  • 8 ~ 9 の間にダウンタイムが発生した場合は、リカバリ後にロールバックするか、トランザクションのコミットを続行するかを選択できます。これは、REDO ログがこの時点で保持されているためです。
  • システムが 9 以降にダウンし、メモリ マップで変更されたデータがディスクにフラッシュバックするには遅すぎる場合、システムが復元された後、REDO ログに従ってデータをディスクにフラッシュバックできます。

Buffer Pool のプロセスのみ:

[外部リンクの画像の転送に失敗しました。ソース サイトにはアンチリーチング メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-tyHXqwlM-1680921939349)(C:/Users/dell/AppData/Roaming/Typora/ Typora-user-images/ image-20230327193914352.png)]

redo + undo

画像-20230327193950114

Buffer Pool のデータを更新する前に、まずデータ トランザクションの状態を Undo Log に書き込む必要があります。更新の途中でエラーが発生したと仮定すると、Undo Log を使用して、トランザクションが開始される前にロールバックできます。

詳細な生成プロセス

InnoDB エンジンの場合、レコード自体のデータに加えて、各行レコードにはいくつかの非表示の列があります。

  • DB_ROW_ID : テーブルに主キーが明示的に定義されておらず、テーブルに一意のインデックスが定義されていない場合、 InnoDB は非表示の row_id カラムをテーブルの主キーとして自動的に追加します。
  • DB_TRX_ID : 各トランザクションにはトランザクション ID が割り当てられ、レコードが変更されると、このトランザクションのトランザクション ID が trx_id に書き込まれます。
  • DB_ROLL_PTR : ロールバック ポインターは、基本的には取り消しログへのポインターです。

画像-20230327194457270

INSERT を実行する場合

begin;
INSERT INTO user (name) VALUES ("tom");

挿入されたデータは挿入取り消しログを生成し、データのロールバック ポインターはそれを指します。元に戻すログは、元に戻すログのシリアル番号、主キーに挿入された列と値を記録します...、ロールバックを実行すると、対応するデータは主キーを介して直接削除できます。

画像-20230327194756686

UPDATE実行時

更新操作では、更新取り消しログが生成され、主キーを更新するものと更新しないものに分けられます。

UPDATE user SET name="Sun" WHERE id=1;

画像-20230327194904935

この時点で、古いレコードは新しい取り消しログに書き込まれ、ロールバック ポインターは新しい取り消しログを指します。その取り消し番号は 1 で、新しい取り消しログは古い取り消しログを指します (取り消し番号 = 0)。

ここで実行するとします:

UPDATE user SET id=2 WHERE id=1 ;

画像-20230327195048245

**主キーの更新操作では、最初に元データの削除マークフラグがオンになります.この時点では、実際のデータの削除はありません.**実際の削除は、判断のためにクリーニングスレッドに渡されます.その後、新しいデータが挿入されます. アンドゥ ログのデータも生成され、アンドゥ ログのシリアル番号がインクリメントされます。

データが変更されるたびにアンドゥ ログが生成されることがわかります.レコードが複数回変更されると、複数のアンドゥ ログが生成されます.アンドゥ ログには、変更前のログと、各アンドゥのシリアル番号が記録されます. log 増分なので、ロールバックしたい時は通し番号順に押し進めると元のデータにたどり着きます。

ログのロールバックを元に戻す方法

上記の例を例にとると、ロールバックが実行されると仮定すると、対応するプロセスは次のようになります。

  1. undo no=3 のログから id=2 のデータを削除

  2. undo no=2 ログを使用して、id=1 のデータの削除マークを 0 に復元します。

  3. undo no=1 ログを使用して、id=1 のデータの名前を Tom に復元します。

  4. undo no=0 log で id=1 のデータを削除します

ログの削除を元に戻す

  • 取り消しログの挿入用

挿入操作の記録は、トランザクション自体にのみ表示され、他のトランザクションには表示されないためです。したがって、パージ操作なしで、トランザクションがコミットされた直後に取り消しログを削除できます。

  • 更新取り消しログ用

元に戻すログは、MVCC メカニズムを提供する必要がある場合があるため、トランザクションがコミットされたときに削除できません。送信時に元に戻すログのリンクされたリストに入れ、パージ スレッドが最終的な削除を実行するのを待ちます。

パージ スレッドの 2 つの主な機能は次のとおりです。

元に戻すページをクリーンアップし、ページ内の Delete_Bit でマークされたデータ行をクリアしますInnoDB では、トランザクションの Delete 操作は実際にはデータ行を削除しませんが、レコードを削除せずにレコードの Delete_Bit をマークする Delete Mark 操作です。これは一種の「誤った削除」です。これは単なるマークであり、実際の削除作業はバックグラウンド パージ スレッドによって完了する必要があります。

3. まとめ

画像-20230327195436357

  • REDO ログは物理ログです、データ ページの物理的な変更を記録します。取り消しログは、やり直しログの逆のプロセスではありません。
  • 元に戻すログは論理ログですトランザクションがロールバックされると、データベースを元の状態に論理的に復元するだけです

参考

https://www.bilibili.com/video/BV1iq4y1u7vj/?spm_id_from=333.337.search-card.all.click&vd_source=25b05e9bd8b4bdac16ca2f47bbeb7990

おすすめ

転載: blog.csdn.net/qq_42130468/article/details/130025918