「MySQL - LIMIT句」の版間の差分
細 文字列「__FORCETOC__」を「{{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,Podman,電気回路,電子回路,基板,プリント基板 |description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This pag… |
編集の要約なし |
||
| 1行目: | 1行目: | ||
== 概要 == | == 概要 == | ||
<code>LIMIT</code>句は、MySQLのSQLクエリの一部として使用されており、取得するレコードの数を制限するためのものである。<br> | <code>LIMIT</code> 句は、MySQLのSQLクエリの一部として使用されており、取得するレコードの数を制限するためのものである。<br> | ||
<br> | <br> | ||
大量のレコードがあるテーブルから特定の数のレコードを取得する場合に使用する。<br> | 大量のレコードがあるテーブルから特定の数のレコードを取得する場合に使用する。<br> | ||
これにより、不要なレコードの読み込みを避けて、パフォーマンスを向上させることができる。<br> | これにより、不要なレコードの読み込みを避けて、パフォーマンスを向上させることができる。<br> | ||
<br> | |||
<code>LIMIT</code> 句は <code>SELECT</code> 文、<code>DELETE</code> 文で使用することができ、<code>OFFSET</code> 句と組み合わせることで、取得開始位置を指定することも可能である。<br> | |||
<br> | |||
ページネーション機能の実装においては、オフセットベース方式とカーソルベース方式の2つのアプローチが存在する。<br> | |||
大規模なデータセットを扱う場合、適切なインデックス設計とページネーション方式の選択がパフォーマンスに大きく影響する。<br> | |||
<br> | |||
MySQL 8.0以降では、ウィンドウ関数との組み合わせにより、より高度なデータ取得が可能となっている。<br> | |||
また、<code>ORDER BY</code> 句と組み合わせることにより、特定の順序でレコードを取得したり、ランダムなレコードを取得したりすることもできる。<br> | |||
<br> | |||
システム変数 <code>max_allowed_packet</code> の設定値にも注意が必要であり、大量のレコードを取得する場合には適切な値に調整する必要がある。<br> | |||
<br><br> | <br><br> | ||
== LIMITの基本構文 == | == LIMITの基本構文 == | ||
<code>LIMIT</code>句は、<code>SELECT</code>文の最後に記述する。<br> | <code>LIMIT</code> 句は、<code>SELECT</code> 文の最後に記述する。<br> | ||
<syntaxhighlight lang="mysql"> | <syntaxhighlight lang="mysql"> | ||
SELECT <* または カラム名> FROM <テーブル名> LIMIT <数値>; | SELECT <* または カラム名> FROM <テーブル名> LIMIT <数値>; | ||
| 15行目: | 25行目: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<br> | <br> | ||
例えば、ユーザにランダムな商品を表示する場合、<code>ORDER BY</code> | 例えば、ユーザにランダムな商品を表示する場合、<code>ORDER BY</code> 句 と <code>RAND()</code> 関数と組み合わせて使用する。<br> | ||
<syntaxhighlight lang="mysql"> | <syntaxhighlight lang="mysql"> | ||
SELECT * FROM <テーブル名> ORDER BY RAND() LIMIT <数値>; | SELECT * FROM <テーブル名> ORDER BY RAND() LIMIT <数値>; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<br> | <br> | ||
新しい順にレコードを取得する場合、<code>ORDER BY</code> | 新しい順にレコードを取得する場合、<code>ORDER BY</code> 句 と <code>DESC</code> キーワードを組み合わせて使用する。<br> | ||
<syntaxhighlight lang="mysql"> | <syntaxhighlight lang="mysql"> | ||
SELECT * FROM <テーブル名> ORDER BY <カラム名> DESC LIMIT <数値>; | SELECT * FROM <テーブル名> ORDER BY <カラム名> DESC LIMIT <数値>; | ||
</syntaxhighlight> | |||
<br> | |||
<u>※注意</u><br> | |||
<u><code>LIMIT</code>句のみを使用する場合、レコードの取得順序は保証されない。</u><br> | |||
<u>特定の順序でレコードを取得する場合は、必ず<code>ORDER BY</code>句と組み合わせる必要がある。</u><br> | |||
<br><br> | |||
== OFFSET句 == | |||
<code>OFFSET</code> 句は、<code>LIMIT</code> 句と組み合わせて、取得開始位置を指定するために使用される。<br> | |||
<br> | |||
==== OFFSET構文 ==== | |||
<code>OFFSET</code> 句を使用することにより、指定した位置からレコードを取得することができる。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- 標準構文 | |||
SELECT * FROM <テーブル名> LIMIT <row_count> OFFSET <offset>; | |||
-- 省略構文 | |||
SELECT * FROM <テーブル名> LIMIT <offset>, <row_count>; | |||
-- 例: 10件目から5件のレコードを取得 | |||
SELECT * FROM users LIMIT 5 OFFSET 10; | |||
SELECT * FROM users LIMIT 10, 5; -- 上と同じ結果 | |||
</syntaxhighlight> | |||
<br> | |||
<u>※注意</u><br> | |||
<u>省略構文では、引数の順序が逆になることに注意が必要である。</u><br> | |||
<br> | |||
==== 引数の仕様 ==== | |||
<code>LIMIT</code>句と<code>OFFSET</code>句の引数には、以下に示す仕様が存在する。<br> | |||
<br> | |||
* 非負の整数定数を指定する必要がある。 | |||
* プリペアドステートメントでは、プレースホルダー (?) を使用することができる。 | |||
* ストアドプロシージャ内では、整数値の変数またはパラメータを使用することができる。 | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- プリペアドステートメントでの使用例 | |||
PREPARE stmt FROM 'SELECT * FROM users LIMIT ? OFFSET ?'; | |||
SET @limit = 10; | |||
SET @offset = 20; | |||
EXECUTE stmt USING @limit, @offset; | |||
</syntaxhighlight> | |||
<br> | |||
==== 特定位置から全行を取得 ==== | |||
特定の位置から残りの全ての行を取得する場合は、大きな数値を指定する。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- 95行目以降の全レコードを取得 | |||
SELECT * FROM users LIMIT 95, 18446744073709551615; | |||
</syntaxhighlight> | |||
<br> | |||
==== LIMIT 0の用途 ==== | |||
<code>LIMIT 0</code> は、実際のデータを取得せずに、以下に示す用途で使用される。<br> | |||
<br> | |||
* クエリ構文の妥当性確認 | |||
* テーブルのメタデータ取得 (列名、データ型等) | |||
* クエリの実行計画の確認 | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- メタデータのみを取得 | |||
SELECT * FROM users LIMIT 0; | |||
</syntaxhighlight> | </syntaxhighlight> | ||
<br><br> | <br><br> | ||
| 29行目: | 103行目: | ||
特定のアプリケーションでは、ユーザがレコードを取得する場合に、一定の制限を設ける場合がある。<br> | 特定のアプリケーションでは、ユーザがレコードを取得する場合に、一定の制限を設ける場合がある。<br> | ||
これにより、以下に示すようなメリットがある。<br> | これにより、以下に示すようなメリットがある。<br> | ||
<br> | |||
* サーバへの過度な負荷を防ぐ。 | * サーバへの過度な負荷を防ぐ。 | ||
* 情報漏洩を制限できる。 | * 情報漏洩を制限できる。 | ||
* | * サービス拒否攻撃 (DoS) の影響を軽減できる。 | ||
* ユーザエクスペリエンスの向上。 | * ユーザエクスペリエンスの向上。 | ||
<br> | <br> | ||
| 38行目: | 113行目: | ||
== MySQLの設定とLIMIT句 == | == MySQLの設定とLIMIT句 == | ||
システム変数<code>max_allowed_packet</code>は、1度に送受信できるデータの最大サイズを制御することができる。<br> | システム変数 <code>max_allowed_packet</code> は、1度に送受信できるデータの最大サイズを制御することができる。<br> | ||
<br> | <br> | ||
ただし、この値が小さすぎる場合、<code>LIMIT</code>句を使用して大量のレコードを取得する時にエラーが発生する可能性がある。<br> | ただし、この値が小さすぎる場合、<code>LIMIT</code> 句を使用して大量のレコードを取得する時にエラーが発生する可能性がある。<br> | ||
適切な値にすることにより、スムーズに大量のレコードを取得することができる。<br> | 適切な値にすることにより、スムーズに大量のレコードを取得することができる。<br> | ||
<br><br> | <br><br> | ||
== | == ページネーションの実装 == | ||
<code>LIMIT</code> | ページネーションは、大量のデータを複数のページに分割して表示する手法である。<br> | ||
MySQLでは、主に2つの方式が存在する。<br> | |||
<br> | |||
==== オフセットベースのページネーション ==== | |||
最も基本的なページネーション方式であり、<code>OFFSET</code> 句を使用して実装する。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- 1ページ目 (0〜9件目) | |||
SELECT * FROM products ORDER BY id LIMIT 10 OFFSET 0; | |||
-- 2ページ目 (10〜19件目) | |||
SELECT * FROM products ORDER BY id LIMIT 10 OFFSET 10; | |||
-- 3ページ目 (20〜29件目) | |||
SELECT * FROM products ORDER BY id LIMIT 10 OFFSET 20; | |||
</syntaxhighlight> | |||
<br> | |||
<u>※注意</u><br> | |||
<u>この方式は、大きな<code>OFFSET</code>値を使用する場合にパフォーマンスが低下する。</u><br> | |||
<br> | |||
==== 決定論的なソートの重要性 ==== | |||
ページネーションを実装する場合、決定論的なソート (常に同じ順序を返すソート) が重要である。<br> | |||
<br> | |||
複数の行が同じ値を持つ場合、<code>ORDER BY</code> 句の結果は不確定になる可能性がある。<br> | |||
これにより、ページをまたいで同じレコードが表示されたり、レコードが欠落したりする問題が発生する。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- 不確定なソート (複数のユーザが同じcreated_atを持つ場合) | |||
SELECT * FROM users ORDER BY created_at LIMIT 10 OFFSET 0; | |||
-- 決定論的なソート (一意性を保証) | |||
SELECT * FROM users ORDER BY created_at, id LIMIT 10 OFFSET 0; | |||
</syntaxhighlight> | |||
<br> | |||
主キーや一意なカラムを <code>ORDER BY</code> 句に追加することにより、決定論的なソートを実現できる。<br> | |||
<br> | |||
==== カーソルベースのページネーション (キーセット方式) ==== | |||
カーソルベースのページネーションは、<code>OFFSET</code> 句を使用せず、<code>WHERE</code> 条件でカーソル位置を指定する方式である。<br> | |||
<br> | |||
この方式は、大規模なデータセットにおいて高いパフォーマンスを発揮する。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- 1ページ目 | |||
SELECT * FROM products ORDER BY id LIMIT 10; | |||
-- 2ページ目 (最後に取得したidが100の場合) | |||
SELECT * FROM products WHERE id > 100 ORDER BY id LIMIT 10; | |||
-- 3ページ目 (最後に取得したidが110の場合) | |||
SELECT * FROM products WHERE id > 110 ORDER BY id LIMIT 10; | |||
</syntaxhighlight> | |||
<br> | |||
===== カーソルベース方式の利点 ===== | |||
* O(log N) の時間計算量であり、大規模なデータセットでも高速である。 | |||
* 行の削除に強い。(他のユーザがレコードを削除しても、ページのずれが発生しにくい) | |||
* インデックスを効果的に活用できる。 | |||
<br> | |||
===== カーソルベース方式の欠点 ===== | |||
* 特定のページ番号に直接アクセスすることができない。 | |||
* 前のページに戻ることが複雑になる。 | |||
* 実装がやや複雑である。 | |||
<br> | |||
===== 複数列でのカーソルベースページネーション ===== | |||
複数の列でソートする場合のカーソルベースページネーションの実装例を以下に示す。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- created_atとidでソートする場合 | |||
-- 1ページ目 | |||
SELECT * FROM users ORDER BY created_at DESC, id DESC LIMIT 10; | |||
-- 2ページ目 (最後に取得したレコードがcreated_at='2024-01-01', id=100の場合) | |||
SELECT * FROM users | |||
WHERE (created_at < '2024-01-01') | |||
OR (created_at = '2024-01-01' AND id < 100) | |||
ORDER BY created_at DESC, id DESC | |||
LIMIT 10; | |||
</syntaxhighlight> | |||
<br> | |||
==== デファードジョイン方式 ==== | |||
デファードジョイン方式は、オフセットベースとカーソルベースの利点を組み合わせた手法である。<br> | |||
<br> | |||
この方式では、まずインデックスを使用して必要な主キーのみを取得し、その後にJOINで全てのカラムを取得する。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- 通常のオフセットベース (非効率) | |||
SELECT * FROM large_table ORDER BY created_at LIMIT 10 OFFSET 100000; | |||
-- デファードジョイン方式 (効率的) | |||
SELECT t.* | |||
FROM large_table t | |||
INNER JOIN ( | |||
SELECT id | |||
FROM large_table | |||
ORDER BY created_at | |||
LIMIT 10 OFFSET 100000 | |||
) AS subquery USING (id); | |||
</syntaxhighlight> | |||
<br> | |||
この方式により、大きな <code>OFFSET</code> 値を使用する場合でも、パフォーマンスの低下を抑えることができる。<br> | |||
<br><br> | |||
== パフォーマンス最適化 == | |||
<code>LIMIT</code> 句を使用する場合、パフォーマンスを最適化するための手法が存在する。<br> | |||
<br> | |||
==== 大きなOFFSET値の問題点 ==== | |||
<code>OFFSET</code> 句に大きな値を指定する場合、MySQLは指定された位置までの全ての行を読み取る必要がある。<br> | |||
<br> | |||
これにより、時間計算量がO(N)となり、<code>OFFSET</code> 値が大きいほどパフォーマンスが低下する。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- 100万件目から10件を取得する場合、 | |||
-- MySQLは100万件を読み取ってから結果を返す | |||
SELECT * FROM large_table ORDER BY id LIMIT 10 OFFSET 1000000; | |||
</syntaxhighlight> | |||
<br> | |||
このような場合、カーソルベースのページネーションやデファードジョイン方式を使用することが推奨される。<br> | |||
<br> | |||
==== カバリングインデックスの活用 ==== | |||
カバリングインデックスは、クエリで必要な全てのカラムを含むインデックスである。<br> | |||
<br> | |||
カバリングインデックスを使用することにより、テーブルデータにアクセスせずにインデックスのみでクエリを処理できる。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- カバリングインデックスを作成 | |||
CREATE INDEX idx_covering ON users (status, created_at, id, name); | |||
-- カバリングインデックスを活用したクエリ | |||
SELECT id, name, created_at | |||
FROM users | |||
WHERE status = 'active' | |||
ORDER BY created_at DESC | |||
LIMIT 10; | |||
</syntaxhighlight> | |||
<br> | |||
<code>EXPLAIN</code> コマンドを使用して、クエリがカバリングインデックスを使用しているかを確認できる。<br> | |||
<br> | |||
==== ORDER BY + LIMIT最適化 ==== | |||
MySQLは、<code>ORDER BY</code> 句 と <code>LIMIT</code> 句を組み合わせたクエリに対して、特別な最適化を行う。<br> | |||
<br> | |||
適切なインデックスが存在する場合、MySQLは全ての行をソートせずに、必要な数の行のみを処理する。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- インデックスを作成 | |||
CREATE INDEX idx_created_at ON users (created_at); | |||
-- 最適化されたクエリ | |||
SELECT * FROM users ORDER BY created_at DESC LIMIT 10; | |||
</syntaxhighlight> | |||
<br> | |||
この最適化により、大規模なテーブルでも高速にクエリを実行できる。<br> | |||
<br> | |||
==== InnoDBでの主キーの自動追加 ==== | |||
InnoDBストレージエンジンでは、<code>ORDER BY</code> 句に主キーが自動的に追加される場合がある。<br> | |||
<br> | |||
これにより、決定論的なソートが自動的に保証されることがある。<br> | |||
ただし、この動作に依存せず、明示的に主キーを指定することが推奨される。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- 明示的に主キーを指定 | |||
SELECT * FROM users ORDER BY status, id LIMIT 10; | |||
</syntaxhighlight> | |||
<br><br> | |||
== サブクエリ・JOIN・UNIONでのLIMIT句 == | |||
<code>LIMIT</code> 句は、サブクエリ、<code>JOIN</code>、<code>UNION</code> と組み合わせて使用することができる。<br> | |||
<br> | |||
==== サブクエリでのLIMIT ==== | |||
サブクエリ内で <code>LIMIT</code> 句を使用することにより、サブクエリの結果を制限できる。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- サブクエリで最新の10件の注文を取得し、その合計金額を計算 | |||
SELECT SUM(amount) AS total | |||
FROM ( | |||
SELECT amount | |||
FROM orders | |||
ORDER BY created_at DESC | |||
LIMIT 10 | |||
) AS recent_orders; | |||
</syntaxhighlight> | |||
<br> | |||
==== JOINでのLIMIT使用時の注意 ==== | |||
<code>JOIN</code> を使用する場合、<code>LIMIT</code> 句は結合後の結果に適用される。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- JOINした後の結果から10件を取得 | |||
SELECT users.name, orders.amount | |||
FROM users | |||
INNER JOIN orders ON users.id = orders.user_id | |||
ORDER BY orders.created_at DESC | |||
LIMIT 10; | |||
</syntaxhighlight> | |||
<br> | |||
各テーブルから10件ずつ取得する場合は、サブクエリを使用する必要がある。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- 各ユーザの最新10件の注文を取得 | |||
SELECT users.name, recent_orders.amount | |||
FROM users | |||
INNER JOIN ( | |||
SELECT user_id, amount, created_at | |||
FROM orders | |||
ORDER BY created_at DESC | |||
LIMIT 10 | |||
) AS recent_orders ON users.id = recent_orders.user_id; | |||
</syntaxhighlight> | |||
<br> | |||
==== UNIONでのLIMIT ==== | |||
<code>UNION</code> を使用する場合、<code>LIMIT</code> 句は個別の <code>SELECT</code> 文または全体の結果に適用できる。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- 各SELECTで10件ずつ取得し、結果を結合 | |||
(SELECT * FROM users WHERE status = 'active' ORDER BY created_at DESC LIMIT 10) | |||
UNION ALL | |||
(SELECT * FROM users WHERE status = 'inactive' ORDER BY created_at DESC LIMIT 10); | |||
-- 結合後の全体から10件を取得 | |||
(SELECT * FROM users WHERE status = 'active') | |||
UNION ALL | |||
(SELECT * FROM users WHERE status = 'inactive') | |||
ORDER BY created_at DESC | |||
LIMIT 10; | |||
</syntaxhighlight> | |||
<br> | |||
<u>※注意</u><br> | |||
<u>個別の <code>SELECT</code> 文に <code>LIMIT</code> 句を適用する場合は、括弧で囲む必要がある。</u><br> | |||
<br><br> | |||
== UPDATE文 / DELETE文でのLIMIT句 == | |||
<code>LIMIT</code> 句は、<code>DELETE</code> 文で使用することができるが、<code>UPDATE</code> 文では直接使用することができない。<br> | |||
<br> | |||
==== DELETE文でのLIMIT ==== | |||
<code>DELETE</code> 文で <code>LIMIT</code> 句を使用することにより、削除する行数を制限できる。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- 古いログレコードを10件削除 | |||
DELETE FROM logs | |||
ORDER BY created_at ASC | |||
LIMIT 10; | |||
</syntaxhighlight> | |||
<br> | |||
==== バッチ削除パターン ==== | |||
大量のレコードを削除する場合、一度に削除せずにバッチで削除することが推奨される。<br> | |||
<br> | |||
これにより、テーブルロックの時間を短縮し、レプリケーションへの影響を最小限に抑えることができる。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- 古いログを1000件ずつ削除 | |||
DELIMITER $$ | |||
CREATE PROCEDURE delete_old_logs() | |||
BEGIN | |||
DECLARE rows_affected INT DEFAULT 1; | |||
WHILE rows_affected > 0 DO | |||
DELETE FROM logs | |||
WHERE created_at < DATE_SUB(NOW(), INTERVAL 90 DAY) | |||
ORDER BY created_at ASC | |||
LIMIT 1000; | |||
SET rows_affected = ROW_COUNT(); | |||
-- レプリケーションの負荷を軽減するために短い待機時間を設ける | |||
DO SLEEP(0.1); | |||
END WHILE; | |||
END$$ | |||
DELIMITER ; | |||
</syntaxhighlight> | |||
<br> | |||
==== 制限事項 ==== | |||
<code>LIMIT</code> 句を <code>DELETE</code> 文で使用する場合、以下に示す制限事項が存在する。<br> | |||
<br> | |||
* 単一テーブルのみサポートされる。(マルチテーブル削除では使用不可) | |||
* サブクエリとの併用には制限がある。 | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- エラー: マルチテーブル削除ではLIMITを使用できない | |||
DELETE users, orders | |||
FROM users | |||
INNER JOIN orders ON users.id = orders.user_id | |||
WHERE users.status = 'deleted' | |||
LIMIT 10; -- エラー | |||
</syntaxhighlight> | |||
<br> | <br> | ||
==== UPDATE文での代替方法 ==== | |||
<code>UPDATE</code> 文では <code>LIMIT</code> 句を直接使用できないが、サブクエリを使用することで同様の効果を得られる。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- サブクエリを使用して最初の10件を更新 | |||
UPDATE users | |||
SET status = 'processed' | |||
WHERE id IN ( | |||
SELECT id FROM ( | |||
SELECT id | |||
FROM users | |||
WHERE status = 'pending' | |||
ORDER BY created_at ASC | |||
LIMIT 10 | |||
) AS tmp | |||
); | |||
</syntaxhighlight> | |||
<br> | |||
<u>※注意</u><br> | |||
<u>サブクエリを2重にネストしているのは、MySQLの制限により、更新対象のテーブルを直接サブクエリで参照できないためである。</u><br> | |||
<br><br> | <br><br> | ||
== MySQL 8.0以降の機能 == | |||
MySQL 8.0では、ウィンドウ関数が導入され、<code>LIMIT</code> 句と組み合わせることで高度なデータ取得が可能になった。<br> | |||
<br> | |||
==== ウィンドウ関数との組み合わせ ==== | |||
ウィンドウ関数を使用することにより、各グループ内での順位付けや集計を行うことができる。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- ウィンドウ関数で順位を付けてから上位10件を取得 | |||
SELECT * | |||
FROM ( | |||
SELECT | |||
id, | |||
name, | |||
department, | |||
salary, | |||
RANK() OVER (ORDER BY salary DESC) AS salary_rank | |||
FROM employees | |||
) AS ranked_employees | |||
WHERE salary_rank <= 10; | |||
</syntaxhighlight> | |||
<br> | |||
==== ROW_NUMBER関数を使用したページネーション ==== | |||
<code>ROW_NUMBER</code> 関数を使用することにより、決定論的なページネーションを定義できる。<br> | |||
<br> | |||
この方式は、決定論的なソートを保証しつつ、特定のページ範囲を取得することができる。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- ROW_NUMBER関数を使用したページネーション | |||
SELECT * | |||
FROM ( | |||
SELECT | |||
id, | |||
name, | |||
created_at, | |||
ROW_NUMBER() OVER (ORDER BY created_at DESC, id DESC) AS row_num | |||
FROM users | |||
) AS numbered_users | |||
WHERE row_num BETWEEN 11 AND 20; -- 2ページ目 (11〜20件目) | |||
</syntaxhighlight> | |||
<br> | |||
==== RANK()、DENSE_RANK()の使用 ==== | |||
<code>RANK</code> 関数 や <code>DENSE_RANK</code> 関数を使用することにより、同順位を考慮したランキングを実装できる。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- 各部門の上位5名を取得 | |||
SELECT * | |||
FROM ( | |||
SELECT | |||
id, | |||
name, | |||
department, | |||
salary, | |||
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank | |||
FROM employees | |||
) AS ranked_employees | |||
WHERE dept_rank <= 5; | |||
</syntaxhighlight> | |||
<br> | |||
==== PARTITION BYの活用 ==== | |||
<code>PARTITION BY</code> 句を使用することにより、グループごとに <code>LIMIT</code> のような制限を実現できる。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- 各カテゴリの最新3件の商品を取得 | |||
SELECT * | |||
FROM ( | |||
SELECT | |||
id, | |||
name, | |||
category, | |||
created_at, | |||
ROW_NUMBER() OVER (PARTITION BY category ORDER BY created_at DESC) AS row_num | |||
FROM products | |||
) AS numbered_products | |||
WHERE row_num <= 3; | |||
</syntaxhighlight> | |||
<br> | |||
==== FETCH FIRST構文の非サポート ==== | |||
SQL標準の <code>FETCH FIRST</code> 構文は、MySQL 8.0でもサポートされていない。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- エラー : MySQLではFETCH FIRST構文はサポートされていない | |||
SELECT * FROM users | |||
ORDER BY created_at DESC | |||
FETCH FIRST 10 ROWS ONLY; -- エラー | |||
-- MySQLでは以下を使用する | |||
SELECT * FROM users | |||
ORDER BY created_at DESC | |||
LIMIT 10; | |||
</syntaxhighlight> | |||
<br><br> | |||
== ROW_COUNT関数との関係 == | |||
<code>ROW_COUNT</code> 関数は、直前のSQL文が影響を与えた行数を返す関数である。<br> | |||
<br> | |||
==== ROW_COUNT関数の動作 ==== | |||
<code>ROW_COUNT</code> 関数は、<code>INSERT</code>、<code>UPDATE</code>、<code>DELETE</code> 文で影響を受けた行数を返す。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- DELETE文で削除された行数を確認 | |||
DELETE FROM logs | |||
WHERE created_at < DATE_SUB(NOW(), INTERVAL 90 DAY) | |||
LIMIT 1000; | |||
SELECT ROW_COUNT(); -- 削除された行数が返される | |||
</syntaxhighlight> | |||
<br> | |||
==== SELECT文では-1を返す ==== | |||
<code>SELECT</code> 文を実行した場合、<code>ROW_COUNT</code> 関数は <u>-1</u> を返す。<br> | |||
<br> | |||
<code>SELECT</code> 文の結果行数を取得する場合は、<code>FOUND_ROWS</code> 関数を使用する必要があるが、これはMySQL 8.0で非推奨となっている。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
SELECT * FROM users LIMIT 10; | |||
SELECT ROW_COUNT(); -- -1が返される | |||
</syntaxhighlight> | |||
<br> | |||
==== バッチ処理での活用 ==== | |||
<code>ROW_COUNT</code> 関数は、バッチ削除やバッチ更新の処理で活用できる。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- バッチ削除の例 | |||
DELIMITER $$ | |||
CREATE PROCEDURE batch_delete() | |||
BEGIN | |||
DECLARE affected_rows INT; | |||
DECLARE total_deleted INT DEFAULT 0; | |||
REPEAT | |||
DELETE FROM logs | |||
WHERE created_at < DATE_SUB(NOW(), INTERVAL 90 DAY) | |||
LIMIT 1000; | |||
SET affected_rows = ROW_COUNT(); | |||
SET total_deleted = total_deleted + affected_rows; | |||
DO SLEEP(0.1); | |||
UNTIL affected_rows = 0 END REPEAT; | |||
SELECT total_deleted AS total_deleted_rows; | |||
END$$ | |||
DELIMITER ; | |||
</syntaxhighlight> | |||
<br> | |||
==== SQL_CALC_FOUND_ROWS、FOUND_ROWS関数の非推奨 ==== | |||
MySQL 8.0.17以降、<code>SQL_CALC_FOUND_ROWS</code> クエリ修飾子 と <code>FOUND_ROWS</code> 関数は非推奨となった。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- 非推奨 : SQL_CALC_FOUND_ROWSの使用 | |||
SELECT SQL_CALC_FOUND_ROWS * FROM users LIMIT 10; | |||
SELECT FOUND_ROWS(); -- 全体の行数を返す (非推奨) | |||
</syntaxhighlight> | |||
<br> | |||
代わりに、<code>COUNT(*)</code> クエリを別途実行することが推奨される。<br> | |||
<br> | |||
<syntaxhighlight lang="mysql"> | |||
-- 推奨 : COUNT(*)を使用 | |||
SELECT COUNT(*) AS total FROM users WHERE status = 'active'; | |||
SELECT * FROM users WHERE status = 'active' LIMIT 10; | |||
</syntaxhighlight> | |||
<br><br> | |||
{{#seo: | {{#seo: | ||
2026年2月5日 (木) 14:04時点における最新版
概要
LIMIT 句は、MySQLのSQLクエリの一部として使用されており、取得するレコードの数を制限するためのものである。
大量のレコードがあるテーブルから特定の数のレコードを取得する場合に使用する。
これにより、不要なレコードの読み込みを避けて、パフォーマンスを向上させることができる。
LIMIT 句は SELECT 文、DELETE 文で使用することができ、OFFSET 句と組み合わせることで、取得開始位置を指定することも可能である。
ページネーション機能の実装においては、オフセットベース方式とカーソルベース方式の2つのアプローチが存在する。
大規模なデータセットを扱う場合、適切なインデックス設計とページネーション方式の選択がパフォーマンスに大きく影響する。
MySQL 8.0以降では、ウィンドウ関数との組み合わせにより、より高度なデータ取得が可能となっている。
また、ORDER BY 句と組み合わせることにより、特定の順序でレコードを取得したり、ランダムなレコードを取得したりすることもできる。
システム変数 max_allowed_packet の設定値にも注意が必要であり、大量のレコードを取得する場合には適切な値に調整する必要がある。
LIMITの基本構文
LIMIT 句は、SELECT 文の最後に記述する。
SELECT <* または カラム名> FROM <テーブル名> LIMIT <数値>;
-- 例: hogeテーブルから最初の5件のレコードを取得する
SELECT * FROM hoge LIMIT 5;
例えば、ユーザにランダムな商品を表示する場合、ORDER BY 句 と RAND() 関数と組み合わせて使用する。
SELECT * FROM <テーブル名> ORDER BY RAND() LIMIT <数値>;
新しい順にレコードを取得する場合、ORDER BY 句 と DESC キーワードを組み合わせて使用する。
SELECT * FROM <テーブル名> ORDER BY <カラム名> DESC LIMIT <数値>;
※注意
LIMIT句のみを使用する場合、レコードの取得順序は保証されない。
特定の順序でレコードを取得する場合は、必ずORDER BY句と組み合わせる必要がある。
OFFSET句
OFFSET 句は、LIMIT 句と組み合わせて、取得開始位置を指定するために使用される。
OFFSET構文
OFFSET 句を使用することにより、指定した位置からレコードを取得することができる。
-- 標準構文
SELECT * FROM <テーブル名> LIMIT <row_count> OFFSET <offset>;
-- 省略構文
SELECT * FROM <テーブル名> LIMIT <offset>, <row_count>;
-- 例: 10件目から5件のレコードを取得
SELECT * FROM users LIMIT 5 OFFSET 10;
SELECT * FROM users LIMIT 10, 5; -- 上と同じ結果
※注意
省略構文では、引数の順序が逆になることに注意が必要である。
引数の仕様
LIMIT句とOFFSET句の引数には、以下に示す仕様が存在する。
- 非負の整数定数を指定する必要がある。
- プリペアドステートメントでは、プレースホルダー (?) を使用することができる。
- ストアドプロシージャ内では、整数値の変数またはパラメータを使用することができる。
-- プリペアドステートメントでの使用例
PREPARE stmt FROM 'SELECT * FROM users LIMIT ? OFFSET ?';
SET @limit = 10;
SET @offset = 20;
EXECUTE stmt USING @limit, @offset;
特定位置から全行を取得
特定の位置から残りの全ての行を取得する場合は、大きな数値を指定する。
-- 95行目以降の全レコードを取得
SELECT * FROM users LIMIT 95, 18446744073709551615;
LIMIT 0の用途
LIMIT 0 は、実際のデータを取得せずに、以下に示す用途で使用される。
- クエリ構文の妥当性確認
- テーブルのメタデータ取得 (列名、データ型等)
- クエリの実行計画の確認
-- メタデータのみを取得
SELECT * FROM users LIMIT 0;
レコード取得の制限
特定のアプリケーションでは、ユーザがレコードを取得する場合に、一定の制限を設ける場合がある。
これにより、以下に示すようなメリットがある。
- サーバへの過度な負荷を防ぐ。
- 情報漏洩を制限できる。
- サービス拒否攻撃 (DoS) の影響を軽減できる。
- ユーザエクスペリエンスの向上。
必要な数のみレコードを取得することにより、サーバのリソースを効果的に使用できる。
MySQLの設定とLIMIT句
システム変数 max_allowed_packet は、1度に送受信できるデータの最大サイズを制御することができる。
ただし、この値が小さすぎる場合、LIMIT 句を使用して大量のレコードを取得する時にエラーが発生する可能性がある。
適切な値にすることにより、スムーズに大量のレコードを取得することができる。
ページネーションの実装
ページネーションは、大量のデータを複数のページに分割して表示する手法である。
MySQLでは、主に2つの方式が存在する。
オフセットベースのページネーション
最も基本的なページネーション方式であり、OFFSET 句を使用して実装する。
-- 1ページ目 (0〜9件目)
SELECT * FROM products ORDER BY id LIMIT 10 OFFSET 0;
-- 2ページ目 (10〜19件目)
SELECT * FROM products ORDER BY id LIMIT 10 OFFSET 10;
-- 3ページ目 (20〜29件目)
SELECT * FROM products ORDER BY id LIMIT 10 OFFSET 20;
※注意
この方式は、大きなOFFSET値を使用する場合にパフォーマンスが低下する。
決定論的なソートの重要性
ページネーションを実装する場合、決定論的なソート (常に同じ順序を返すソート) が重要である。
複数の行が同じ値を持つ場合、ORDER BY 句の結果は不確定になる可能性がある。
これにより、ページをまたいで同じレコードが表示されたり、レコードが欠落したりする問題が発生する。
-- 不確定なソート (複数のユーザが同じcreated_atを持つ場合)
SELECT * FROM users ORDER BY created_at LIMIT 10 OFFSET 0;
-- 決定論的なソート (一意性を保証)
SELECT * FROM users ORDER BY created_at, id LIMIT 10 OFFSET 0;
主キーや一意なカラムを ORDER BY 句に追加することにより、決定論的なソートを実現できる。
カーソルベースのページネーション (キーセット方式)
カーソルベースのページネーションは、OFFSET 句を使用せず、WHERE 条件でカーソル位置を指定する方式である。
この方式は、大規模なデータセットにおいて高いパフォーマンスを発揮する。
-- 1ページ目
SELECT * FROM products ORDER BY id LIMIT 10;
-- 2ページ目 (最後に取得したidが100の場合)
SELECT * FROM products WHERE id > 100 ORDER BY id LIMIT 10;
-- 3ページ目 (最後に取得したidが110の場合)
SELECT * FROM products WHERE id > 110 ORDER BY id LIMIT 10;
カーソルベース方式の利点
- O(log N) の時間計算量であり、大規模なデータセットでも高速である。
- 行の削除に強い。(他のユーザがレコードを削除しても、ページのずれが発生しにくい)
- インデックスを効果的に活用できる。
カーソルベース方式の欠点
- 特定のページ番号に直接アクセスすることができない。
- 前のページに戻ることが複雑になる。
- 実装がやや複雑である。
複数列でのカーソルベースページネーション
複数の列でソートする場合のカーソルベースページネーションの実装例を以下に示す。
-- created_atとidでソートする場合
-- 1ページ目
SELECT * FROM users ORDER BY created_at DESC, id DESC LIMIT 10;
-- 2ページ目 (最後に取得したレコードがcreated_at='2024-01-01', id=100の場合)
SELECT * FROM users
WHERE (created_at < '2024-01-01')
OR (created_at = '2024-01-01' AND id < 100)
ORDER BY created_at DESC, id DESC
LIMIT 10;
デファードジョイン方式
デファードジョイン方式は、オフセットベースとカーソルベースの利点を組み合わせた手法である。
この方式では、まずインデックスを使用して必要な主キーのみを取得し、その後にJOINで全てのカラムを取得する。
-- 通常のオフセットベース (非効率)
SELECT * FROM large_table ORDER BY created_at LIMIT 10 OFFSET 100000;
-- デファードジョイン方式 (効率的)
SELECT t.*
FROM large_table t
INNER JOIN (
SELECT id
FROM large_table
ORDER BY created_at
LIMIT 10 OFFSET 100000
) AS subquery USING (id);
この方式により、大きな OFFSET 値を使用する場合でも、パフォーマンスの低下を抑えることができる。
パフォーマンス最適化
LIMIT 句を使用する場合、パフォーマンスを最適化するための手法が存在する。
大きなOFFSET値の問題点
OFFSET 句に大きな値を指定する場合、MySQLは指定された位置までの全ての行を読み取る必要がある。
これにより、時間計算量がO(N)となり、OFFSET 値が大きいほどパフォーマンスが低下する。
-- 100万件目から10件を取得する場合、
-- MySQLは100万件を読み取ってから結果を返す
SELECT * FROM large_table ORDER BY id LIMIT 10 OFFSET 1000000;
このような場合、カーソルベースのページネーションやデファードジョイン方式を使用することが推奨される。
カバリングインデックスの活用
カバリングインデックスは、クエリで必要な全てのカラムを含むインデックスである。
カバリングインデックスを使用することにより、テーブルデータにアクセスせずにインデックスのみでクエリを処理できる。
-- カバリングインデックスを作成
CREATE INDEX idx_covering ON users (status, created_at, id, name);
-- カバリングインデックスを活用したクエリ
SELECT id, name, created_at
FROM users
WHERE status = 'active'
ORDER BY created_at DESC
LIMIT 10;
EXPLAIN コマンドを使用して、クエリがカバリングインデックスを使用しているかを確認できる。
ORDER BY + LIMIT最適化
MySQLは、ORDER BY 句 と LIMIT 句を組み合わせたクエリに対して、特別な最適化を行う。
適切なインデックスが存在する場合、MySQLは全ての行をソートせずに、必要な数の行のみを処理する。
-- インデックスを作成
CREATE INDEX idx_created_at ON users (created_at);
-- 最適化されたクエリ
SELECT * FROM users ORDER BY created_at DESC LIMIT 10;
この最適化により、大規模なテーブルでも高速にクエリを実行できる。
InnoDBでの主キーの自動追加
InnoDBストレージエンジンでは、ORDER BY 句に主キーが自動的に追加される場合がある。
これにより、決定論的なソートが自動的に保証されることがある。
ただし、この動作に依存せず、明示的に主キーを指定することが推奨される。
-- 明示的に主キーを指定
SELECT * FROM users ORDER BY status, id LIMIT 10;
サブクエリ・JOIN・UNIONでのLIMIT句
LIMIT 句は、サブクエリ、JOIN、UNION と組み合わせて使用することができる。
サブクエリでのLIMIT
サブクエリ内で LIMIT 句を使用することにより、サブクエリの結果を制限できる。
-- サブクエリで最新の10件の注文を取得し、その合計金額を計算
SELECT SUM(amount) AS total
FROM (
SELECT amount
FROM orders
ORDER BY created_at DESC
LIMIT 10
) AS recent_orders;
JOINでのLIMIT使用時の注意
JOIN を使用する場合、LIMIT 句は結合後の結果に適用される。
-- JOINした後の結果から10件を取得
SELECT users.name, orders.amount
FROM users
INNER JOIN orders ON users.id = orders.user_id
ORDER BY orders.created_at DESC
LIMIT 10;
各テーブルから10件ずつ取得する場合は、サブクエリを使用する必要がある。
-- 各ユーザの最新10件の注文を取得
SELECT users.name, recent_orders.amount
FROM users
INNER JOIN (
SELECT user_id, amount, created_at
FROM orders
ORDER BY created_at DESC
LIMIT 10
) AS recent_orders ON users.id = recent_orders.user_id;
UNIONでのLIMIT
UNION を使用する場合、LIMIT 句は個別の SELECT 文または全体の結果に適用できる。
-- 各SELECTで10件ずつ取得し、結果を結合
(SELECT * FROM users WHERE status = 'active' ORDER BY created_at DESC LIMIT 10)
UNION ALL
(SELECT * FROM users WHERE status = 'inactive' ORDER BY created_at DESC LIMIT 10);
-- 結合後の全体から10件を取得
(SELECT * FROM users WHERE status = 'active')
UNION ALL
(SELECT * FROM users WHERE status = 'inactive')
ORDER BY created_at DESC
LIMIT 10;
※注意
個別の SELECT 文に LIMIT 句を適用する場合は、括弧で囲む必要がある。
UPDATE文 / DELETE文でのLIMIT句
LIMIT 句は、DELETE 文で使用することができるが、UPDATE 文では直接使用することができない。
DELETE文でのLIMIT
DELETE 文で LIMIT 句を使用することにより、削除する行数を制限できる。
-- 古いログレコードを10件削除
DELETE FROM logs
ORDER BY created_at ASC
LIMIT 10;
バッチ削除パターン
大量のレコードを削除する場合、一度に削除せずにバッチで削除することが推奨される。
これにより、テーブルロックの時間を短縮し、レプリケーションへの影響を最小限に抑えることができる。
-- 古いログを1000件ずつ削除
DELIMITER $$
CREATE PROCEDURE delete_old_logs()
BEGIN
DECLARE rows_affected INT DEFAULT 1;
WHILE rows_affected > 0 DO
DELETE FROM logs
WHERE created_at < DATE_SUB(NOW(), INTERVAL 90 DAY)
ORDER BY created_at ASC
LIMIT 1000;
SET rows_affected = ROW_COUNT();
-- レプリケーションの負荷を軽減するために短い待機時間を設ける
DO SLEEP(0.1);
END WHILE;
END$$
DELIMITER ;
制限事項
LIMIT 句を DELETE 文で使用する場合、以下に示す制限事項が存在する。
- 単一テーブルのみサポートされる。(マルチテーブル削除では使用不可)
- サブクエリとの併用には制限がある。
-- エラー: マルチテーブル削除ではLIMITを使用できない
DELETE users, orders
FROM users
INNER JOIN orders ON users.id = orders.user_id
WHERE users.status = 'deleted'
LIMIT 10; -- エラー
UPDATE文での代替方法
UPDATE 文では LIMIT 句を直接使用できないが、サブクエリを使用することで同様の効果を得られる。
-- サブクエリを使用して最初の10件を更新
UPDATE users
SET status = 'processed'
WHERE id IN (
SELECT id FROM (
SELECT id
FROM users
WHERE status = 'pending'
ORDER BY created_at ASC
LIMIT 10
) AS tmp
);
※注意
サブクエリを2重にネストしているのは、MySQLの制限により、更新対象のテーブルを直接サブクエリで参照できないためである。
MySQL 8.0以降の機能
MySQL 8.0では、ウィンドウ関数が導入され、LIMIT 句と組み合わせることで高度なデータ取得が可能になった。
ウィンドウ関数との組み合わせ
ウィンドウ関数を使用することにより、各グループ内での順位付けや集計を行うことができる。
-- ウィンドウ関数で順位を付けてから上位10件を取得
SELECT *
FROM (
SELECT
id,
name,
department,
salary,
RANK() OVER (ORDER BY salary DESC) AS salary_rank
FROM employees
) AS ranked_employees
WHERE salary_rank <= 10;
ROW_NUMBER関数を使用したページネーション
ROW_NUMBER 関数を使用することにより、決定論的なページネーションを定義できる。
この方式は、決定論的なソートを保証しつつ、特定のページ範囲を取得することができる。
-- ROW_NUMBER関数を使用したページネーション
SELECT *
FROM (
SELECT
id,
name,
created_at,
ROW_NUMBER() OVER (ORDER BY created_at DESC, id DESC) AS row_num
FROM users
) AS numbered_users
WHERE row_num BETWEEN 11 AND 20; -- 2ページ目 (11〜20件目)
RANK()、DENSE_RANK()の使用
RANK 関数 や DENSE_RANK 関数を使用することにより、同順位を考慮したランキングを実装できる。
-- 各部門の上位5名を取得
SELECT *
FROM (
SELECT
id,
name,
department,
salary,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank
FROM employees
) AS ranked_employees
WHERE dept_rank <= 5;
PARTITION BYの活用
PARTITION BY 句を使用することにより、グループごとに LIMIT のような制限を実現できる。
-- 各カテゴリの最新3件の商品を取得
SELECT *
FROM (
SELECT
id,
name,
category,
created_at,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY created_at DESC) AS row_num
FROM products
) AS numbered_products
WHERE row_num <= 3;
FETCH FIRST構文の非サポート
SQL標準の FETCH FIRST 構文は、MySQL 8.0でもサポートされていない。
-- エラー : MySQLではFETCH FIRST構文はサポートされていない
SELECT * FROM users
ORDER BY created_at DESC
FETCH FIRST 10 ROWS ONLY; -- エラー
-- MySQLでは以下を使用する
SELECT * FROM users
ORDER BY created_at DESC
LIMIT 10;
ROW_COUNT関数との関係
ROW_COUNT 関数は、直前のSQL文が影響を与えた行数を返す関数である。
ROW_COUNT関数の動作
ROW_COUNT 関数は、INSERT、UPDATE、DELETE 文で影響を受けた行数を返す。
-- DELETE文で削除された行数を確認
DELETE FROM logs
WHERE created_at < DATE_SUB(NOW(), INTERVAL 90 DAY)
LIMIT 1000;
SELECT ROW_COUNT(); -- 削除された行数が返される
SELECT文では-1を返す
SELECT 文を実行した場合、ROW_COUNT 関数は -1 を返す。
SELECT 文の結果行数を取得する場合は、FOUND_ROWS 関数を使用する必要があるが、これはMySQL 8.0で非推奨となっている。
SELECT * FROM users LIMIT 10;
SELECT ROW_COUNT(); -- -1が返される
バッチ処理での活用
ROW_COUNT 関数は、バッチ削除やバッチ更新の処理で活用できる。
-- バッチ削除の例
DELIMITER $$
CREATE PROCEDURE batch_delete()
BEGIN
DECLARE affected_rows INT;
DECLARE total_deleted INT DEFAULT 0;
REPEAT
DELETE FROM logs
WHERE created_at < DATE_SUB(NOW(), INTERVAL 90 DAY)
LIMIT 1000;
SET affected_rows = ROW_COUNT();
SET total_deleted = total_deleted + affected_rows;
DO SLEEP(0.1);
UNTIL affected_rows = 0 END REPEAT;
SELECT total_deleted AS total_deleted_rows;
END$$
DELIMITER ;
SQL_CALC_FOUND_ROWS、FOUND_ROWS関数の非推奨
MySQL 8.0.17以降、SQL_CALC_FOUND_ROWS クエリ修飾子 と FOUND_ROWS 関数は非推奨となった。
-- 非推奨 : SQL_CALC_FOUND_ROWSの使用
SELECT SQL_CALC_FOUND_ROWS * FROM users LIMIT 10;
SELECT FOUND_ROWS(); -- 全体の行数を返す (非推奨)
代わりに、COUNT(*) クエリを別途実行することが推奨される。
-- 推奨 : COUNT(*)を使用
SELECT COUNT(*) AS total FROM users WHERE status = 'active';
SELECT * FROM users WHERE status = 'active' LIMIT 10;