- JavaScript はコードを 単一スレッド で一度に 1 つずつ実行します。
- コールスタックが空の場合、最初にすべてのマイクロタスクが実行され、次に 1 つのタスクが実行されます。
setTimeout(fn, 0)は「今」ではなく「最小限の遅延の後、できるだけ早く」を意味します。- 長時間の同期作業はすべてをブロックするため、分割するかワーカーに移動します。
setTimeout と Promise を含む数行を実行すると、出力順序に驚かれるかもしれません。その理由は JavaScript イベント ループ で説明されています。これは、シングルスレッド言語がフリーズすることなくタイマー、ネットワーク呼び出し、クリックを処理できるようにするメカニズムです。
一度それを理解すると、混乱を招くバグの種類全体が意味を理解し始めます。各サンプルを Node.js 24 で実行し、出力はその実行から得られます。中心的な順序付けルールはブラウザーにも適用され、Node.js が異なる点を指摘します。 JavaScript の Promise の基本をすでに知っている場合は、さらに多くのことを理解できるでしょう。
イベントループが存在する理由
JavaScript は 1 つのメインスレッドでコードを実行するため、一度に実行されるコードは 1 つだけです。単にネットワーク要求を待っているだけでは、応答が返されるまでページがフリーズします。
したがって、JavaScript は待機しません。遅い作業はホスト環境、つまりバックグラウンドで処理できるブラウザまたは Node.js に渡されます。その作業が完了すると、コールバックがキューに入れられます。イベント ループの仕事は、キューに入れられたコールバックをスタックが空いたときにスタックに移動することです。
一人で働くシェフを想像してみてください。シェフは料理を見つめるのではなく、オーブンに料理を入れてタイマーをセットします。タイマーが鳴ると、タスクがリストに登録され、シェフは現在担当しているタスクを完了した後にタスクを受け取ります。
可動部品
| パート | 何をするのか | 例 |
|---|---|---|
| コールスタック | 現在実行中の機能を追跡します | 同期コード |
| ホストAPI | コードの外で作業を行う | タイマー、fetch、ファイル読み取り |
| タスクキュー | 実行準備ができているコールバックを保持します | setTimeout、クリック数、I/O |
| マイクロタスクキュー | 優先度の高いコールバックを保持します | .then、await、queueMicrotask |
| イベントループ | 次に何を実行するかを決定します | 継続的に実行 |
タスクはマクロタスクと呼ばれることもあります。厳密に言えば、タスク ソースは複数存在する可能性があり、正確な詳細は環境によって異なります。
ルール
- 呼び出しスタックが空になるまで、すべての同期コードを実行します。
- 途中で追加された新しいマイクロタスクを含む、すべて のマイクロタスクを実行します。
- 更新期限が迫っている場合は、ブラウザで更新をレンダリングします。
- キューから 1 つ のタスクを取得して実行します。
- ステップ 2 に戻ります。
最も重要な詳細: すべてのマイクロタスク、次に1つのタスク。ほとんどのイベント ループ パズルは、その 1 つのルールを適用することで解決されます。
注文を見る
console.log("1 sync");
setTimeout(() => console.log("5 timeout"), 0);
Promise.resolve().then(() => console.log("3 promise"));
queueMicrotask(() => console.log("4 microtask"));
console.log("2 sync");
// 1 sync
// 2 sync
// 3 promise
// 4 microtask
// 5 timeout
ここで何が起こったのかを説明します。 2 つの console.log 行は同期しているため、最初に実行されました。 Promise コールバックと queueMicrotask はマイクロタスク キューに入り、スタックが空になるとすぐに、追加された順序で実行されました。遅延がゼロであっても、タイマー コールバックはタスクのターンを待つ必要がありました。
マイクロタスクはタイマー間で実行されます
単一タスクが完了するたびに、マイクロタスク キューは再び空になります。この例はそれを証明しています。
setTimeout(() => {
console.log("timer 1");
Promise.resolve().then(() => console.log("promise inside timer 1"));
}, 0);
setTimeout(() => console.log("timer 2"), 0);
// timer 1
// promise inside timer 1
// timer 2
最初のタイマー中に作成された Promise は、2 番目のタイマーの前に実行されました。最新のブラウザと Node.js 11 以降はこのように動作します。
await が適切な場所
await は関数を 2 つに分割します。実行前のすべては同期的に実行され、実行後のすべてはマイクロタスクとしてスケジュールされます。これは多くの人を驚かせます:
async function task() {
console.log("A before await");
await null;
console.log("C after await");
}
task();
console.log("B after calling task");
// A before await
// B after calling task
// C after await
async 機能はバックグラウンドでは起動しません。最初の await に到達するまで、同じスレッド上で実行されます。これは、null のように、本当の約束ではないものを待っている場合にも当てはまります。この構文の詳細については、JavaScript async/await ガイドを参照してください。
ループをブロックする
同期コードが実行されている間は、他のコードは実行できません。これを示すために、タイマーを 0 ミリ秒に設定し、スレッドを 0.5 秒間ビジー状態に保ちます。
const start = Date.now();
setTimeout(() => console.log("timer ran after", Date.now() - start, "ms"), 0);
while (Date.now() - start < 500) {}
// timer ran after about 500 ms
タイマーの遅延は最小であり、保証するものではありません。スタックが空になった場合にのみ実行されました。 Web ページでこのような長いループが発生すると、クリックが反応せず、アニメーションが停止します。サーバー上では、他のすべてのリクエストは待機する必要があるため、1 つの遅い同期呼び出しが Node.js プロセスのすべてのユーザーに悪影響を与える可能性があります。
ブロックの一般的な原因には、巨大なループ、非常に大規模な JSON の解析、リクエスト ハンドラーの readFileSync などの同期ファイル関数、および壊滅的な正規表現が含まれます。
ヒント: Chrome DevTools では、パフォーマンス パネルは 50 ミリ秒を超えるタスクを長いタスクとしてマークします。これは、ページが遅く感じられる原因を簡単に見つける方法です。
応答性を維持する
負荷の高い作業の場合は、イベント ループに一呼吸置く機会を与えます。 1 つの方法は、タイマーで時々一時停止し、他のタスクを実行できるようにすることです。
const items = [0, 1, 2, 3, 4];
async function processAll() {
for (const item of items) {
if (item === 2) await new Promise((r) => setTimeout(r, 0));
console.log("item", item);
}
}
setTimeout(() => console.log("other work gets a turn"), 0);
processAll();
// item 0
// item 1
// other work gets a turn
// item 2
// item 3
// item 4
私たちが実際のタスクで譲歩したため、もう一方のタイマーは真ん中で実行されました。実際のコードでは、非常に多くの項目またはミリ秒ごとに生成されます。画像処理など、本当に CPU を使用する作業の場合は、ブラウザーの Web ワーカー、または別のスレッドで実行される Node.js のワーカー スレッドを使用します。
マイクロタスク飢餓
ループは次に進む前にマイクロタスク キューを完全に空にするため、マイクロタスクがさらにスケジュールを立て続けると、他のすべてが停止する可能性があります。これを 100,000 個のチェーンされたマイクロタスクでテストしました。
let count = 0;
let timerFired = false;
setTimeout(() => { timerFired = true; }, 0);
function spin() {
count++;
if (count < 100000) queueMicrotask(spin);
else console.log("timer fired during microtasks?", timerFired);
}
queueMicrotask(spin);
// timer fired during microtasks? false
すべての作業が完了するまでタイマーは実行できませんでした。その連鎖が終わらなければ、タイマーは決して起動せず、ブラウザではページが再描画されることはありません。解決された Promise に対して await を指定して譲歩しても、別のマイクロタスクが追加されるだけなので、ここでは役に立ちません。他の人に順番を与えるには、タイマーなどの実際のタスクが必要です。
ブラウザでのレンダリング
ブラウザーは通常、タスクの間にペイントする機会を得ます。これは一般的なバグの説明です。
spinner.hidden = false;
doHeavyWork(); // blocks the thread
spinner.hidden = true;
ブラウザにはこれらの行間をレンダリングする機会がなかったため、スピナーは表示されません。これを修正するには、重労働が始まる前にフレームを通過させるか、その作業を作業者に移します。ビジュアルを変更するコードの場合、requestAnimationFrame は次のペイントの直前に実行されるため、アニメーションに最適なツールになります。
Node.jsの違い
Node.js には、タイマー、I/O ポーリング、setImmediate などのいくつかのフェーズを持つ独自のイベント ループがあります。独自のキューで実行される process.nextTick もあります。これらを非同期関数内から実行したときに確認された内容は次のとおりです。
setTimeout(() => console.log("timeout"), 0);
setImmediate(() => console.log("immediate"));
process.nextTick(() => console.log("nextTick"));
Promise.resolve().then(() => console.log("promise"));
// promise
// nextTick
// immediate
// timeout
多くの記事では、nextTick は常に約束を破り、タイマーは setImmediate を破ると述べています。私たちの結果は、それがそれほど単純ではないことを示しています。私たちのコードはすでにマイクロタスク内で実行されていたため、Promise キューが最初に実行されました。また、setTimeout と setImmediate の順序は、どこから呼び出されるかによって異なることが知られています。したがって、これらの正確な順序に依存するコードを作成しないでください。あることが次々と発生する必要がある場合は、それらを明示的に連鎖させます。
インタビュークイズ
答えを読む前に、順序を考えてみてください。
console.log("a");
setTimeout(() => console.log("b"), 0);
Promise.resolve().then(() => {
console.log("c");
setTimeout(() => console.log("d"), 0);
Promise.resolve().then(() => console.log("e"));
});
console.log("f");
実行すると、a、f、c、e、b、dが出力されました。その理由は次のとおりです。
aとfは同期しているため、最初に実行されます。- スタックは空であるため、同じラウンドでマイクロタスク
cが実行され、次に新しいマイクロタスクeが実行されます。 - 今度は 1 つのタスク、つまり最初にキューに入れられたタイマー
bです。 - 次に、次のタスク: タイマー
d。これはマイクロタスク中にキューに入れられました。
よくある間違い
setTimeoutの考えは正確です。 遅延は最小限にすぎません。asyncは別のスレッドを意味します。 非同期コードは待機の間にメインスレッドで実行されます。- スリープを使用して順序を修正します。実際の状態を待つ方がはるかに信頼性が高く、これは不安定なテストにも当てはまります。
forEachをawaitと組み合わせて使用します。項目間で待機しません。 JavaScript 配列メソッドを参照してください。- サーバー ハンドラーで大量の同期作業を実行する。 1 つのリクエストが他のリクエストごとに遅延する可能性があります。
まとめ
基本的なルールを覚えておいてください。スタックが空になるまでスタックを実行し、すべてのマイクロタスクを実行してから 1 つのタスクを実行し、これを繰り返します。他のすべてはそこから派生します。定着させるには、上記のクイズの独自バージョンを作成し、出力を紙に予測して実行します。一つ間違えて、その理由を理解することは、どんな図よりも勉強になります。
よくある質問
JavaScriptのイベントループとは何ですか?
これは、コール スタックが空のときにキューに入れられたコールバックを実行するメカニズムです。これにより、シングルスレッドの JavaScript がブロックすることなくタイマー、入力、ネットワーク応答を処理できるようになります。
マイクロタスクとタスクの違いは何ですか?
Promise コールバックなどのマイクロタスクは、スタックが空になるとすぐにすべて実行されます。タイマーなどのタスクは一度に 1 つずつ実行され、それぞれの後にマイクロタスクが排出されます。
setTimeout を 0 にするとすぐに実行されないのはなぜですか?
そのコールバックはタスクのターンを待つ必要があります。すべての同期コードとマイクロタスクが最初に実行され、遅延は正確な時間ではなく最小値になります。
JavaScript はシングルスレッドですか?
メインの JavaScript コードは 1 つのスレッドで実行されます。ブラウザーと Node.js は一部のバックグラウンド作業に他のスレッドを使用し、ワーカーを使用するとコードを並列実行できます。
await はイベントループをブロックしますか?
いいえ、await はその非同期関数のみを一時停止し、その間に他のコードを実行できます。ただし、その前後に長い同期コードがあるとループがブロックされる可能性があります。
学び続ける
テスト、WordPress、または SEO についてサポートが必要ですか?
私たちは、チームがより優れたソフトウェアを出荷し、サイトを成長させるのを支援します。あなたが取り組んでいることを教えてください。明確な次のステップを提案します。
詳しいガイドはこちら テスト、AI、最新の開発
この記事は気に入りましたか?トピックを選択して、最新のチュートリアルに直接アクセスしてください。
AI 幻覚の説明: 言語モデルが物事をでっち上げる理由とそれを軽減する方法
AI 幻覚について説明しました。幻覚とは何か、言語モデルが幻覚を生成する理由、一般的な種類、グラウンディングとプロンプトで幻覚を軽減する方法、そして私たちが実行した簡単なチェックです。
記事を読む →DevOps環境変数の説明: .env ファイル、シークレット、および設定が正しく行われている
実行した Python の例を使用して環境変数について説明します。環境変数の読み取りと設定、すべての値が文字列である理由、.env ファイル、プロセスの継承、シークレット、検証について説明します。
記事を読む →ソフトウェア開発API レート制限の説明: アルゴリズム、ヘッダー、および 429 エラーの処理
API レート制限については、実行したシミュレーションで説明します。トークン バケット、固定ウィンドウ、スライディング ウィンドウ、429 応答、レート制限ヘッダー、バックオフによる再試行、およびテスト制限です。
記事を読む →テスト自動化Python でのモッキング:unittest.mock、patch、およびテストを嘘にする間違い
Python でのunittest.mockによるモック、テスト済み: モックとreturn_value、side_effect、名前が使用されている場所のパッチ、仕様と自動仕様、日付のモック、およびよくある間違い。
記事を読む →ソフトウェアテストコードカバレッジの説明: 回線カバレッジとブランチカバレッジ、および 100% の実際の意味
実際のcoverage.pyの実行でコードカバレッジを説明: 行、分岐、条件のカバレッジ、レポートの読み取り、100%のカバレッジでもバグを見逃す可能性がある理由、および賢明なターゲット。
記事を読む →データベースとSQLSQL における DELETE、TRUNCATE、DROP: 違い、ロールバック、およびそれぞれをいつ使用するか
DELETE、TRUNCATE、DROP について、テスト済み SQL を使用して説明します。それぞれの削除内容、ロールバック、トリガー、ID リセット、速度、PostgreSQL、MySQL、SQL Server、SQLite の違いについて説明します。
記事を読む →データベースとSQLSQL ビューの説明: 作成、使用、落とし穴の回避、例を示します
SQL ビューについて、実行したクエリとともに説明しました。ビューの作成、ライブ結果、列の非表示、パフォーマンス、更新可能なビューと具体化されたビュー、壊れた依存関係、ベスト プラクティスについて説明します。
記事を読む →ソフトウェア開発TypeScript インターフェイスと型: 実際の違い、コンパイラでテスト
TypeScript インターフェイスと型、実際のコンパイラでチェック: 両方でできること、片方だけでできること、宣言のマージ、拡張と交差、および従うべき簡単なルール。
記事を読む →ソフトウェア開発Python *args と **kwargs の説明: 柔軟な関数と明確な例
Python の *args と **kwargs について、実行したコードで説明しました。パッキングとアンパッキング、パラメーターの順序、キーワードのみの引数と位置のみの引数、デコレーター、一般的なエラーです。
記事を読む →ソフトウェア開発Python Lambda 関数: 構文、実際の用途、および使用すべきでない場合
Python のラムダ関数について、実行したコードとともに説明します。構文、キーによるソート、マップとフィルター、クロージャー、遅延バインディング トラップ、通常の def がより適切な選択である場合。
記事を読む →人工知能AIエージェントとは何ですか?シンプルなエージェント ループを実行できる仕組み
AIエージェントとは何ですか?エージェント ループ、ツール、メモリ、ガードレール、実行した小さな Python ループ、実際のリスク、およびシンプルなチャットボットがより良い選択である場合についてのわかりやすいガイドです。
記事を読む →DevOpsモニタリングと可観測性: ログ、メトリクス、トレースによる本当の違い
モニタリングと可観測性について説明: 既知および未知の障害、ログ、メトリクスとトレース、チェックアウト インシデントのウォークスルー、ゴールデン シグナル、OpenTelemetry、および便利なアラート。
記事を読む →DevOpsDocker ボリュームとバインド マウント: コンテナ データを正しい方法で保持する
Docker ボリュームとバインド マウントについて説明します。コンテナ データが消える理由、名前付きボリューム、バインド マウント、tmpfs、Compose の例、バックアップ、アクセス許可、およびそれぞれをいつ使用するかについて説明します。
記事を読む →テスト自動化TDD と BDD: 主な違い、1 つの機能が両方の方法で構築され、それぞれをいつ使用するか
TDD と BDD は、両方の方法で構築された 1 つの割引機能で説明されています。実行したコードによるレッド-グリーン-リファクタリング、Given-When-Then シナリオ、ツール、トレードオフ、およびそれぞれをいつ使用するかです。
記事を読む →ソフトウェアテストユーザー受け入れテスト (UAT): プロセス、チェックリスト、および実際の例
ユーザー受け入れテストの説明: 誰が実行するか、QA との違い、7 段階のプロセス、UAT テスト ケース テンプレート、チェックリスト、サインオフ基準、およびよくある間違い。
記事を読む →データベースとSQLSQL NULL 値の説明: IS NULL、COALESCE、NULLIF、および回避すべきトラップ
SQL の NULL 値について、実行したクエリで説明しました。3 つの値のロジック、IS NULL、COALESCE、NULLIF、集計、結合、並べ替えでの NULL の扱い方、およびそれが密かに引き起こすバグです。
記事を読む →データベースとSQLSQL CASE WHEN ステートメント: 構文、実際の例、およびよくある間違い
SQL CASE WHEN では、単純な検索フォーム、条件順序、SUM を使用したピボット、COUNT トリック、カスタム並べ替え、更新、回避すべき NULL トラップなど、実行したクエリを使用して説明しました。
記事を読む →ソフトウェア開発JWTとは何ですか? JSON Web トークンを実際の例で説明する
JWTとは何ですか? JSON Web トークンについて説明しました。3 つの部分、実行したコード内で構築および改ざんされたトークン、JWT とセッション、ストレージ、リフレッシュ トークン、およびセキュリティ上の間違いです。
記事を読む →ソフトウェア開発JavaScript イベントループの説明: コールスタック、マイクロタスク、タイマーと例
JavaScript イベント ループについて、実行したコードで説明しました。コール スタック、タスク キュー、マイクロタスク キュー、setTimeout 0 が即時ではない理由、ブロッキング、スターベーション、およびインタビュー クイズです。
記事を読む →ソフトウェア開発Python ジェネレーターと収量の説明: 実際の例でメモリを節約する
Python ジェネレーターと Yield について、実行したコードを使用して説明します。一時停止と再開の方法、メモリ節約の測定、パイプライン、無限シーケンス、Yield from、send、および一般的なトラップについて説明します。
記事を読む →人工知能埋め込みとは何ですか?ベクトルとコサインの類似度を簡単に説明
埋め込みとは何ですか?ベクトル、コサイン類似度、セマンティック検索に関する平易なガイド。私たちが実行した小さな例、一般的な使用法、モデルの選択、既知の制限が含まれています。
記事を読む →DevOpsBlue-Green デプロイメントと Canary デプロイメント: 違い、トレードオフ、および選択方法
ブルー グリーン デプロイメントとカナリア デプロイメントについて説明: それぞれの仕組み、コスト、リスクとロールバック、テストした安定したユーザー バケット化、データベース移行、ローリング アップデート、および選択方法。
記事を読む →テスト自動化不安定なテスト: 問題の原因とそれを永久に修正する方法
不安定なテストについては、私たちが実行したデモを使用して説明しました。主な原因、確認および診断する方法、タイミングの適切な修正、共有された状態と日付、隔離プロセスなどです。
記事を読む →ソフトウェアテスト負荷テストとストレス テスト: 違い、メトリクス、および k6 の例
ロード テストとストレス テストについて説明します。それぞれが何を証明するか、スパイク テストとソーク テスト、重要な指標、平均値が誤解を招く理由、K6 の例、テストの計画方法について説明します。
記事を読む →ソフトウェアテストテスト ケースとテスト シナリオ: 主な違い、例、およびそれぞれをいつ使用するか
テスト ケースとテスト シナリオの説明: 明確な定義、並べて表、シナリオからケースへの 1 つのパスワード リセット機能、テンプレート、およびそれぞれをいつ使用するか。
記事を読む →データベースとSQLSQL クエリの最適化: 実行プランを読み取り、タイミングを考慮して遅いクエリを修正
SQL クエリの最適化は 100 万行でテストされています。実行プランの読み取り、適切なインデックスの追加、検索対象外のフィルターの修正、複合インデックスとキーセットのページネーションの使用。
記事を読む →データベースとSQLSQL UNION と UNION ALL: 違い、例、テストされたパフォーマンス
SQL UNION と UNION ALL について、実行したクエリで説明しました。重複、両方に従うルール、実際のデータのバグ、測定されたパフォーマンス、INTERSECT と EXCEPT、およびそれぞれをいつ使用するかについて説明します。
記事を読む →ソフトウェア開発JavaScript Fetch API チュートリアル: GET、POST、エラー処理、タイムアウト
ローカル サーバーに対してテストされた JavaScript Fetch API チュートリアル: GET と POST、404 がスローされない理由、再利用可能なヘルパー、クエリ文字列、タイムアウト、CORS、およびよくある間違い。
記事を読む →ソフトウェア開発Python ファイル処理: 読み取り、書き込み、CSV、JSON、パスライブラリと例
Python のファイル処理について、実行したコードで説明します。オープン モード、大きいファイルの読み取り、CSV、JSON、パスライブラリ、バイナリ ファイル、安全な書き込み、エラー処理、およびよくある間違いについて説明します。
記事を読む →ソフトウェア開発Python のクラスとオブジェクト: 実際の例を含む OOP チュートリアル
Python のクラスとオブジェクトについて、実行したコードとともに説明します: __init__、メソッド、クラスとインスタンスの属性、継承、プロパティ、ダンダー メソッド、データクラス、およびよくある間違い。
記事を読む →人工知能大規模言語モデルとは何ですか? LLM の仕組みを誇大広告なしで説明
大規模言語モデルとは何ですか? LLM は、トークン、エンベディング、アテンション、トレーニング、温度およびコンテキスト ウィンドウ、小さな次の単語のデモ、実際の制限など、平易な言葉で説明されています。
記事を読む →DevOps初心者のための Linux コマンド: 例を含む基本ターミナル ガイド
初心者向けの Linux コマンド。ナビゲーション、ファイル、ログの表示、grep、パイプ、権限、プロセス、アーカイブなどのタスク別にグループ化されており、実用的なログ分析の例も含まれています。
記事を読む →テスト自動化Selenium と Playwright: 主な違い、コードの比較、どちらを選択するか
Selenium と Playwright の比較: アーキテクチャ、待機、ブラウザと言語のサポート、デバッグ、両方のツールでの同じテスト、移行のアドバイスと選択方法。
記事を読む →ソフトウェアテスト等価性分割: テスト設計手法を実際の例で説明
等価性パーティション化について説明しました。有効なパーティションと無効なパーティションを見つける方法、年齢、パスワード、クーポンに関する実際の例、実行したテスト、境界と組み合わせる理由。
記事を読む →ソフトウェアテストソフトウェアテストにおける重大度と優先度: 相違点、マトリックス、および実際の例
ソフトウェア テストにおける重大度と優先度について説明します。明確な定義、例を含む 4 方向のマトリックス、レベル スケール、それぞれの設定者、バグ トリアージの実行方法。
記事を読む →データベースとSQLSQL サブクエリのチュートリアル: 型、相関サブクエリ、および EXISTS と例
実行したクエリを含む SQL サブクエリのチュートリアル: スカラー、IN、FROM、SELECT サブクエリ、相関サブクエリ、EXISTS、NOT IN NULL トラップ、サブクエリ対 JOIN 対 CTE。
記事を読む →データベースとSQLSQL トランザクションと ACID の説明: COMMIT、ROLLBACK、分離レベル
SQL トランザクションと ACID について、実行した銀行振込で説明しました。COMMIT、ROLLBACK、セーブポイント、ロック、分離レベル、ダーティ リード、デッドロック、ベスト プラクティスについて説明します。
記事を読む →ソフトウェア開発Git mergeとrebaseの違い:例と使い分け
Git mergeとrebaseの違いを、実際のログ出力、比較表、rebaseの黄金律、安全なチームワークフローで解説します。
記事を読む →ソフトウェア開発Pythonのリスト内包表記:構文・例・使うべきでない場面
Pythonのリスト内包表記を解説:構文、フィルタ、if-else、ネストしたループ、実測した処理時間、そして通常のforループの方が良い場面。
記事を読む →データベースとSQLSQL JOINを例で解説(Inner・Left・Right・Full)
サンプルのテーブルとクエリでSQL JOINを順に解説。INNER、LEFT、RIGHT、FULL、CROSS、SELFに加え、よくある間違いとパフォーマンスのコツも紹介します。
記事を読む →