【发布时间】:2012-01-20 11:54:00
【问题描述】:
考虑一个结构,其中您与两个表上的条件(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.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 ;
您可以使用这些来填充tableTwo 中的 10,000 行和 tableOne 中的 20,000 行(随机引用 tableOne 和随机 objectIds 介于 1 和 5 之间),这需要 26.2 和 70.77 秒分别为我跑:
call populate_table_two(10000);
call populate_table_one(20000, 10000, 5);
更新 2(测试触发 SQL):
下面是基于 daniHp 的触发方式的久经考验的 SQL。当添加tableOne 或更新tableTwo 时,这会使dateTime 与tableOne 保持同步。此外,如果将条件列复制到连接表,则此方法也适用于多对多关系。在我对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;
【问题讨论】:
-
您可以创建另一个聚合表。
-
@anttir:有没有比在现有表之一中复制数据更可取的原因?
-
Sample code(此处为 SQL 形式)比 ad hoc 模式更有用。
-
@outis:谢谢,我会记住的。