Оптимизация индекса mysql для упорядоченного запроса диапазона - PullRequest
5 голосов
/ 04 декабря 2011

Мне нужна помощь с некоторыми оскорбительными запросами mysql, которые я сейчас выполняю на своем сервере. Моя цель - показать самые дорогие предметы на ebay, время окончания которых меньше месяца назад.

Я использую MySQL 5.1.

Мой запрос выглядит следующим образом ('ebay_items' имеет ~ 350 000 строк):

explain SELECT `ebay_items`.* FROM `ebay_items` 
WHERE (endtime > NOW()-INTERVAL 1 MONTH) ORDER BY price desc\G;

выходы:

*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: ebay_items
type: range
possible_keys: endtime
key: endtime
key_len: 9
ref: NULL
rows: 71760
Extra: Using where; Using filesort
1 row in set (0.00 sec)

Результатом этого запроса является дорогая файловая сортировка с использованием 71760 строк.

show indexes on ebay_items;

выходы (я только включил рассматриваемый индекс, 'endtime'):

*************************** 7. row ***************************
Table: ebay_items
Non_unique: 1
Key_name: endtime
Seq_in_index: 1
Column_name: endtime
Collation: A
Cardinality: 230697
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment: 
*************************** 8. row ***************************
Table: ebay_items
Non_unique: 1
Key_name: endtime
Seq_in_index: 2
Column_name: price
Collation: A
Cardinality: 230697
Sub_part: NULL
Packed: NULL
Null: YES
Index_type: BTREE
Comment: 

Используется только ключ 'endtime' составного индекса endtime (endtime, price). Насколько я знаю, MySQL не будет эффективно использовать составной индекс при работе с запросом диапазона в сочетании с предложением order by.

Кто-нибудь нашел хорошую тренировку по этим вопросам? Прежде всего, я бы хотел решить эту проблему на уровне БД (с помощью более разумного использования индексов или изменений схемы), но я открыт для предложений.

Один из способов избежать запроса диапазона - сделать так, чтобы фоновая задача периодически повторялась каждые X часов и помечала поле типа enum на ebay_items как «<1 день назад», «<1 неделя», «<1 месяц». старый и т. д. Я надеялся решить проблему более чистым способом. </p>

Есть ли способ выполнить запрос диапазона MySQL с предложением order by, эффективными способами?

Огромное спасибо за помощь!

Редактировать: Коханьи Роберт сделал хорошее замечание, что я должен прояснить точную проблему, которая у меня возникла с запросом. Запрос приводит к тому, что дисковый ввод-вывод будет привязан на время. Если несколько из этих запросов выполняются одновременно, процессы резервируются и машина блокируется. Я предполагаю, что файловая сортировка потребляет данные ввода / вывода

Я должен также упомянуть, что в таблице используется движок MyISAM. Будет ли более производительным и менее интенсивным ввод / вывод использовать механизм InnoDB? Еще раз спасибо.

1 Ответ

8 голосов
/ 04 декабря 2011

Введение

Мне нравится ваш вопрос, поэтому я немного поиграл с MySQL и попытался найти источник проблемы. Для этого я создал несколько тестов.

Данные

Я сгенерировал 100.000 строк образцов данных, используя инструмент под названием Генератор случайных данных (я думаю, документация немного устарела, но она работает). Файл конфигурации, который я передал gendata.pl, выглядит следующим образом.

$tables = {
  rows => [100000],
  names => ['ebay_items'],
  engines => ['MyISAM'],
  pk => ['int auto_increment']
};

$fields = {
  types => ['datetime', 'int'],
  indexes => [undef]
};

$data = {
  numbers => [
    'tinyint unsigned', 
    'smallint unsigned', 
    'smallint unsigned',
    'mediumint unsigned'
  ],
  temporals => ['datetime']
}; 

Я выполнил два отдельных пакета тестов: один, который использовал таблицу MyISAM, и другой, который использовал InnoDB. (Так что в основном вы заменяете MyISAM на InnoDB в приведенном выше фрагменте.)

Таблица

Инструмент создает таблицу, в которой столбцы называются pk, col_datetime и col_int. Я переименовал их, чтобы они соответствовали столбцам вашей таблицы. Итоговая таблица чуть ниже.

+---------+----------+------+-----+---------+----------------+
| Field   | Type     | Null | Key | Default | Extra          |
+---------+----------+------+-----+---------+----------------+
| endtime | datetime | YES  | MUL | NULL    |                |
| id      | int(11)  | NO   | PRI | NULL    | auto_increment |
| price   | int(11)  | YES  | MUL | NULL    |                |
+---------+----------+------+-----+---------+----------------+

Индексы

Инструмент не создает индексов, потому что мне понравилось создавать их вручную.

CREATE INDEX `endtime` ON `ebay_items` (endtime, price);
CREATE INDEX `price` ON `ebay_items` (price, endtime);
CREATE INDEX `endtime_only` ON `ebay_items` (endtime);
CREATE INDEX `price_only` ON `ebay_items` (price);

Запрос

запрос, который я использовал.

SELECT `ebay_items`.* 
FROM `ebay_items`  
FORCE INDEX (`endtime|price|endtime_only|price_only`)
WHERE (`endtime` > '2009-01-01' - INTERVAL 1 MONTH) 
ORDER BY `price` DESC

(Четыре разных запроса с использованием одного из индексов. Я использовал 2009-01-01 вместо NOW(), потому что инструмент, похоже, генерирует даты около 2009 года.)

Объясните

Вот вывод EXPLAIN для вышеприведенного запроса для каждого индекса таблицы MyISAM (вверху) и InnoDB (внизу).

1044 * время окончания * id: 1 select_type: SIMPLE table: ebay_items type: range possible_keys: endtime key: endtime key_len: 9 ref: NULL rows: 25261 Extra: Using where; Using filesort id: 1 select_type: SIMPLE table: ebay_items type: range possible_keys: endtime key: endtime key_len: 9 ref: NULL rows: 21026 Extra: Using where; Using index; Using filesort цена

           id: 1
  select_type: SIMPLE
        table: ebay_items
         type: index
possible_keys: NULL
          key: price
      key_len: 14
          ref: NULL
         rows: 100000
        Extra: Using where

         id: 1
  select_type: SIMPLE
        table: ebay_items
         type: index
possible_keys: NULL
          key: price
      key_len: 14
          ref: NULL
         rows: 100226
        Extra: Using where; Using index
* * Endtime_only тысяча сорок-девять
           id: 1
  select_type: SIMPLE
        table: ebay_items
         type: range
possible_keys: endtime_only
          key: endtime_only
      key_len: 9
          ref: NULL
         rows: 11666
        Extra: Using where; Using filesort

          id: 1
  select_type: SIMPLE
        table: ebay_items
         type: range
possible_keys: endtime_only
          key: endtime_only
      key_len: 9
          ref: NULL
         rows: 21270
        Extra: Using where; Using filesort

price_only

           id: 1
  select_type: SIMPLE
        table: ebay_items
         type: index
possible_keys: NULL
          key: price_only
      key_len: 5
          ref: NULL
         rows: 100000
        Extra: Using where

           id: 1
  select_type: SIMPLE
        table: ebay_items
         type: index
possible_keys: NULL
          key: price_only
      key_len: 5
          ref: NULL
         rows: 100226
        Extra: Using where

Исходя из этого, я решил использовать индекс endtime_only для своих тестов, потому что мне приходилось также выполнять запросы к MyISAM и к таблице InnoDB. Но, как вы видите, наиболее логичный индекс endtime кажется лучшим.

Test

Для проверки эффективности запроса (относительно сгенерированной активности ввода-вывода) с таблицами MyISAM и InnoDB я написал следующую простую Java-программу.

static final String J = "jdbc:mysql://127.0.0.1:3306/test?user=root&password=root";
static final String Q = "SELECT * FROM ebay_items FORCE INDEX (endtime_only) WHERE (endtime > '2009-01-01'-INTERVAL 1 MONTH) ORDER BY price desc;";

public static void main(String[] args) throws InterruptedException {
  for (int i = 0; i < 1000; i++)
    try (Connection c = DriverManager.getConnection(J);
        Statement s = c.createStatement()) {
      TimeUnit.MILLISECONDS.sleep(10L);
      s.execute(Q);
    } catch (SQLException ex) {
      ex.printStackTrace();
    }
}

Настройка

Я запускал двоичный файл Windows MySQL 5.5 на ноутбуке Dell Vostro 1015, Intel Core Duo T6670 с частотой 2,20 ГГц, 4 ГБ оперативной памяти. Программа Java взаимодействовала с процессом сервера MySQL через TCP / IP.

Государство

Я зафиксировал состояние процесса mysqld до и после выполнения моих тестов для таблицы с использованием MyISAM и InnoDB (с использованием Process Explorer ).

До

mysqld Performance tab

mysqld Disk and Network tab

вечерний MyISAM

mysqld Performance tab / MyISAM

mysqld Disk and Network tab / MyISAM

вечерний InnoDB

mysqld Performance tab / InnoDB

mysqld Disk and Network tab / InnoDB

Заключение

По сути, эти два прогона отличаются только количеством отдельных операций чтения ввода-вывода, которое довольно велико, когда в таблице использовался механизм MyISAM. Оба теста длились по 50–60 секунд. Максимальная загрузка процессора в случае с движком MyISAM составляла около 42 процентов, а при использовании InnoDB - около 38.

Я не совсем уверен, каковы последствия большого числа операций чтения ввода-вывода, но в этом случае чем меньше, тем лучше (вероятно). Если у вас есть еще несколько столбцов в вашей таблице (кроме того, который вы указали) и у вас есть конфигурация MySQL не по умолчанию (относительно размеров буфера и тому подобное), возможно, MySQL будет использовать дисковые ресурсы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...