mysql - 可以跨表索引吗?

标签 mysql join indexing

考虑一个结构,其中您与两个表上的条件(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。如果您随后对上述查询进行解释,它将显示“使用文件排序”。本质上,它首先应用 tableOne.objectId 索引,但它不能应用 tableTwo.eventTime 索引,因为该索引是针对整个 tableTwo 的(不是有限的结果set),因此它必须进行手动排序。

因此,有没有办法进行跨表索引,这样就不必在每次检索结果时都进行文件排序?类似:

create index ind_t1oi_t2et on tableOne t1 
  inner join tableTwo t2 on t1.tableTwoId = t2.id 
  (t1.objectId, t2.eventTime);

另外,我已经研究过创建一个 View 并为其编制索引,但 View 不支持索引。

如果无法进行跨表索引,我一直倾向于的解决方案是将条件数据复制到一个表中。在这种情况下,这意味着 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 时,这会使 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;

最佳答案

如您所知,SQLServer 通过 indexed views 实现了这一点:

indexed views provide additional performance benefits that cannot be achieved using standard indexes. Indexed views can increase query performance in the following ways:

Aggregations can be precomputed and stored in the index to minimize expensive computations during query execution.

Tables can be prejoined and the resulting data set stored.

Combinations of joins or aggregations can be stored.

在 SQLServer 中,要利用此技术,您必须查询 View 而不是表。这意味着您应该了解 View 和索引。

MySQL 没有索引 View ,但您可以用表+触发器+索引模拟行为

您必须创建一个索引表,一个使数据表保持最新的触发器,而不是创建一个 View ,然后您必须查询您的新表而不是您的规范化表。

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

已编辑:

请注意,并不总是需要创建新表。例如,在 1:N 关系(主从)触发器中,您可以将“主”表中的字段副本保存到“详细信息”表中。在你的情况下:

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;

免责声明:未经测试。

关于mysql - 可以跨表索引吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8509026/

相关文章:

javascript - 如何正确维护 JQuery Counter 中的索引?

php - MySQL根据另一个表的时间戳选择信息

php - MySQL:基于列值自动递增?

mysql - Wampserver 图标没有完全变绿,mysql 服务没有启动?

mysql - 如何在带有 SUM 的多重连接查询中包含 DISTINCT

mysql - 选择在另一个表中没有特定值的项目

mysql - 为什么额外的是 "using where;using index"而不是 "using index"

C#/LINQ(?) - 加入两个数据表(没有 SQL!)

r - 在 R 中,如何使用索引修改/重新分配列表元素?

mysql 表与 max() 连接