Mysql

為什麼要在檢查超過 1 行的索引列上使用“LIMIT 1”查詢來確定是否存在(或不)匹配某些條件的行?

  • July 15, 2016

考慮這樣創建的表:

CREATE TABLE `someTable` (
 `id` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
 `owner_id` bigint(11) unsigned NOT NULL,
 `device_id` bigint(11) unsigned NOT NULL,
 `reviewed` tinyint(1) NOT NULL DEFAULT '1',
 `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `owner_id reviewed` (`owner_id`,`reviewed`),
KEY `device_id reviewed` (`device_id`,`reviewed`)
);

此表記錄發生在具有所有者的設備上的事件。大約有 1000 個所有者,每個所有者可以擁有 1-100 台設備。每個設備的表中可以有數百萬個事件。

我們需要兩個查詢來告訴我們:

  1. 給定的任何設備上是否owner_id有未審核的事件?
  2. 給定的事件是否device_id有未審查的事件?

我們一直在使用:

  1. SELECT 1 FROM someTable s WHERE s.reviewed = 0 AND s.owner_id = 0 LIMIT 1;
  2. SELECT 1 FROM someTable s WHERE s.reviewed = 0 AND s.device_id = 0 LIMIT 1;

但我們發現,隨著事件數量的增加,這些查詢的速度出乎意料地變慢了。對它們執行 EXPLAIN 表示我們將檢查許多行。我承認我期望它只需要檢查 1 行。我的想法是 MySQL 可以查找相應的索引('owner_id reviewed'index 或 ’ device_id reviewed'index )並立即判斷是否有任何相應的行。

我了解 EXPLAIN 的rows列只是一個估計值,對於使用 的查詢可能並不完全準確LIMIT 1,但我發現隨著數據集的增加,這些查詢的執行時間會增加到幾秒(有時是幾分鐘),這導致我相信MySQL確實在檢查不止一行。

我在這裡做錯了什麼?如何重組我的查詢(或索引)以有效地做到這一點?

我們在 Linux 上執行 MySQL 5.6.21。該表是innodb。我在這裡創建了一個簡單的 SQL 小提琴:http://sqlfiddle.com/#!9/ f0f4f1 /1

LIMIT 提供了一個進入結果集的視窗。LIMIT 1 等價於 LIMIT 0, 1;返回從偏移量 0 開始的 1 行。解釋計劃報告 LIMIT 正在執行的估計行數。但是,只應檢查一場比賽。

對 SQL Fiddle 的測試顯示查詢針對 INDEX (KEY) 執行,這應該是最快的訪問方法。我發現這些形式的查詢有時比返回常量的查詢要快。

SELECT owner_id
FROM someTable s 
WHERE reviewed = 0
AND owner_id = 0
LIMIT 1;

我會檢查儲存您數據的設備上的 I/O。I/O 爭用可能會導致大型數據庫出現問題。

我還會考慮以下形式的查詢:

SELECT MIN(event_id)
FROM someTable s 
WHERE reviewed = 0
AND owner_id = 0; 

帶有 (owner_id, review, event_id) 索引。這應該針對索引執行並且只檢查索引的一行。

嘗試使用exists運算符,該運算符應在找到與條件匹配的第一條記錄時執行搜尋並停止:

select 1 from dual where exists (
   select * from someTable
   where
       owner_id = 0
       and reviewed = 0
);

引用自:https://dba.stackexchange.com/questions/143660