2011-12-14 2 views
14

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

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 ; 

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

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

Update 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

Вы можете создать другую агрегированную таблицу. – anttir

+0

@anttir: Есть ли причина, которая предпочтительнее для репликации данных в одной из существующих таблиц? – Briguy37

+0

[Пример кода] (http://sscce.org/) (здесь, в форме SQL) более полезен, чем специальная схема. – outis

ответ

5

Как вы знаете, SQLServer это достигается с indexed views:

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

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

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

Сочетания соединений или агрегатов могут храниться.

В 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; 

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

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

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

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

+0

+1: Мне нравится идея использования триггеров для копирования данных индексирования! Я, скорее всего, поеду с этим методом, но добавлю 'eventTime' непосредственно в' tableOne', так как это должно свести к минимуму копирование, переписывание кода и дополнительное потребление памяти, необходимые для решения. – Briguy37

+0

приятно. Если это база данных, которая поддерживает репликативные данные, они не могут забыть делать обновления. В данный момент я работаю с ORM (django), и я сохраняю этот код в методе save() (persistence).Я не решался сделать это по причинам нормализации, но я рад воспроизвести данные. Для «академического разработчика» это трудное решение;) – danihp

+0

Примечание: для тех, кто использует это решение, обязательно добавьте триггер обновления на 'tableTwo', если« eventTime »не исправлен. – Briguy37

Смежные вопросы