繁体   English   中英

MySQL 优化 OR 与多个表

[英]MySQL optimize OR with multiple tables

我试图找到一个更好的解决方案,因为实际查询花费的时间太长。

假设您有 3 个表:任务、地址、人员 现在我们有一个像“tobias london”这样的字符串 - 我们将按空格拆分字符串并得到 ["tobias","london"](当然我们可以有更复杂的包含更多单词的字符串)

现在我们想要所有的任务,在地址或人员中有匹配并且所有单词都必须匹配

实际查询示例:

SELECT
  tasks.task_id
FROM
  (
    tasks,
    (SELECT
      adress_id AS id,
      'adress_id' AS typ
    FROM
      adresses
    WHERE adresses.city LIKE 'tobias%'
      OR adresses.street LIKE 'tobias%'
    UNION
    SELECT
      person_id AS id,
      'person_id' AS typ
    FROM
      persons
    WHERE persons.name LIKE 'tobias%') f0,
    (SELECT
      adress_id AS id,
      'adress_id' AS typ
    FROM
      adresses
    WHERE adresses.city LIKE 'london%'
      OR adresses.street LIKE 'london%'
    UNION
    SELECT
      person_id AS id,
      'person_id' AS typ
    FROM
      persons
    WHERE persons.name LIKE 'london%') f1
  )
WHERE (
    f0.typ = 'adress_id'
    AND tasks.adress_id = f0.id
    OR f0.typ = 'person_id'
    AND tasks.person_id = f0.id
  )
  AND (
    f1.typ = 'adress_id'
    AND tasks.adress_id = f1.id
    OR f1.typ = 'person_id'
    AND tasks.person_id = f1.id
  )

这是非常慢的,因为 WHERE 子句中的 OR (这只是一个有 2 个单词的例子)

有人知道如何让它比这更好吗?

编辑:根据要求一个小的 db-fiddle:

 CREATE TABLE `adresses` ( `adress_id` int(10) unsigned NOT NULL AUTO_INCREMENT, `street` varchar(200) NOT NULL, `house_number` varchar(6) NOT NULL, `postal_code` smallint(5) unsigned NOT NULL, `city` varchar(100) NOT NULL, PRIMARY KEY (`adress_id`), KEY `street` (`street`), KEY `postal_code` (`postal_code`), KEY `city` (`city`) ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8; /*Data for the table `adresses` */ insert into `adresses`(`adress_id`,`street`,`house_number`,`postal_code`,`city`) values (1,'first','1',12345,'London'), (2,'second','2',23456,'Paris'), (3,'third','3',34567,'Okawa'), (4,'fourth','4',45678,'Berlin'), (5,'fiveth','5',56789,'ABCDE'), (6,'sixth','6',65535,'DFGHJH'); /*Table structure for table `persons` */ CREATE TABLE `persons` ( `person_id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, PRIMARY KEY (`person_id`), KEY `name` (`name`) ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8; /*Data for the table `persons` */ insert into `persons`(`person_id`,`name`) values (1,'Anna'), (2,'John'), (3,'Michel'), (4,'Olivia'), (5,'Pavel'), (6,'Peter'), (7,'Sarah'), (8,'Tobias'); /*Table structure for table `tasks` */ CREATE TABLE `tasks` ( `task_id` int(10) unsigned NOT NULL AUTO_INCREMENT, `person_id` int(10) unsigned NOT NULL, `adress_id` int(10) unsigned NOT NULL, `description` varchar(200) NOT NULL, PRIMARY KEY (`task_id`), KEY `person_id` (`person_id`), KEY `adress_id` (`adress_id`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8; /*Data for the table `tasks` */ insert into `tasks`(`task_id`,`person_id`,`adress_id`,`description`) values (1,1,2,''), (2,2,3,''), (3,3,4,''), (4,5,6,''), (5,4,1,''), (6,1,4,''), (7,3,3,''), (8,8,1,''); SELECT tasks.task_id FROM ( tasks, (SELECT adress_id AS id, 'adress_id' AS typ FROM adresses WHERE adresses.city LIKE 'tobias%' OR adresses.street LIKE 'tobias%' UNION SELECT person_id AS id, 'person_id' AS typ FROM persons WHERE persons.name LIKE 'tobias%') f0, (SELECT adress_id AS id, 'adress_id' AS typ FROM adresses WHERE adresses.city LIKE 'london%' OR adresses.street LIKE 'london%' UNION SELECT person_id AS id, 'person_id' AS typ FROM persons WHERE persons.name LIKE 'london%') f1 ) WHERE ( f0.typ = 'adress_id' AND tasks.adress_id = f0.id OR f0.typ = 'person_id' AND tasks.person_id = f0.id ) AND ( f1.typ = 'adress_id' AND tasks.adress_id = f1.id OR f1.typ = 'person_id' AND tasks.person_id = f1.id )
  ✓ ✓ ✓ ✓ ✓ ✓ | 任务编号 |  |  ------: |  |  8 |

db<> 在这里摆弄

重新设计架构。 在关系数据库中使用typ在两个表之间进行选择是一个非常糟糕的主意。 (正如你所发现的那样。)

在那之后,在可能具有“tobias%”或“london%”的列上创建FULLTEXT索引可能会很有用。 但是问题来了,因为地址和人名在不同的表中。

如果你在同一张桌子上都有:

SELECT ...
    WHERE MATCH(firstname, last name, address, city)
          AGAINST("+tobias* +london*" IN BOOLEAN MODE)

并将这个索引放在桌子上

FULLTEXT(firstname, lastname, address, city)

(注意字长等方面的限制)

另一种方法

考虑制作一个可能包含两列的单独表格( Search ):

  • words - 用户可能搜索的所有内容的混搭(名称、城市等)
  • task_id (或任何必要的键)——用于连接到其他表

然后使用它(例如)这样:

SELECT ...
    FROM Search
    JOIN ... ON Search.task_id = ...
    WHERE MATCH(Search.text) AGAINST ("+word1 +word2" IN BOOLEAN MODE)
    ...

它有助于在构建查询时消除任何“短”词。

插入行时,此技术需要额外的努力。 并且数据库中存在冗余数据。 (这是一个禁忌,但通常是性能所必需的。)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM