mariadb没有使用带子查询的正确索引

时间:2017-04-21 11:20:27

标签: mysql mariadb

我在mariadb数据库上面临性能问题。在我看来,当使用子查询执行请求时,mariadb没有使用正确的索引,而在请求中手动注入子查询的结果成功使用了索引:

以下是具有错误行为的请求(请注意,第二个子查询读取的行数多于必要行):

ANALYZE SELECT  `orders`.* FROM `orders` 
  WHERE `orders`.`account_id` IN (SELECT `accounts`.`id` FROM `accounts` WHERE `accounts`.`user_id` = 88144) 
  AND (               orders.type not in ("LimitOrder", "MarketOrder")
                   OR orders.type     in ("LimitOrder", "MarketOrder") AND orders.state <> "canceled"
                   OR orders.type     in ("LimitOrder", "MarketOrder") AND orders.state =  "canceled" AND orders.traded_btc > 0 ) 
  AND (NOT (orders.type = 'AdminOrder' AND orders.state = 'canceled'))  ORDER BY `orders`.`id` DESC LIMIT 20 OFFSET 0 \G;
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: accounts
         type: ref
possible_keys: PRIMARY,index_accounts_on_user_id
          key: index_accounts_on_user_id
      key_len: 4
          ref: const
         rows: 7
       r_rows: 7.00
     filtered: 100.00
   r_filtered: 100.00
        Extra: Using index; Using temporary; Using filesort
*************************** 2. row ***************************
           id: 1
  select_type: PRIMARY
        table: orders
         type: ref
possible_keys: index_orders_on_account_id_and_type,index_orders_on_type_and_state_and_buying,index_orders_on_account_id_and_type_and_state,index_orders_on_account_id_and_type_and_state_and_traded_btc
          key: index_orders_on_account_id_and_type_and_state_and_traded_btc
      key_len: 4
          ref: bitcoin_central.accounts.id
         rows: 60
       r_rows: 393.86
     filtered: 100.00
   r_filtered: 100.00
        Extra: Using index condition; Using where

当手动注入子查询的结果时,我有正确的行为(和预期的性能):

ANALYZE SELECT  `orders`.* FROM `orders`      
    WHERE `orders`.`account_id` IN (433212, 433213, 433214, 433215, 436058, 436874, 437950)
    AND (               orders.type not in ("LimitOrder", "MarketOrder")                           
                     OR orders.type     in ("LimitOrder", "MarketOrder") AND orders.state <> "canceled"
                     OR orders.type     in ("LimitOrder", "MarketOrder") AND orders.state =  "canceled" AND orders.traded_btc > 0 )
   AND (NOT (orders.type = 'AdminOrder' AND orders.state = 'canceled')) 
   ORDER BY `orders`.`id` DESC LIMIT 20 OFFSET 0\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: orders
         type: range
possible_keys: index_orders_on_account_id_and_type,index_orders_on_type_and_state_and_buying,index_orders_on_account_id_and_type_and_state,index_orders_on_account_id_and_type_and_state_and_traded_btc
          key: index_orders_on_account_id_and_type_and_state_and_traded_btc
      key_len: 933
          ref: NULL
         rows: 2809
       r_rows: 20.00
     filtered: 100.00
   r_filtered: 100.00
        Extra: Using index condition; Using where; Using filesort
1 row in set (0.37 sec)

请注意,在加入两个表时,我遇到了完全相同的问题。

以下是我的订单表定义的摘录:

SHOW CREATE TABLE orders \G;
*************************** 1. row ***************************
       Table: orders
Create Table: CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `account_id` int(11) NOT NULL,
  `traded_btc` decimal(16,8) DEFAULT '0.00000000',
  `type` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `state` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  KEY `index_orders_on_account_id_and_type_and_state_and_traded_btc` (`account_id`,`type`,`state`,`traded_btc`),
  CONSTRAINT `orders_account_id_fk` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=8575594 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

有谁知道这里发生了什么? 有没有办法强制数据库在我的子请求中使用我的索引。

2 个答案:

答案 0 :(得分:1)

IN ( SELECT ... )优化得很差。通常的解决方案是变成JOIN

FROM accounts AS a
JOIN orders AS o  ON a.id = o.account_id
WHERE a.user_id = 88144
  AND ... -- the rest of your WHERE

或者就是你用&#34做的事情;注意我在加入两个表时遇到了完全相同的问题。&#34;?如果是这样,请查看查询及其EXPLAIN

您参考&#34;预期效果&#34; ...您是指EXPLAIN中的数字?或者你有时间来支持断言?

我喜欢这样做,以便更细致地了解多少&#34;工作&#34;正在进行:

FLUSH STATUS;
SELECT ...;
SHOW SESSION STATUS LIKE 'Handler%';

这些数字通常表明是否涉及表扫描或查询是否在OFFSET+LIMIT之后停止。这些数字是精确计数,与EXPLAIN不同,这只是估算值。

据推测,您通常会通过orders查看account_id?以下是加速此类查询的方法:

替换当前的两个索引

 PRIMARY KEY (`id`),
 KEY `account_id__type__state__traded_btc` 
    (`account_id`,`type`,`state`,`traded_btc`),

用这些:

 PRIMARY KEY (`account_id`, `type`, `id`),
 KEY (id)  -- to keep AUTO_INCREMENT happy.

这会聚合给定帐户的所有行,从而使查询运行得更快,尤其是当您现在 I / O绑定时。如果列的某些组合使得#34;自然&#34; PK,然后完全抛出id

(并注意我如何在不丢失任何信息的情况下缩短您的密钥名称?)

此外,如果您受I / O约束,通过将那些冗长的VARCHARs(州和类型)转换为ENUMs,可以缩小表格。

更多

鉴于该查询涉及

WHERE ... mess with INs and ORs ...
ORDER BY ...
LIMIT 20

该用户有200万行,没有INDEX可以通过WHERE进入ORDER BY以便它可以使用LIMIT。也就是说,必须以这种方式执行:

  1. 为该一个用户过滤2M行
  2. 排序(ORDER BY)2M行的一些重要部分
  3. 剥离20排。 (是的,5.6使用&#34;优先级&#34;队列,排序O(1)而不是O(log N),但这没有多大帮助。
  4. 我真的很惊讶IN( constants )效果很好。

答案 1 :(得分:1)

我有同样的问题。请使用内部联接而不是子查询。