MariaDB講座 / 結果を変えずに日付検索・ソート・ページ分割を改善する

問い合わせ改善は、必要な行を保ちながら条件・順序・ページ移動を適切なアクセス経路へ合わせる作業です。主キーと表示順の同値性を先に確認し、性能は実際の計画と代表データで評価します。

速さより先に同じ結果かを確かめる

境界時刻や同時刻の行を落とした問い合わせは、同値な改善ではありません。期間は開始以上・次境界未満、順序は一意な補助キー、ページ方式は移動と整合性の要件に合わせます。

ORDER BYとキーセット、前後の実行計画で結果と費用を分けて確認します。部分一致を前方一致へ変えるなど、要求自体を削って速くする方法は別の検索です。

期間順の表と索引を準備する

course_userでshopcourseに接続し、SELECT VERSION(), DATABASE();とSHOW TABLES;で対象を確認します。以下は独立した実習表の作成・追加を含む完全な例で、UTF-8ファイルとして実行できます。同名表があれば定義と行を調べ、必要なものを保存してから判断します。

USE shopcourse;
CREATE TABLE c41_customers (
 customer_id INT NOT NULL PRIMARY KEY,
 customer_name VARCHAR(50) NOT NULL,
 city VARCHAR(30) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE c41_products (
 product_id INT NOT NULL PRIMARY KEY,
 product_name VARCHAR(100) NOT NULL,
 category VARCHAR(30) NOT NULL,
 price DECIMAL(10,2) NOT NULL CHECK (price >= 0),
 stock INT NOT NULL CHECK (stock >= 0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE c41_orders (
 order_id INT NOT NULL PRIMARY KEY,
 customer_id INT NOT NULL,
 ordered_at DATETIME NOT NULL,
 status VARCHAR(20) NOT NULL,
 shipping_fee DECIMAL(10,2) NOT NULL DEFAULT 1000,
 CONSTRAINT fk_c41_order_customer FOREIGN KEY (customer_id)
  REFERENCES c41_customers(customer_id) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE c41_items (
 order_id INT NOT NULL,
 product_id INT NOT NULL,
 quantity INT NOT NULL CHECK (quantity > 0),
 unit_price DECIMAL(10,2) NOT NULL CHECK (unit_price >= 0),
 PRIMARY KEY (order_id, product_id),
 CONSTRAINT fk_c41_item_order FOREIGN KEY (order_id)
  REFERENCES c41_orders(order_id) ON DELETE RESTRICT,
 CONSTRAINT fk_c41_item_product FOREIGN KEY (product_id)
  REFERENCES c41_products(product_id) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO c41_customers VALUES
 (1,'김민수','서울'),(2,'이서연','부산'),
 (3,'박지우','서울'),(4,'최하늘','대전');
INSERT INTO c41_products VALUES
 (1,'노트','문구',3500,20),(2,'볼펜','문구',1500,50),
 (3,'머그컵','생활',8000,12),(4,'스탠드','생활',25000,5);
INSERT INTO c41_orders
 (order_id,customer_id,ordered_at,status,shipping_fee) VALUES
 (101,1,'2026-01-10 10:00:00','paid',1000),
 (102,1,'2026-01-20 11:00:00','paid',1000),
 (103,2,'2026-02-05 12:00:00','paid',1000),
 (104,3,'2026-02-10 13:00:00','canceled',1000);
INSERT INTO c41_items VALUES
 (101,1,2,3500),(101,2,3,1500),(102,3,1,8000),
 (103,1,1,3500),(103,3,2,8000),(104,2,1,1500);

文字列は原文の韓国語サンプルを維持しています。商品1~4はノート、ペン、マグカップ、ライト、顧客1・3はソウル、2は釜山、4は大田です。UTF-8で保存・接続し、安定したIDと数値で照合します。

CREATE INDEX idx_c41_time ON c41_orders(ordered_at,order_id);

四注文では全走査も合理的です。ここは行と境界の正しさを学ぶ例で、規模による効果は別の代表データで測定します。

日付関数と明示的な範囲を比べる

同じ一日を二通りで選びます。

SELECT order_id FROM c41_orders WHERE DATE(ordered_at)='2026-01-10'
ORDER BY order_id;
SELECT order_id FROM c41_orders
WHERE ordered_at>='2026-01-10' AND ordered_at<'2026-01-11' ORDER BY order_id;

どちらも101です。範囲は時刻や小数秒を含む一日を表します。MariaDB 11.1以降では対応するDATE・YEAR条件を索引範囲へ変換できるため、「列の関数は必ず索引を使えない」とは言いません。明示範囲は意図が読みやすくても、それだけで高速の証拠ではありません。実際のEXPLAINでキーと範囲を比較します。

時刻とIDの両方で次ページを選ぶ

降順の例です。

SELECT order_id,ordered_at FROM c41_orders
ORDER BY ordered_at DESC,order_id DESC LIMIT 2;
SELECT order_id,ordered_at FROM c41_orders
WHERE ordered_at<'2026-02-05 12:00:00'
 OR(ordered_at='2026-02-05 12:00:00' AND order_id<103)
ORDER BY ordered_at DESC,order_id DESC LIMIT 2;

最初は104・103、103の時刻とIDを使う次は102・101です。同時刻を落とさないためID条件も必要で、方向を変えたら不等号も見直します。キーセットは深いOFFSETを避け得ますが、任意番号のページ移動とは違います。同時更新の間に一覧を固定するには別の方針が必要です。

同時刻の境界を取消可能な実験で確認する

同じ接続で次を実行します。

START TRANSACTION;
UPDATE c41_orders SET ordered_at='2026-02-05 12:00:00' WHERE order_id=104;
SELECT order_id,ordered_at FROM c41_orders
ORDER BY ordered_at DESC,order_id DESC LIMIT 1;
SELECT order_id,ordered_at FROM c41_orders
WHERE ordered_at<'2026-02-05 12:00:00'
   OR (ordered_at='2026-02-05 12:00:00' AND order_id<104)
ORDER BY ordered_at DESC,order_id DESC LIMIT 1;
ROLLBACK;

一ページ目は104、次は同時刻の103です。時刻だけを境界にすると飛ばす場合があります。ROLLBACK後に104の元時刻も確認します。データを書き換えたまま比較しないでください。

投影列・検索模様・条件を記録する

SELECT *を減らすと大きい文章の転送などを避けられますが、対象行を減らすこととは別です。カバリングの可能性も計画で確認します。通常のB-treeでは前方一致と先頭ワイルドカードの部分一致で可能な経路が違うので、検索意図と照合順序を先に決めます。

両SQL、返るキー、版、索引、件数と分布を記録し、キャッシュ差を切り分けます。関連する別のソートやページ要件も同じ経路へ依存していないかを調べます。

やり直すときは、実際のDB・定義・行を確認し、必要なデータを保存して、この記事の削除可能な表だけを子から親の順に消します。DROP TABLEは定義とデータを削除し、通常のROLLBACKでは戻せません。

DROP TABLE shopcourse.c41_items;
DROP TABLE shopcourse.c41_orders;
DROP TABLE shopcourse.c41_products;
DROP TABLE shopcourse.c41_customers;

関連する公式リファレンス