2011-12-14 71 views
14

考虑一种结构,在这种结构中,您与两个表上的条件(where,order by等)有多对一(或一对多)的关系。例如:交叉表索引可能吗?

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.objectIdtableTwo.eventTime。如果你接着解释上述查询,它将显示“使用filesort”。本质上,它首先应用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.objectIdtableOne.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 ; 

您可以使用这些如下来填充tableOnetableTwo 10,000行和20000行(用随机引用tableOne和随机objectId s在1和5之间),分别花费26.2和70.77秒来运行我:

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

更新2(测试触发SQL):

下面是基于daniHp的触发方法的尝试和测试SQL。当tableOne被添加或tableTwo被更新时,这保持dateTimetableOne上同步。此外,如果条件列被复制到连接表中,此方法也适用于多对多关系。在我测试tableOne中的300,000行和tableTwo中的200,000行时,具有类似限制的旧查询的速度为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 2011-12-14 19:34:23

+0

@anttir:是否有理由优于复制现有表之一中的数据? – Briguy37 2011-12-14 20:03:33

回答

5

如你所知,SQLServer的实现了这一具有indexed views

索引视图提供额外的性能优势无法 使用标准的指标来实现的。索引视图可以通过以下方式增加查询 的性能:

聚合可以预先计算并存储在索引中,以最大限度地减少查询执行期间的昂贵计算。

可以预先加入表格并存储结果数据集。

可以存储连接或聚合的组合。

在SQLServer中,要利用此技术,您必须通过视图进行查询,而不是在表上进行查询。这意味着你应该了解视图和索引。

MySQL没有索引视图,但您可以模拟表+触发器+索引的行为。

您不必创建视图,而必须创建一个索引表,一个用于保持数据表处于最新状态的触发器,然后您必须查询新表而不是标准化表。

您必须评估写入操作的开销是否抵消了读取操作的改进。

编辑:

注意,它并不总是必要创建一个新表。例如,在1:N关系(master-detail)触发器中,您可以将“主”表中的字段副本保留到“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; 

免责声明:未经测试。