【问题标题】:Optimization MySQL near match query优化 MySQL 近匹配查询
【发布时间】:2016-08-20 06:21:09
【问题描述】:

我有一个这样的查询(我的真实查询包括额外的连接和过滤器):

SELECT CustomerId, 
  if (username = '$username', 1, 0) +
  if (name     = '$name'    , 1, 0) +
  if (phone    = '$phone'   , 1, 0) +
  if (address  = '$address' , 1, 0) AS matches
FROM Customers
ORDER BY matches DESC
LIMIT 50

该查询采用多个过滤器并将其与客户信息进行比较,以查找并返回最接近的匹配项。我的问题是这很慢,而且这个查询在一个包含数百万客户记录的巨大数据库上运行。目前正在以分钟为单位查看执行时间。

有人对我如何优化此查询或编写新查询以执行近似匹配搜索有任何建议吗?

【问题讨论】:

  • 这 4 个字段:usernamenamephoneaddress 是否已编入索引?
  • 能否提供show create table Customers的输出
  • @Drew 实际查询不只是使用一个表。它正在加入包含正在搜索的特定客户信息的其他表。可能有大约 10 个表被加入。我还要注意,所有正在搜索和加入的信息都已编入索引
  • @user11406 出于好奇,查询时间是如何变得如此糟糕的?用户在很长一段时间内一定已经在处理越来越慢的查询时间了。
  • @MonkeyZeus 问题更多在于以不利用表索引的方式搜索大量客户数据。按主键或表索引过滤的常规查询很好,并且通常会像您期望的那样在几秒钟内返回结果。

标签: php mysql sql optimization query-optimization


【解决方案1】:

这应该会有所帮助,但我并不积极:

SELECT
    CustomerId, 
    if (username = '$username', 1, 0) +
    if (name     = '$name'    , 1, 0) +
    if (phone    = '$phone'   , 1, 0) +
    if (address  = '$address' , 1, 0) AS matches
FROM Customers
WHERE username = '$username' OR // at least one of these columns should match or else we don't care about this record, right?
      name     = '$name' OR
      phone    = '$phone' OR
      address  = '$address'
ORDER BY matches DESC
LIMIT 50

此查询假定您不关心匹配零个条件的记录,因此 MySQL 可以有效地在 WHERE 子句中使用它的索引,您只需计算匹配至少一个记录的 matches 分数搜索条件。


关于“你的代码容易被 MySQL 注入,等等,等等,等等”的强制性说明

【讨论】:

  • 我已经试过了。它似乎确实有一点帮助(大约快 10%),但这还不足以使查询变得可行
  • @user11406 您很可能正在突破单个 MySQL 实例的限制,并且可能需要在存储需求方面变得更加复杂;查看stackoverflow.com/a/14733561。另外,您已经在 SSD 上运行了吗?
【解决方案2】:

原始查询需要进行表扫描。带有OR 的版本也可以。没有任何指数对任何一种表述都有一点帮助。由于您的表很大,将OR 转换为UNION 应该可以正常工作:

SELECT CustomerId, SUM(ct) AS matches FROM (
( SELECT CustomerId, COUNT(*) AS ct FROM Customers Where username = '$username' )
UNION ALL
( SELECT CustomerId, COUNT(*) AS ct FROM Customers Where name = '$name' )
UNION ALL
( SELECT CustomerId, COUNT(*) AS ct FROM Customers Where phone = '$phone' )
UNION ALL
( SELECT CustomerId, COUNT(*) AS ct FROM Customers Where address= '$address' )
)
GROUP BY CustomerId
ORDER BY matches DESC LIMIT 50;

它将需要这 4 个索引:

INDEX(username, CustomerId),
INDEX(name,     CustomerId),
INDEX(phone,    CustomerId),
INDEX(address,  CustomerId)

每个子 SELECT 将非常有效地扫描相应索引的一小部分,将很少的行写入 tmp 表。然后外部的 SELECT 将进行排序(很可能在 RAM 中,所以忽略 EXPLAIN 中的“filesort”)并提供最多 50 行。

是的,注意 SQL 注入;它可能会破坏您的服务器。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-28
    • 2013-11-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多