Возможна ли перекрестная индексация? - PullRequest
19 голосов
/ 14 декабря 2011

Рассмотрим структуру, в которой у вас есть отношение многие-к-одному (или один-ко-многим) с условием (где, порядок и т. Д.) В обеих таблицах.Например:

CREATE TABLE tableTwo (
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    eventTime DATETIME NOT NULL,
    INDEX (eventTime)
) ENGINE=InnoDB;

CREATE TABLE tableOne (
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    tableTwoId INT UNSIGNED NOT NULL,
    objectId INT UNSIGNED NOT NULL,
    INDEX (objectID),
    FOREIGN KEY (tableTwoId) REFERENCES tableTwo (id)
) ENGINE=InnoDB;

и для примера запроса:

select * from tableOne t1 
  inner join tableTwo t2 on t1.tableTwoId = t2.id
  where objectId = '..'
  order by eventTime;

Допустим, вы индексировали tableOne.objectId и tableTwo.eventTime.Если вы объясните запрос, приведенный выше, он покажет «Использование сортировки файлов».По сути, он сначала применяет индекс tableOne.objectId, но не может применять индекс tableTwo.eventTime, потому что этот индекс предназначен для всей таблицы tableTwo (не для ограниченного набора результатов), и, следовательно, он должен выполнять сортировку вручную.

Таким образом, существует ли способ создания индекса между таблицами, чтобы не приходилось сортировать файлы при каждом получении результатов? Что-то вроде:

create index ind_t1oi_t2et on tableOne t1 
  inner join tableTwo t2 on t1.tableTwoId = t2.id 
  (t1.objectId, t2.eventTime);

Кроме того, я рассмотрел создание представления и его индексацию, но индексация для представлений не поддерживается.

Решение, к которому я склонялся, если индексация между таблицами невозможна, - это репликация условных данных в одну таблицу.В этом случае это означает, что eventTime будет реплицировано в tableOne, а многостолбцовый индекс будет установлен на tableOne.objectId и tableOne.eventTime (по сути, создание индекса вручную).Тем не менее, я подумал, что сначала должен найти опыт других людей, чтобы убедиться, что это лучший способ.

Большое спасибо!

Обновление:

Вот несколько процедур для загрузки данных испытаний и сравнения результатов:

drop procedure if exists populate_table_two;
delimiter #
create procedure populate_table_two(IN numRows int)
begin
declare v_counter int unsigned default 0;
  while v_counter < numRows do
    insert into tableTwo (eventTime) 
    values (CURRENT_TIMESTAMP - interval 0 + floor(0 + rand()*1000) minute);
    set v_counter=v_counter+1;
  end while;
end #
delimiter ;

drop procedure if exists populate_table_one;
delimiter #
create procedure populate_table_one
   (IN numRows int, IN maxTableTwoId int, IN maxObjectId int)
begin
declare v_counter int unsigned default 0;
  while v_counter < numRows do
    insert into tableOne (tableTwoId, objectId) 
      values (floor(1 +(rand() * maxTableTwoId)), 
              floor(1 +(rand() * maxObjectId)));
    set v_counter=v_counter+1;
  end while;
end #
delimiter ;

Вы можете использовать их следующим образом, чтобы заполнить 10000 строк в tableTwo и 20000 строк в tableOne (со случайными ссылками на tableOne и случайными objectId с между 1 и 5), что заняло 26,2 и 70,77 секунды соответственно для запускадля меня:

call populate_table_two(10000);
call populate_table_one(20000, 10000, 5);

Обновление 2 (проверенный триггерный SQL):

Ниже приведен проверенный и проверенный SQL, основанный на методе запуска daniHp.Это синхронизирует dateTime с tableOne при добавлении tableOne или обновлении tableTwo.Кроме того, этот метод также должен работать для отношений «многие ко многим», если столбцы условий копируются в присоединяющуюся таблицу.В моем тестировании 300 000 строк в tableOne и 200 000 строк в tableTwo скорость старого запроса с аналогичными ограничениями составляла 0,12 с, а скорость нового запроса по-прежнему равна 0,00 с.Таким образом, есть явное улучшение, и этот метод должен хорошо работать на миллионы строк и дальше.

alter table tableOne add column tableTwo_eventTime datetime;

create index ind_t1_oid_t2et on tableOne (objectId, tableTwo_eventTime);

drop TRIGGER if exists t1_copy_t2_eventTime;
delimiter #
CREATE TRIGGER t1_copy_t2_eventTime
   BEFORE INSERT ON tableOne
for each row
begin
  set NEW.tableTwo_eventTime = (select eventTime 
       from tableTwo t2
       where t2.id = NEW.tableTwoId);
end #
delimiter ;

drop TRIGGER if exists upd_t1_copy_t2_eventTime;
delimiter #
CREATE TRIGGER upd_t1_copy_t2_eventTime
   BEFORE UPDATE ON tableTwo
for each row
begin
  update tableOne 
    set tableTwo_eventTime = NEW.eventTime 
    where tableTwoId = NEW.id;
end #
delimiter ;

И обновленный запрос:

select * from tableOne t1 
  inner join tableTwo t2 on t1.tableTwoId = t2.id
  where t1.objectId = 1
  order by t1.tableTwo_eventTime desc limit 0,10;

Ответы [ 3 ]

7 голосов
/ 15 декабря 2011

Как вы знаете, SQLServer достигает этого с помощью индексированных представлений :

индексированных представлений обеспечивают дополнительные преимущества производительности, которые невозможно достичь с помощью стандартных индексов.Индексированные представления могут повысить производительность запросов следующими способами:

Агрегаты могут быть предварительно вычислены и сохранены в индексе, чтобы минимизировать дорогостоящие вычисления во время выполнения запроса.

Таблицы могут быть предварительно объединены, а результирующий набор данных сохранен.

Комбинации объединений или агрегатов могут быть сохранены.

В SQLServer, чтобы воспользоваться преимуществами этого метода, необходимо выполнять запросы к представлению, а не к таблицам.Это означает, что вы должны знать о представлении и индексах.

MySQL не имеет индексированных представлений, но вы можете моделировать поведение с помощью таблицы + триггеры + индексы .

Вместо создания представления необходимо создать индексированную таблицу, триггер для обновления таблицы данных, а затем запросить новую таблицу вместо нормализованных таблиц.

Вы должны оценить, будут ли накладные расходыопераций записи компенсирует улучшение операций чтения.

Отредактировано:

Обратите внимание, что создание новой таблицы не всегда необходимо.Например, в триггере отношения 1: N (master-detail) вы можете сохранить копию поля из таблицы master в таблицу detail.В вашем случае:

CREATE TABLE tableOne (
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    tableTwoId INT UNSIGNED NOT NULL,
    objectId INT UNSIGNED NOT NULL,
    desnormalized_eventTime DATETIME NOT NULL,
    INDEX (objectID),
    FOREIGN KEY (tableTwoId) REFERENCES tableTwo (id)
) ENGINE=InnoDB;

CREATE TRIGGER tableOne_desnormalized_eventTime
   BEFORE INSERT ON tableOne
for each row
begin
  DECLARE eventTime DATETIME;
  SET eventTime = 
      (select eventTime 
       from tableOne
       where tableOne.id = NEW.tableTwoId);
  NEW.desnormalized_eventTime = eventTime;
end;

Обратите внимание, что это триггер до вставки.

Теперь запрос переписывается следующим образом:

select * from tableOne t1 
  inner join tableTwo t2 on t1.tableTwoId = t2.id
  where t1.objectId = '..'
  order by t1.desnormalized_eventTime;

Отказ от ответственности: не тестировался.

2 голосов
/ 09 апреля 2018

Перекрестная индексация в MySQL невозможна, кроме как через теперь уже не существующий механизм Akiban (?).

У меня есть правило: «Не нормализуйте« непрерывные »значения, такие как INT, FLOAT, DATETIME., так далее."Стоимость JOIN, когда вам нужно выполнить сортировку или диапазонное тестирование непрерывного значения, снизит производительность.

DATETIME занимает 5 байтов;INT занимает 4. Таким образом, любой аргумент «пробела» в направлении нормализации даты и времени довольно слабый.Редко когда вам нужно «нормализовать» дату и время, когда все изменения определенного значения будут изменены.

0 голосов
/ 06 апреля 2018

Может быть, я ошибаюсь, но если это мое приложение, я не буду дублировать данные, если мне не нужно упорядочить по 2 столбцам в 2 разных таблицах, а это горячий запрос (требуется много раз).Но так как нет четкого решения, чтобы избежать filesort, как насчет этого небольшого трюка (вынудите оптимизатор использовать индекс для столбца в порядке по пункту eventTime)

select * from tableOne t1 
inner join tableTwo t2 use index (eventTime)  on t1.tableTwoId = t2.id and t2.eventTime > 0
where t1.objectId = 1
order by t2.eventTime desc limit 0,10;

уведомлениеuse index (eventTime) и t2.eventTime > 0

Это объяснение показывает, что оптимизатор использовал индекс для eventTime вместо filesort

1   SIMPLE  t2  range   eventTime   eventTime   5       5000    Using where; Using index
1   SIMPLE  t1  ref objectId,tableTwoId tableTwoId  4   tests.t2.id 1   Using where
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...