为什么添加额外的 'OR' 使得针对 MYSQL 视图的查询非常慢

Why is adding an extra 'OR' making a query against a MYSQL View very slow

我的 SQL 查询如下所示:

SELECT * FROM `userListView` `t` 
WHERE (((((((email LIKE '%western millwork%') OR 
(firstName LIKE '%western millwork%')) OR 
(lastName LIKE '%western millwork%')) OR 
(name LIKE '%western millwork%')) OR 
(company LIKE '%western millwork%')) OR 
(companyName LIKE '%western millwork%')) OR 
(visualId LIKE '%western millwork%')) 
AND (active='1') 
ORDER BY `t`.`lastName`, `t`.`firstName` LIMIT 30

查询 运行 很好,直到我添加了关于 'companyName'.

的行

一旦发生这种情况,它就会从大约 1 秒变成 15 - 20 秒。

我认为这与视图的创建方式有关?所以有 'company'.'name' 然后 'userListView' 中有 'companyName' 这就是我要查询的内容。

创建视图的方式如下:

CREATE 
    ALGORITHM = UNDEFINED 
    DEFINER = `3form`@`192.168.%.%` 
    SQL SECURITY DEFINER
VIEW `userListView` AS
    (SELECT 
        `user`.`uid` AS `uid`,
        `user`.`active` AS `active`,
        `user`.`email` AS `email`,
        `user`.`fname` AS `firstName`,
        `user`.`lname` AS `lastName`,
        CONCAT(`user`.`fname`, ' ', `user`.`lname`) AS `name`,
        `user`.`company` AS `company`,
        `user`.`cust_id` AS `visualId`,
        `user`.`repid` AS `repId`,
        `user`.`srepid` AS `studioRepId`,
        `user`.`lightartrepid` AS `lightArtRepId`,
        `user`.`exteriorrepid` AS `exteriorRepId`,
        `user`.`nationalrepid` AS `nationalRepId`,
        `user`.`phone` AS `phone`,
        `user`.`phone_ext` AS `phoneExt`,
        `user`.`fax` AS `fax`,
        `user`.`mobile` AS `mobile`,
        `billingAddress`.`label` AS `billingAddressLabel`,
        `billingAddress`.`addr` AS `billingAddressAddr`,
        `billingAddress`.`addr2` AS `billingAddressAddr2`,
        `billingAddress`.`city` AS `billingAddressCity`,
        `billingAddress`.`state` AS `billingAddressState`,
        `billingAddress`.`zip` AS `billingAddressZip`,
        `billingAddress`.`country` AS `billingAddressCountry`,
        `shippingAddress`.`label` AS `shippingAddressLabel`,
        `shippingAddress`.`addr` AS `shippingAddressAddr`,
        `shippingAddress`.`addr2` AS `shippingAddressAddr2`,
        `shippingAddress`.`city` AS `shippingAddressCity`,
        `shippingAddress`.`state` AS `shippingAddressState`,
        `shippingAddress`.`zip` AS `shippingAddressZip`,
        `shippingAddress`.`country` AS `shippingAddressCountry`,
        `branch`.`bid` AS `bid`,
        `branch`.`description` AS `branchDescription`,
        `branch`.`repid` AS `branchRepId`,
        `branch`.`studiorepid` AS `branchStudioRepId`,
        `branch`.`lightartrepid` AS `branchLightArtRepId`,
        `branch`.`exteriorrepid` AS `branchExteriorRepId`,
        `branch`.`nationalrepid` AS `branchNationalRepId`,
        `branchAddress`.`label` AS `branchAddressLabel`,
        `branchAddress`.`addr` AS `branchAddressAddr`,
        `branchAddress`.`addr2` AS `branchAddressAddr2`,
        `branchAddress`.`city` AS `branchAddressCity`,
        `branchAddress`.`state` AS `branchAddressState`,
        `branchAddress`.`zip` AS `branchAddressZip`,
        `branchAddress`.`country` AS `branchAddressCountry`,
        `company`.`cid` AS `cid`,
        `company`.`name` AS `companyName`,
        `company`.`url` AS `companyUrl`,
        `company`.`visual_id` AS `companyVisualId`,
        `company`.`phone` AS `companyPhone`,
        `company`.`fax` AS `companyFax`,
        `companyAddress`.`label` AS `companyAddressLabel`,
        `companyAddress`.`addr` AS `companyAddressAddr`,
        `companyAddress`.`addr2` AS `companyAddressAddr2`,
        `companyAddress`.`city` AS `companyAddressCity`,
        `companyAddress`.`state` AS `companyAddressState`,
        `companyAddress`.`zip` AS `companyAddressZip`,
        `companyAddress`.`country` AS `companyAddressCountry`
    FROM
        ((((((`users` `user`
        LEFT JOIN `useraddr` `billingAddress` ON (((`user`.`uid` = `billingAddress`.`uid`)
            AND (`user`.`billaddr` = (`billingAddress`.`label` COLLATE utf8_unicode_ci)))))
        LEFT JOIN `useraddr` `shippingAddress` ON (((`user`.`uid` = `shippingAddress`.`uid`)
            AND (`user`.`billaddr` = (`shippingAddress`.`label` COLLATE utf8_unicode_ci)))))
        LEFT JOIN `branch` ON ((`user`.`bid` = `branch`.`bid`)))
        LEFT JOIN `branch_address` `branchAddress` ON (((`branch`.`bid` = `branchAddress`.`bid`)
            AND (`branch`.`shipto` = (`branchAddress`.`label` COLLATE utf8_unicode_ci)))))
        LEFT JOIN `company` ON ((`company`.`cid` = `branch`.`cid`)))
        LEFT JOIN `company_address` `companyAddress` ON (((`company`.`cid` = `companyAddress`.`cid`)
            AND (`company`.`billto` = (`companyAddress`.`label` COLLATE utf8_unicode_ci))))))

想法是 'companyName' 会优先于 'company',但这还没有发生。因此,为什么原始查询同时查看两者。

知道问题出在哪里吗?

更新:

解释如下:

由于多种原因,此查询及其基础视图可能很慢。

  1. column LIKE '%value%' 是减慢查询速度的最有效方法。它必须检查列中的每个值,并且不能利用任何索引查找。如果您可以将这些项目的部分或全部更改为 column LIKE 'value%',您将会看到改进。
  2. OR 子句,尤其是与 LIKE '%value%' 结合使用时,查询速度也很慢。有时查询规划器必须对结果集进行多次传递。
  3. 您已将 OR 子句用括号分组,这告诉查询计划器您不信任它来决定 OR 子句链的哪些部分最有可能是最快的。去掉所有那些嵌套的括号。
  4. 您正在对 CONCAT() 操作的结果进行 LIKE 搜索。这保证了查询规划器无法优化上述问题。
  5. MySQL 没有物化视图。每次使用时都会重建视图。 MySQL 中的视图主要用于清晰度,而不是性能。
  6. SELECT * FROM ... ORDER BY something LIMIT small-number 是复杂查询中的经典反模式。它执行整个查询,然后对其进行排序,然后丢弃大部分。
  7. 您在 JOIN ... ON 子句上使用了 COLLATE 修饰符。如果这些是必要的,那么您就无法在 ON 条件下使用索引:排序规则被嵌入到 MySQL 索引中。如果您的 COLLATE 子句覆盖默认排序规则,则无法使用索引。
  8. 但显着放缓的 'real' 问题更为隐蔽。起初,您过滤一个 table (users)。当您添加 companyName 时,它不需要同时过滤 VIEW 中的另一个 table。这意味着它必须保留第一个 table 的所有行,以防 companyName 匹配。解决方法是将 that OR 变成 UNION.
  9. 此处列出的许多问题没有简单的加速。

在尝试解决此问题时,您可能需要将 SQL_NO_CACHE 添加到 select 语句中,如下所示

  SELECT SQL_NO_CACHE whatever FROM whatever

那是因为 MySQL 检测到重复的查询,并且 returns 如果可以(并且 "Query cache" 已打开),先前的结果会很快(亚秒级)。

这种广角站内搜索,大概应该对每一大类数据分别处理。例如,您需要执行单独的单个 table 查询以从 usercompany table 中提取相关的 id 值,然后弄清楚如何获取相关数据。