2017-07-27 179 views
1

TL; DR; MySQL认为索引不可能用于查询,但由于基数较低,所以使用它的所有值仍然更快。有没有办法强制MySQL总是使用组合索引,即使它认为它不是可能的索引?MySQL迫使MySQL使用它认为不可能的索引

全部问题...

我存储在MySQL中的排队系统的状态。将队列项目推送到队列服务器,但是我们使用数据库来确保仅处理节点上的相关对象1次。

在同一个表中有多个队列被跟踪,由queue_name varchar字段标识。任何项目的状态可以是queued,processing,donefailed之一。要快速计数或获取未完成项目,请在queue_name + status上有一个复合索引。

queue_name是一个非常低的基数列(目前只有3个可能的值)。

模式:

CREATE TABLE `queue` (
    `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, 
    `queue_name` varchar(255) NOT NULL, 
    `status` enum('queued','processing','done','failed') NOT NULL, 
    `payload` longtext NOT NULL, 
    PRIMARY KEY (`id`), 
    KEY `queue_queue_name_status_index` (`queue_name`,`status`) 
) ENGINE=InnoDB; 

时,取在processing状态MySQL的所有项目执行全表扫描。

EXPLAIN SELECT * FROM queue WHERE status IN ('queued', 'processing'); 

    select_type: SIMPLE 
     table: queue 
    partitions: NULL 
     type: ALL 
possible_keys: NULL 
      key: NULL 
     key_len: NULL 
      ref: NULL 
     rows: 1036882 
    filtered: 50.00 
     Extra: Using where 

这将如我所料,因为没有“合适的”索引来快速选择此选项。

然而,鉴于我知道queue_name列具有非常低的基数,我也能达到同样的查询列出每一个可能的queue_name值:

EXPLAIN SELECT * FROM queue 
    WHERE queue_name IN ('default', 'email', 'order') /* All values */ 
     AND status IN ('queued', 'processing'); 

    select_type: SIMPLE 
     table: queue 
    partitions: NULL 
     type: range 
possible_keys: queue_queue_name_status_index 
      key: queue_queue_name_status_index 
     key_len: 767 
      ref: NULL 
     rows: 9 
    filtered: 100.00 
     Extra: Using index condition; Using where 

这正确地使用综合指数,从100万行的过滤器取决于当前的数据为5-10。

这对于相同的结果要快得多。我试图告诉MySQL使用该索引,但查询计划员似乎抛弃它并忽略它。例如。

EXPLAIN SELECT * FROM queue 
    FORCE INDEX (queue_queue_name_status_index) 
    WHERE status IN ('queued', 'processing'); 

    select_type: SIMPLE 
     table: queue 
    partitions: NULL 
     type: ALL 
possible_keys: NULL 
      key: NULL 
     key_len: NULL 
      ref: NULL 
     rows: 1037684 
    filtered: 50.00 
     Extra: Using where 

这产生与未指定索引相同的解释和慢查询。索引没有显示为可能的索引,并且未被使用。

有没有办法迫使MySQL总是使用复合索引,即使它认为它不是一个可能的索引?查询规划器总是将其排除,因此即使使用FORCE INDEX,MySQL也会决定不使用索引,而是进行全表扫描。这显然要慢得多。

+2

首先创建状态列的索引 –

+0

'分析表“来更新基数可能有帮助吗? – GhostGambler

+0

@IlyaBursov当然,我们可以在状态列上单独创建一个单独的索引,但我宁愿我们的数据库只有一个可能的索引来维护。这是一个相当高的吞吐量表。鉴于MySQL已经不得不对这个领域进行分类索引,那么做两次就太浪费了。 – AndySavage

回答

0

我不认为有一种方法可以在你已经完成的工作之外强制它;尽管用queue_name <> 'someimpossiblevalue'这样的东西代替“强制”条件queue_name IN ('default', 'email', 'order')可能会更简单(也更快)。

queue_name IS NULL可能会更快,因为该字段被定义为NOT NULL,允许MySQL优化器用“TRUE”替换它;但最终可能会忽略索引。这值得一试。

+0

好主意。当使用'NOT NULL'时,它现在在查询规划器中显示为可能的索引,但MySQL仍然不使用它(即使使用'FORCE INDEX')。非常接近! – AndySavage

+0

如果你知道它不能是一个空白的字符串; 'queue_name <>'''可能是最好的解决方案,因为(我猜)它具有最少的字符串比较逻辑。 – Uueerdo

+0

同样的结果我很害怕。作为一个可能的索引列表(单独'force'不能达到),但仍然不使用它。 – AndySavage