【问题标题】:How to optimize the following SELECT query如何优化以下 SELECT 查询
【发布时间】:2020-05-29 17:14:24
【问题描述】:

我们有下表

id  # primary key

device_id_fk 

auth # there's an index on it

old_auth  # there's an index on it

还有下面的查询。

$select_user = $this->db->prepare("
        SELECT device_id_fk 
        FROM wtb_device_auths AS dv 
        WHERE (dv.auth= :auth OR dv.old_auth= :auth) 
        LIMIT 1
");

请说明,我无法访问主客户端的服务器,但这里有另一个数据较少的客户端

由于在 auth 上有很多其他更新查询,更新查询开始被写入慢查询日志和 cpu 峰值

如果您从 auth 中删除索引,则选择查询会写入慢查询日志,但不会写入更新,如果您将索引添加到 device_id_fk,则没有区别。

我尝试使用 union 而不是 or 重写查询,但我被告知仍然存在 cpu 峰值,并且选择查询仍然写入慢查询日志

$select_user = $this->db->prepare("
    (SELECT device_id_fk 
     FROM wtb_device_auths 
     AS dv WHERE dv.auth= :auth) 
    UNION ALL
    (SELECT device_id_fk 
     FROM wtb_device_auths AS dv 
     WHERE dv.old_auth= :auth)
     LIMIT 1"
    );
");

解释

大多数情况下,这是慢查询日志中的唯一查询。是否有更优化的方式来编写查询?有没有更优化的方法来添加索引?客户端在运行 LAMP 的 centos 6 服务器上使用旧的 MariaDB 版本,相当于 MYSQL 5.5

附加信息

每当将索引添加到auth 时,都会记录到慢查询日志中的更新查询是

$update_device_auth = $this->db->prepare("UPDATE wtb_device_auths SET auth= :auth WHERE device_id_fk= :device_id_fk");

【问题讨论】:

标签: mysql sql mariadb


【解决方案1】:

您的少数索引不应拖慢您的更新速度。

您需要两个索引才能使您的更新和选择都表现良好。我最好的猜测是你从来没有同时拥有过。

UPDATE wtb_device_auths SET auth= :auth WHERE device_id_fk= :device_id_fk

您需要在device_id_fk 上建立索引才能使此更新正常运行。不管它的索引是什么,它都应该被声明为外键。

    SELECT device_id_fk 
    FROM wtb_device_auths AS dv 
    WHERE (dv.auth= :auth OR dv.old_auth= :auth) 
    LIMIT 1

您需要auth, old_auth 上的单个组合索引才能使此查询正常执行。

如果没有太多重复,单独的 authold_auth 索引也应该可以正常工作。 MySQL will merge the results from the indexes 并且合并应该很快...除非很多行匹配。

如果您还单独搜索old_auth,请在old_auth 上添加索引。

而且,正如其他人所指出的,select 查询可能会返回具有匹配身份验证或 old_auth 的多个匹配设备之一。这可能很糟糕。如果authold_auth 用于识别设备,则add a unique constraint


或者,您需要重组数据。具有相同值的多个列是一个危险信号。正如您所经历的那样,它可能会导致索引激增,并且还会限制您可以存储的版本数量。相反,每行只有一个身份验证,并允许每个设备有多行。

create table wtb_device_auths (
  id serial primary key,
  device_id bigint not null references wtb_devices(id),
  auth text not null,
  created_at datetime not null default current_timestamp,

  index(auth)
);

现在您只需要搜索一列。

select device_id from wtb_device_auths where auth = ?

现在一个设备可以有许多 wtb_device_auths 行。如果您想要设备的当前身份验证,请搜索最新的。

select device_id
from wtb_device_auths
where device_id = ?
order by created_at desc
limit 1

由于每个设备只有几个身份验证,因此仅使用 device_id 索引可能会非常快;对设备的少数行进行排序会很快。

如果没有,您可能需要一个额外的组合索引,例如 created_at, device_id。这包括单独按created_at 进行搜索和排序,以及同时按created_atdevice_id 进行搜索和排序的查询。

【讨论】:

    【解决方案2】:

    OR 通常会导致缓慢的全表扫描。这个UNION 技巧,加上适当的INDEXes 会快得多:

    ( SELECT  device_id_fk
        FROM  wtb_device_auths AS dv
        WHERE  dv.auth= :auth
        LIMIT  1 )
    UNION ALL
    ( SELECT  device_id_fk
        FROM  wtb_device_auths AS dv
        WHERE  dv.old_auth= :auth
        LIMIT  1 )
    LIMIT 1
    

    并拥有这些“复合”索引:

    INDEX(auth, device_id)
    INDEX(old_auth, device_id)
    

    这些索引可以用相同的第一列替换现有索引。

    请注意,我有 3 个LIMITs;你只有 1 个。

    UNION ALL 涉及一个临时表。您应该升级到 5.7(至少);该版本优化了临时表。

    没有ORDER BYLIMIT 给出随机行;可以吗?

    请提供此查询的慢日志条目的完整文本 - 它包含可能有用的信息。如果“Rows_examined”大于 2(或者可能是 3),那么就会发生一些奇怪的事情。

    【讨论】:

      猜你喜欢
      • 2012-08-21
      • 1970-01-01
      • 2011-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多