Оптимизация SQL-запроса, чтобы избежать полного сканирования таблицы - PullRequest
5 голосов
/ 02 декабря 2010

Рассмотрим следующий запрос:

SELECT * FROM Transactions
WHERE day(Stamp - interval 3 hour) = 1;

Столбец Штамп в таблице Транзакции является TIMESTAMP, и для него имеется индекс. Как я могу изменить этот запрос, чтобы избежать полного сканирования таблицы? (то есть, используя Штамп вне функции day () )

Спасибо!

Ответы [ 6 ]

8 голосов
/ 02 декабря 2010

Вот как я бы это сделал:

добавьте несколько дополнительных полей: ГОД, МЕСЯЦ, ДЕНЬ или даже ЧАС, МИНУТА в зависимости от ожидаемого трафика.Затем создайте триггер для заполнения дополнительных полей, возможно, предварительно вычтя 3-часовой интервал.Наконец, создайте индекс для дополнительных полей.

1 голос
/ 02 декабря 2010

Вы часто можете переписать функцию, чтобы у вас было что-то похожее на WHERE Stamp=XXXX, а XXXX - это какое-то выражение.Вы можете создать серию операторов BETWEEN для каждого месяца, WHERE Stamp BETWEEN timestamp('2010-01-01 00:00:00') AND timestamp ('2010-01-01 23:59:59') OR Stamp BETWEEN ..., но я не уверен, что в этом случае будет использоваться индекс.Я бы построил столбец, который был днем ​​месяца, как подсказывает @petr.

1 голос
/ 02 декабря 2010

Если цель состоит просто в том, чтобы избежать полного сканирования таблицы, и у вас есть ПЕРВИЧНЫЙ КЛЮЧ (например, с именем PK) для транзакций, рассмотрите возможность добавления покрывающего индекса

ALTER TABLE Transactions ADD INDEX cover_1 (PK, Stamp)

Затем

SELECT * FROM Transactions WHERE PK IN (SELECT PK FROM Transactions
WHERE day(Stamp - interval 3 hour) = 1
 )

Этот запрос не должен использовать полное сканирование таблицы (однако оптимизатор может решить использовать полное сканирование, если число строк в таблице невелико или по какой-либо другой статистической причине :))

Лучше может быть использование временной таблицывместо подзапроса.

0 голосов
/ 02 декабря 2010

Немного переработан ответ Петра, чтобы избежать предложения IN и сделать его для MyISAM или InnoDB.

Для MyISAM

ALTER TABLE Transactions ADD INDEX cover_1 (PK, Stamp)

Или, для InnoDB, где PK неявно включен в каждый индекс,

ALTER TABLE Transactions ADD INDEX Stamp (Stamp)

Тогда

SELECT * 
FROM Transactions LEFT JOIN
  (
  SELECT PK 
  FROM Transactions 
  WHERE DAYOFMONTH(Stamp - interval 3 hour) = 1
  ) a ON Transactions.PK=a.PK

Подзапрос будет иметь только индексное выполнение, а внешний запрос будет извлекать только строки из таблицы, через которую прошел a.PK.

0 голосов
/ 02 декабря 2010

Если я правильно понимаю, вы, в принципе, хотите вернуть все строки, где штамп стоит первым в каждом месяце (вычтя 3 часа)?Если (и это большой случай), у вас есть фиксированное окно, скажем, последние 6 месяцев, вы можете просто перечислить 6 тестов диапазона.Но, тем не менее, я не уверен, что индексированный доступ будет быстрее в любом случае.

select *
  from transactions
 where stamp between timestamp '2010-06-01 03:00:00' and timestamp '2010-06-02 02:59:59'
    or stamp between timestamp '2010-07-01 03:00:00' and timestamp '2010-07-02 02:59:59'
    or stamp between timestamp '2010-08-01 03:00:00' and timestamp '2010-08-02 02:59:59'
    or stamp between timestamp '2010-09-01 03:00:00' and timestamp '2010-09-02 02:59:59'
    or stamp between timestamp '2010-10-01 03:00:00' and timestamp '2010-10-02 02:59:59'
    or stamp between timestamp '2010-11-01 03:00:00' and timestamp '2010-11-02 02:59:59'
    or stamp between timestamp '2010-12-01 03:00:00' and timestamp '2010-12-02 02:59:59';

Внимание!Я не уверен, как работает миллисекундная часть метки времени.Возможно, вам придется дополнить его соответственно.

0 голосов
/ 02 декабря 2010

Рассчитайте желаемое значение штампа отдельно перед выполнением основного запроса, т. Е.

Шаг 1 - вычислить желаемое значение штампа

Шаг 2 - выполнить запрос, где Штамп> (расчетное значение)

Поскольку на шаге 2 нет расчетов, вы сможете использовать свой индекс.

...