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