如何在MySQL中创建条件索引?


24

如何在MySQL中创建索引以过滤表的特定范围或子集?AFAIK无法直接创建,但我认为可以模拟此功能。

示例:我NAME只想为带有行的列创建索引STATUS = 'ACTIVE'

此功能在SQL Server中称为筛选索引,在Postgres中称为部分索引

Answers:


9

MySQL当前不支持条件索引。

为了达到您的要求(不是您应该这样做;)),您可以开始创建辅助表:

CREATE TABLE  `my_schema`.`auxiliary_table` (
   `id` int unsigned NOT NULL,
   `name` varchar(250), /* specify the same way as in your main table */
   PRIMARY KEY (`id`),
   KEY `name` (`name`)
);

然后,在主表中添加三个触发器:

delimiter //

CREATE TRIGGER example_insert AFTER INSERT ON main_table
FOR EACH ROW
BEGIN
   IF NEW.status = 'ACTIVE' THEN
      REPLACE auxiliary_table SET
         auxiliary_table.id = NEW.id,
         auxiliary_table.name = NEW.name;
   END IF;
END;//

CREATE TRIGGER example_update AFTER UPDATE ON main_table
FOR EACH ROW
BEGIN
   IF NEW.status = 'ACTIVE' THEN
      REPLACE auxiliary_table SET
         auxiliary_table.id = NEW.id,
         auxiliary_table.name = NEW.name;
   ELSE
      DELETE FROM auxiliary_table WHERE auxiliary_table.id = OLD.id;
   END IF;
END;//

CREATE TRIGGER example_delete AFTER DELETE ON main_table
FOR EACH ROW
BEGIN
   DELETE FROM auxiliary_table WHERE auxiliary_table.id = OLD.id;
END;//

delimiter ;

我们需要,delimiter //因为我们想;在触发器内部使用。

这样,辅助表将完全包含与主表行相对应的ID,这些主表行包含由触发器更新的字符串“ ACTIVE”。

要将其用于select,您可以使用通常的方法join

SELECT main_table.* FROM auxiliary_table LEFT JOIN main_table
   ON auxiliary_table.id = main_table.id
   ORDER BY auxiliary_table.name;

如果主表已经包含数据,或者如果您进行一些外部操作以不寻常的方式更改数据(例如,MySQL外部),则可以使用以下方法修复辅助表:

INSERT INTO auxiliary_table SET
   id = main_table.id,
   name = main_table.name,
   WHERE main_table.status="ACTIVE";

关于性能,插入,更新和删除的速度可能会较慢。仅当您确实处理了所需条件为肯定的少数情况时,这才有意义。即使那样,也许只有测试,您才能看到节省的空间是否真的证明了这种方法(以及您是否真的节省了任何空间)。


7

如果我正确理解了这个问题,那么我认为可以在NAME和STATUS这两个列上创建索引来完成您要执行的操作。这样可以有效地查询NAME ='SMITH'和STATUS ='ACTIVE'的位置


1
好的,但是如果状态行为ACTIVE的行相对较少,这将不节省空间。
Maniero

不,不是,但这不是问题的要件,也没有说该表已被加权为其中一个值。为此,我将创建您要查找的状态的物化视图,但MySQL不支持这些。
BlackICE 2011年

和磁盘空间很便宜...
BlackICE 2011年

2
是的,这不是直接要求,因此我以“确定”开始评论。我正在寻找一些专业的替代品。专业替代方案始终在寻找最有效的方式来完成任务。您的答案可能是最明显的答案。没问题。但是我完全不同意“磁盘空间便宜”,不是因为它昂贵,而是当然便宜,但是内存并不是那么便宜,内存具有低限制,索引应该主要依靠内存来提高效率。磁盘访问并不是那么便宜。您的答案当然是实现目标的一种正确方法,但我怀疑这是最好的。
Maniero

我也不会在内存上表示异议,这些天也是如此便宜(肯定不如磁盘空间便宜,但是其中一些价格为$ 10 /
g

6

你不能这样做有条件的索引,但你的榜样,您可以添加多列索引(namestatus)。

即使它将索引这些列中的所有数据,它仍将帮助您找到状态为“活动”的名称。


4

您可以通过在两个表之间拆分数据,在需要所有数据时使用视图将两个表合并并为该列上的一个表建立索引来实现此目的,但是我认为这会导致需要查询的性能问题运行整个表,除非查询计划程序比我认为的要聪明。本质上,您将手动对表进行分区(并将索引仅应用于其中一个分区)。

不幸的是,内置的表分区功能将无法为您提供帮助,因为您无法将索引应用于单个分区。

您可以维护带有索引的额外列,并且仅当您希望索引所基于的条件为true时,才在该列中具有值,但是这可能是劳动密集型的,并且在以下方面受限制(或负值)查询效率和节省空间。


我不会有两个表只是为了拥有更好的索引,因为联接仍然会很昂贵,不是吗?
jcolebrand

@jcolebrand:对于一般查询(在执行联合的视图上),查询会更昂贵,您需要从分区表中专门选择才能使用索引。内置分区将为您高效地做到这一点,但是只有Bigown想要(节省空间)的方式(如果它支持分区特定的索引)才能为您完成此操作。我说他可以做到,不是他愿意!
David Spillett

0

MySQL现在具有虚拟列,可用于索引。


3
如何使用此功能来模拟过滤索引?
ypercubeᵀᴹ

1
@ yper-trollᵀᴹ,druud62可能会考虑甲骨文:dbfiddle.uk/... - MySQL不看到治疗的NULL同样的方式,但:dbfiddle.uk/...
杰克·道格拉斯

@JackDouglas也许。(这不只是一种索引优化,它可以节省空间吗?换句话说,可以select count(*) from foo where id is null ;使用索引吗?)
ypercubeᵀᴹ17/

@yper-trollᵀᴹOracle不会索引所有索引列均为NULL的行(use-the-index-luke.com/sql/where-clause/null/index)—decode(status,'ACTIVE',name,null)例如,虚拟列可能处于打开状态。
杰克·道格拉斯

Thnx,我认为在最近的版本中已经发生了变化(并且对null进行了索引)。
ypercubeᵀᴹ
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.