行构造函数表达式选择不使用索引

Posted

技术标签:

【中文标题】行构造函数表达式选择不使用索引【英文标题】:Row Constructor Expression selects not using index 【发布时间】:2021-05-19 07:02:45 【问题描述】:

我们有这张桌子

CREATE TABLE `resource_grant` (
  `resource_grant_id` int(11) NOT NULL AUTO_INCREMENT,
  `member_type_id` int(11) NOT NULL,
  `member_ref` varchar(36) NOT NULL,
  `resource_type_id` int(11) NOT NULL,
  `resource_ref` varchar(36) NOT NULL,
  `role_id` int(11) NOT NULL,
  `modified_by` varchar(255) NOT NULL,
  `modified_timestamp` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  `deleted` tinyint(1) NOT NULL DEFAULT '0',
  `parent_grant_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`resource_grant_id`),
  UNIQUE KEY `member_ref` (`member_ref`,`member_type_id`,`resource_type_id`,`resource_ref`),
  KEY `member_type_id` (`member_type_id`),
  KEY `resource_type_id` (`resource_type_id`),
  KEY `role_id` (`role_id`),
  KEY `resource_ref` (`resource_ref`,`resource_type_id`),
  KEY `idx_rg_parent_grant_id` (`parent_grant_id`),
  KEY `resource_ref_2` (`resource_ref`,`member_ref`,`resource_type_id`,`member_type_id`,`role_id`),
  CONSTRAINT `resource_grant_ibfk_1` FOREIGN KEY (`member_type_id`) REFERENCES `member_type` (`member_type_id`),
  CONSTRAINT `resource_grant_ibfk_2` FOREIGN KEY (`resource_type_id`) REFERENCES `resource_type` (`resource_type_id`),
  CONSTRAINT `resource_grant_ibfk_3` FOREIGN KEY (`role_id`) REFERENCES `role` (`role_id`)
) ENGINE=InnoDB;

还有这些相关的表格

CREATE TABLE `member_type` (
  `member_type_id` int(11) NOT NULL AUTO_INCREMENT,
  `member_type` varchar(36) NOT NULL,
  `modified_by` varchar(36) NOT NULL,
  `modified_timestamp` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  `deleted` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`member_type_id`),
  UNIQUE KEY `member_type` (`member_type`),
  KEY `member_type_2` (`member_type`)
) ENGINE=InnoDB;

CREATE TABLE `resource_type` (
  `resource_type_id` int(11) NOT NULL AUTO_INCREMENT,
  `resource_type` varchar(36) NOT NULL,
  `modified_by` varchar(36) NOT NULL,
  `modified_timestamp` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  `deleted` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`resource_type_id`),
  UNIQUE KEY `resource_type` (`resource_type`),
  KEY `resource_type_2` (`resource_type`)
) ENGINE=InnoDB;

CREATE TABLE `role` (
  `role_id` int(11) NOT NULL AUTO_INCREMENT,
  `role_ref` varchar(50) NOT NULL,
  `name` varchar(256) NOT NULL,
  `modified_by` varchar(36) NOT NULL,
  `modified_timestamp` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  `deleted` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`role_id`),
  UNIQUE KEY `role_ref` (`role_ref`),
  KEY `role_ref_2` (`role_ref`)
) ENGINE=InnoDB;

我们需要像这样运行选择(“行构造函数表达式”语法)(基本上是“批量选择”)

SELECT rg.resource_grant_id
FROM resource_grant rg
JOIN resource_type rt ON rg.resource_type_id = rt.resource_type_id
JOIN member_type mt ON rg.member_type_id = mt.member_type_id
JOIN role r ON r.role_id = rg.role_id
WHERE
(rg.resource_ref, rg.member_ref, rt.resource_type, mt.member_type, r.role_ref)
IN
(
('759','624962','property','epc-user','role.171'),
('11974','624962','property','epc-user','role.171')
);

选择需要大约 60 秒才能运行,这是不可接受的长

请注意,有一个索引 (resource_ref,member_ref,resource_type_id,member_type_id,role_id)

我们也不想运行 n 个单独的选择语句 - 我们需要这些“批量选择”。

mysql 5.6 文档讨论了这种不使用索引的选择方式,但您可以使用一些技巧来实现它

https://dev.mysql.com/doc/refman/5.6/en/row-constructor-optimization.html

https://dev.mysql.com/doc/refman/5.6/en/range-optimization.html

不确定我们缺少什么才能使其使用索引

编辑这里的计划

mysql> explain SELECT rg.resource_grant_id  FROM resource_grant rg  JOIN resource_type rt ON rg.resource_type_id = rt.resource_type_id  JOIN member_type mt ON rg.member_type_id = mt.member_type_id  JOIN role r ON r.role_id = rg.role_id  WHERE  (rg.resource_ref, rg.member_ref, rt.resource_type, mt.member_type, r.role_ref)  IN  (  ('759','624962','property','epc-user','role.171'),  ('11974','624962','property','epc-user','role.171')  );
+----+-------------+-------+--------+-----------------------------------------+----------------+---------+--------------------------+---------+----------------------------------------------------+
| id | select_type | table | type   | possible_keys                           | key            | key_len | ref                      | rows    | Extra                                              |
+----+-------------+-------+--------+-----------------------------------------+----------------+---------+--------------------------+---------+----------------------------------------------------+
|  1 | SIMPLE      | rt    | index  | PRIMARY                                 | resource_type  | 38      | NULL                     |       3 | Using index                                        |
|  1 | SIMPLE      | mt    | index  | PRIMARY                                 | member_type    | 38      | NULL                     |       6 | Using index; Using join buffer (Block Nested Loop) |
|  1 | SIMPLE      | rg    | ref    | member_type_id,resource_type_id,role_id | member_type_id | 4       | samsDB.mt.member_type_id | 2370237 | Using where                                        |
|  1 | SIMPLE      | r     | eq_ref | PRIMARY                                 | PRIMARY        | 4       | samsDB.rg.role_id        |       1 | Using where                                        |
+----+-------------+-------+--------+-----------------------------------------+----------------+---------+--------------------------+---------+----------------------------------------------------+
4 rows in set (0.53 sec)

【问题讨论】:

您的行构造函数从不同的表中获取列。当然不使用索引。 对,但是引用的列在这些表中被索引。有没有办法使这项工作,因为它涵盖了多个表? 为什么要使用这种你知道在正常情况下不会使用索引的行构造函数格式,而不是使用 AND 和 OR 语句组合的更常见的 WHERE 子句格式?您的示例值是否代表您要运行的实际查询,即只有 rg.resource_ref 值是变化的? 有没有办法让它工作,因为它覆盖了多个表?这是可能的,但查询必须认真修改。必须将单独表的过滤移动到子查询中,然后连接到主表 - 在这一步将使用索引。 你可以尝试强制索引(例如使用... FROM resource_grant rg force index (resource_ref_2) JOIN ...)吗?你可能有一些特殊的数据分布,但总的来说,当前的执行计划看起来有点意外。 【参考方案1】:

首先将where 子句更改为:

WHERE rg.member_ref = '624962' AND
      rt.resource_type = 'property' AND
      mt.member_type = 'epc-user' AND
      r.role_ref = 'role.171' AND
      rg.resource_ref IN ('759', '11974')

现有的索引对此并不十分理想。您需要一个前两个键为 (member_ref, resource_ref) 的索引——好吧,除了最新版本的 MySQL 实现了跳过扫描索引优化。

您也许可以将resource_ref_2 更改为:

KEY `resource_ref_2` (`member_ref`, `resource_ref`, `resource_type_id`, `member_type_id`, `role_id`),

【讨论】:

【参考方案2】:

我对 5.6 上的 60 岁并不感到惊讶。 “行构造函数”已经存在了很长时间。但它们在 5.7 之前没有优化。

按照 Gordon 的建议,升级或重写 WHERE

【讨论】:

以上是关于行构造函数表达式选择不使用索引的主要内容,如果未能解决你的问题,请参考以下文章

(59)C#里多个地方使用表达式的函数体

(59)C#里多个地方使用表达式的函数体

使用构造函数表达式和 JPQL 的复合 DTO 投影

[性能调优]在PeopleSoft中使用函数索引

JPQL 构造函数表达式中的 JPQL 和子查询

错误:选择列表中的表达式无效(不包含在聚合函数或 GROUP BY 子句中)