草庐IT

MySQL 将 SELECT 加载 CPU 100% 一分钟

coder 2023-10-11 原文

情况

我的 SQL 查询包含两部分。第一个很简单:

SELECT * FROM `clients` WHERE id IN (...)

这将返回有关具有特定 ID 的客户端的所有信息

第二个查询要大得多,它应该为之前的查询准备 ID 列表(为了更好的可读性我省略了几个 UNION,你可以在最后看到完整的查询):

SELECT client_id
  FROM `contact_persons`
  WHERE id IN (
              SELECT owner_id
                FROM `contacts`
                WHERE contact_info LIKE '%keyword%' AND company_or_person = 'person'
  )
UNION
SELECT owner_id
  FROM `contacts`
  WHERE contact_info LIKE '%keyword%' AND company_or_person = 'company'
UNION
SELECT id
  FROM `clients`
  WHERE client_name LIKE '%keyword%'

这些查询中的每一个都需要不到 0.5 秒来单独执行。但是,当我将第二个查询放入第一个查询时,这两个查询都需要一分多钟才能完成,此时 CPU 负载跃升至 100%。

我尝试了什么

  • 分别运行此查询的两个部分。结果:一切都足够快(不到 0.5 秒)
  • SELECT * ... 更改为 SELECT id ...(尽管这会使整个查询变得无用)。结果:什么都没有改变
  • 使用几个不同的关键字以避免缓存
  • 使内部(大的)返回 0 行。结果:什么都没有改变。完整的查询仍然非常慢

看起来如果我单独运行查询的这些部分并将 ID 列表存储在一个 php 变量中它会工作正常,但即使对于我这个初学者来说也是如此。

完成查询

SELECT * FROM `clients` WHERE deleted = 0 AND id IN (
SELECT client_id as found
  FROM `contact_persons`
  WHERE id IN (
              SELECT owner_id
                FROM `contacts`
                WHERE contact_info LIKE '%keyword%' AND company_or_person = 'person'
 )
UNION
SELECT owner_id as found
  FROM `contacts`
  WHERE contact_info LIKE '%keyword%' AND company_or_person = 'company'
UNION
SELECT id as found
  FROM `clients`
  WHERE client_name LIKE '%keyword%'
UNION
SELECT client_id as found
  FROM `cargo`
  WHERE cargo_name LIKE '%keyword%'
UNION
SELECT page_id as found
  FROM `comments`
  WHERE message LIKE '%keyword%' AND page_type = 'client' AND deleted = 0
UNION
SELECT client_id as found
  FROM `contact_persons`
  WHERE person_name LIKE '%keyword%')

我的数据库非常小 (160 Kb),特别是 clients 表只有 160 行和 5 列。我尝试了所有我能想出的办法,但仍然无法解决问题。

更新 1.解释

EXPLAIN 的结果中我得到

更新 2. 这与 UNION 无关

我刚刚运行了这个最小的查询,其中我用 UNION 切断了每个部分。结果:花费了几乎相同的时间 - 执行 57 秒

SELECT SQL_NO_CACHE id FROM `clients` WHERE id IN (
 SELECT client_id
   FROM `contact_persons`
   WHERE id IN (
               SELECT owner_id
                 FROM `contacts`
                 WHERE contact_info LIKE '%keyword%'
             )
)

更新3.解决方案

正如所建议的那样,我尝试用 INNER JOIN 替换其中一个 IN 并且成功了,~60 秒现在变成了~0.4s

所以代替

SELECT .. WHERE .. IN (SELECT .. WHERE .. IN (SELECT ..))

我是这样写的

SELECT .. AS t1 INNER JOIN (SELECT .. WHERE .. IN (SELECT ..)) AS t2 WHERE ti.id = t2.id

对于人类来说,这几乎是一回事,但看起来它不适用于 MySQL 服务器。

如果有人好奇,我会在此处发布最终查询的 EXPLAIN 输出:

:

最佳答案

您的查询可能太复杂,查询优化器提出了错误的优化。正如@huhushow 建议的那样,针对您的查询运行 EXPLAIN 可能会显示这一点。

您可以尝试多种方法。

  • 更简单的方法是单独运行查询,并以编程方式累积所有 ID,然后使用已删除重复的 ID 列表构建最后一个查询。这就是你觉得“不对劲”的地方。事实上,随着更多 ID 的检索,这种方法可能会变得笨拙。

  • 类似的解决方案是将查询的结果累积到一个临时表中,使用 INSERT IGNOREfound 上的唯一索引来确保重复数据删除。然后,您将在客户表和临时表之间运行 JOIN。

  • 一种完全不同的方法是为 SQL 查询优化器提供更多内存和时间限制,以鼓励它找到更快的查询计划。这里的困难在于干预 mysqld 的参数(您可能没有被授权这样做),而且稍后更改查询可能会导致优化器再次失败;换句话说,这个解决方案不一定稳定。

  • 另一种方法是将外部 SELECT 分布在 UNION 内部,从而将 UNION 引出;即,而不是做

    SELECT a.* WHERE id IN ( select1 UNION select2 UNION select3 )
    

    由于 id 的生成位置和使用位置之间的“分离度”太多,因此不容易优化,您会先做

    SELECT a.* WHERE id IN ( select1 )
    UNION
    SELECT a.* WHERE id IN ( select2 )
    UNION
    SELECT a.* WHERE id IN ( select3 )
    

    然后,依次合并 IN 并将其转换为 JOIN。例如第一组:

    SELECT * FROM `clients` WHERE deleted = 0 AND id IN (
        SELECT client_id as found
        FROM `contact_persons`
        WHERE id IN (
          SELECT owner_id
            FROM `contacts`
            WHERE contact_info LIKE '%keyword%' AND company_or_person
            = 'person' )
    

    将成为第一个:

    SELECT * FROM `clients` WHERE deleted = 0 AND id IN (
        SELECT cp.client_id as found
        FROM `contact_persons` AS cp
        JOIN `contacts` AS c ON (cp.id = c.owner_id)
            WHERE c.contact_info LIKE '%keyword%' AND c.company_or_person
            = 'person' )
    )
    

    最后是优化器更容易的形式:

    SELECT [DISTINCT] clients.* FROM `clients`
        JOIN `contact_persons` AS cp ON (clients.id = cp.client_id)
        JOIN `contacts` AS c ON (cp.id = c.owner_id)
             WHERE clients.deleted = 0 
             AND c.contact_info LIKE '%keyword%'
             AND c.company_or_person = 'person'
    

    作为最后一个阶段,您将添加索引:例如,在上面的例子中,您根据 company_or_personcontacts 中选择 owner_id (这是一个 = 标准)和 contact_info。因此你可以

    CREATE INDEX contacts_ndx_1 ON contacts(company_or_person, contact_info, owner_id)
    

    这还允许完整地执行所有子查询,并查看其中是否有一个特别减慢速度。

关于MySQL 将 SELECT 加载 CPU 100% 一分钟,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34250799/

有关MySQL 将 SELECT 加载 CPU 100% 一分钟的更多相关文章

  1. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  2. ruby-on-rails - rspec should have_select ('cars' , :options => ['volvo' , 'saab' ] 不工作 - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion在首页我有:汽车:VolvoSaabMercedesAudistatic_pages_spec.rb中的测试代码:it"shouldhavetherightselect"dovisithome_pathit{shouldhave_select('cars',:options=>['volvo','saab','mercedes','audi'])}end响应是rspec./spec/request

  3. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  4. ruby-on-rails - 使用 config.threadsafe 时从 lib/加载模块/类的正确方法是什么!选项? - 2

    我一直致力于让我们的Rails2.3.8应用程序在JRuby下正确运行。一切正常,直到我启用config.threadsafe!以实现JRuby提供的并发性。这导致lib/中的模块和类不再自动加载。使用config.threadsafe!启用:$rubyscript/runner-eproduction'pSim::Sim200Provisioner'/Users/amchale/.rvm/gems/jruby-1.5.1@web-services/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:105:in`co

  5. 使用canal同步MySQL数据到ES - 2

    文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co

  6. ruby-on-rails - 从应用程序中自定义文件夹内的命名空间自动加载 - 2

    我们目前正在为ROR3.2开发自定义cms引擎。在这个过程中,我们希望成为我们的rails应用程序中的一等公民的几个类类型起源,这意味着它们应该驻留在应用程序的app文件夹下,它是插件。目前我们有以下类型:数据源数据类型查看我在app文件夹下创建了多个目录来保存这些:应用/数据源应用/数据类型应用/View更多类型将随之而来,我有点担心应用程序文件夹被这么多目录污染。因此,我想将它们移动到一个子目录/模块中,该子目录/模块包含cms定义的所有类型。所有类都应位于MyCms命名空间内,目录布局应如下所示:应用程序/my_cms/data_source应用程序/my_cms/data_ty

  7. ruby-on-rails - 事件记录 : Select max of limit - 2

    我正在尝试将以下SQL查询转换为ActiveRecord,它正在融化我的大脑。deletefromtablewhereid有什么想法吗?我想做的是限制表中的行数。所以,我想删除少于最近10个条目的所有内容。编辑:通过结合以下几个答案找到了解决方案。Temperature.where('id这给我留下了最新的10个条目。 最佳答案 从您的SQL来看,您似乎想要从表中删除前10条记录。我相信到目前为止的大多数答案都会如此。这里有两个额外的选择:基于MurifoX的版本:Table.where(:id=>Table.order(:id).

  8. ruby-on-rails - rails 上的 ruby : radio buttons for collection select - 2

    我有一个集合选择:此方法的单选按钮是什么?谢谢 最佳答案 Rails3中没有这样的助手。在Rails4中,它是collection_radio_buttons. 关于ruby-on-rails-rails上的ruby:radiobuttonsforcollectionselect,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/18525986/

  9. ruby-on-rails - 无法安装 mysql2 0.3.14 gem - 2

    我看到其他人也遇到过类似的问题,但没有一个解决方案对我有用。0.3.14gem与其他gem文件一起存在。我已经完全按照此处指示完成了所有操作:https://github.com/brianmario/mysql2.我仍然得到以下信息。我不知道为什么安装程序指示它找不到include目录,因为我已经检查过它存在。thread.h文件存在,但不在ruby​​目录中。相反,它在这里:C:\RailsInstaller\DevKit\lib\perl5\5.8\msys\CORE\我正在运行Windows7并尝试在Aptana3中构建我的Rails项目。我的Ruby是1.9.3。$gemin

  10. ruby - 如何使用 ruby​​ mysql2 执行事务 - 2

    我已经开始使用mysql2gem。我试图弄清楚一些基本的事情——其中之一是如何明确地执行事务(对于批处理操作,比如多个INSERT/UPDATE查询)。在旧的ruby-mysql中,这是我的方法:client=Mysql.real_connect(...)inserts=["INSERTINTO...","UPDATE..WHEREid=..",#etc]client.autocommit(false)inserts.eachdo|ins|beginclient.query(ins)rescue#handleerrorsorabortentirelyendendclient.commi

随机推荐