Возможно ли перекрестное табличное индексирование?

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

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. Если вы затем объясните по вышеуказанному запросу, он покажет "Using filesort". По существу, он сначала применяет индекс tableOne.objectId, но не может использовать индекс tableTwo.eventTime, потому что этот индекс предназначен для всего tableTwo (а не ограниченного набора результатов), и поэтому он должен выполнять ручную сортировку.

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

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 (по существу, вручную создавая индекс). Тем не менее, я думал, что сначала найду других людей, чтобы убедиться, что это лучший способ.

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

Update:

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

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 ;

Вы можете использовать их следующим образом, чтобы заполнить 10 000 строк в tableTwo и 20 000 строк в 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;

Ответы

Ответ 1

Как вы знаете, SQLServer достигает этого с индексированными представлениями:

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

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

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

Можно сохранять комбинации объединений или агрегатов.

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

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

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

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

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

Обратите внимание, что не всегда необходимо создавать новую таблицу. Например, в триггере отношения 1: N (мастер-деталь) вы можете сохранить копию поля из таблицы "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;

Обратите внимание, что это триггер before insert.

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

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

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