【问题标题】:Optimise MySQL query, which uses sub-query; MariaDB much faster优化 MySQL 查询,使用子查询; MariaDB 快得多
【发布时间】:2016-06-23 20:12:26
【问题描述】:

我有以下查询(带有子查询):

SELECT  ROUND(SUM(cb.points)) AS points
    FROM  clients_bonuses cb
    WHERE  cb.cb_year = 2016
      AND  cb.account_id IN (
        SELECT  CONCAT('85500/',uc.contract)
            FROM  users_contracts uc , users_details ud
            WHERE  ud.id=uc.users_details_id
              AND  uc.platform_id = 1
              AND  ud.id=6  ) 

在我的本地 MySQL 服务器 (10.1.9-MariaDB) 上运行得非常好 (0.2 秒),但是在我的生产 MySQL 服务器 (5.5.46-0ubuntu0.14.04.2) 上它需要 35 秒强>。完成。

我的本​​地数据库是生产数据库的精确副本,硬件配置仅因视频适配器而异(本地内置英特尔显卡)。

我的问题是 - 问题的原因可能是什么?我可以(以及如何)优化这个查询吗?

EXPLAIN 上述查询的结果(注意FirstMatch(cb); Using join buffer (flat, BNL join) 的差异)

(生产服务器):

"id"    "select_type"   "table" "type"  "possible_keys" "key"   "key_len"   "ref"   "rows"  "Extra"
"1" "PRIMARY"   "cb"    "ALL"   \N  \N  \N  \N  "394886"    "Using where"
"2" "DEPENDENT SUBQUERY"    "ud"    "const" "PRIMARY"   "PRIMARY"   "4" "const" "1" "Using index"
"2" "DEPENDENT SUBQUERY"    "uc"    "ALL"   \N  \N  \N  \N  "12243" "Using where"

(本地机器):

"id"    "select_type"   "table" "type"  "possible_keys" "key"   "key_len"   "ref"   "rows"  "Extra"
"1" "PRIMARY"   "ud"    "const" "PRIMARY"   "PRIMARY"   "4" "const" "1" "Using index"
"1" "PRIMARY"   "cb"    "ALL"   \N  \N  \N  \N  "394537"    "Using where"
"1" "PRIMARY"   "uc"    "ALL"   \N  \N  \N  \N  "12238" "Using where; FirstMatch(cb); Using join buffer (flat, BNL join)"

表格(由于这个编辑器,从表格列名称中删除了引号):

  1. users_contracts

    CREATE TABLE users_contracts ( id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, users_details_id INT(11) NOT NULL DEFAULT '0', platform_id INT(11) NOT NULL DEFAULT '0', contract VARCHAR(20) NOT NULL DEFAULT '', createdon TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', createdby INT(11) NOT NULL DEFAULT '0', updatedon TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', updatedby INT(11) NOT NULL DEFAULT '0', portfolio_id INT(11) NULL DEFAULT NULL, PRIMARY KEY (id) ) COLLATE='cp1251_general_ci' ENGINE=MyISAM AUTO_INCREMENT=13617;

  2. clients_bonuses

    CREATE TABLE clients_bonuses ( id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, account_id VARCHAR(255) NOT NULL DEFAULT '', bi_id INT(11) NOT NULL DEFAULT '0', cb_year YEAR NOT NULL DEFAULT '0000', cb_month TINYINT(1) NOT NULL DEFAULT '0', cb_day TINYINT(1) NOT NULL DEFAULT '0', bonus DECIMAL(10,4) NOT NULL DEFAULT '0.0000', amount DECIMAL(20,4) NOT NULL DEFAULT '0.0000', points DECIMAL(20,4) NOT NULL DEFAULT '0.0000', month_lots DECIMAL(10,4) NOT NULL DEFAULT '0.0000', PRIMARY KEY (id) ) COLLATE='cp1251_general_ci' ENGINE=MyISAM AUTO_INCREMENT=395015;

  3. users_details

    CREATE TABLE users_details ( id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, email VARCHAR(100) NOT NULL DEFAULT '', phone VARCHAR(32) NULL DEFAULT NULL, interest TINYINT(1) NOT NULL DEFAULT '0', contact TINYINT(1) NOT NULL DEFAULT '0', instrument TINYINT(1) NOT NULL DEFAULT '0', instrument1 TINYINT(1) NOT NULL DEFAULT '0', comments TEXT NOT NULL, reminder TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', reminder1 TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', last_accessed TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', dealer INT(11) NOT NULL DEFAULT '0', dealer1 INT(11) NOT NULL DEFAULT '0', real_client TINYINT(1) NOT NULL DEFAULT '0', real_client_meta TINYINT(1) NOT NULL DEFAULT '0', real_client_bgtrader TINYINT(1) NOT NULL DEFAULT '0', real_client_bmpro TINYINT(1) NOT NULL DEFAULT '0', advmails TINYINT(1) NOT NULL DEFAULT '0', analysis_status TINYINT(4) NULL DEFAULT '0', real_client_date TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', real_client_meta_date TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', real_client_bgtrader_date TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', real_client_bmpro_date TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', reg_status TINYINT(1) NOT NULL DEFAULT '0', password VARCHAR(100) NOT NULL DEFAULT '', real_name VARCHAR(100) NOT NULL DEFAULT '', date_of_first_points TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', trader_points INT(11) NOT NULL DEFAULT '0', metatrader_points INT(11) NOT NULL DEFAULT '0', bgtrader_points INT(11) NOT NULL DEFAULT '0', bmpro_points INT(11) NOT NULL DEFAULT '0', vps_status TINYINT(1) NOT NULL DEFAULT '0', vps_username VARCHAR(50) NOT NULL DEFAULT '', vps_password VARCHAR(50) NOT NULL DEFAULT '', vps_start_date TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', metatrader5_points INT(11) NOT NULL DEFAULT '0', mt_points INT(11) NOT NULL DEFAULT '0', reminder_from INT(11) NOT NULL DEFAULT '0', reminder_mt4 DATE NULL DEFAULT NULL, taken_bonus DATE NOT NULL DEFAULT '0000-00-00', taken_bonus_from DATE NOT NULL DEFAULT '0000-00-00', experience INT(11) NOT NULL DEFAULT '0', invalid_phone TINYINT(4) NOT NULL DEFAULT '0', invalid_email TINYINT(4) NOT NULL DEFAULT '0', number_of_calls INT(11) NOT NULL DEFAULT '0', last_call DATETIME NULL DEFAULT NULL, PRIMARY KEY (id), UNIQUE INDEX email (email), INDEX users_details_email (email(30)) ) COLLATE='cp1251_general_ci' ENGINE=InnoDB AUTO_INCREMENT=63820;

【问题讨论】:

  • MySQL 和 MariaDB 看起来相同。然而,MariaDB 为查询提供了不同的(我认为“更好”)优化。您应该在两个平台上使用相同的数据库。
  • 很遗憾我不能在两个平台上使用 MariaDB
  • 您的帖子在本地和 Mysql 社区的生产中显示 mariadb,由于这个原因,您在解释中得到不同的查询执行计划,进一步的问题是为什么查询需要时间..so account_id in您保留单个 id 或多个 id 的 client_bonuses 表。为什么它是 varchar。
  • 是的,它是 varchar,但是account_id 是单个 ID(出于“历史”原因)
  • uc.users_details_id ud.id 的索引是主键,所以它已经被索引了

标签: php mysql sql optimization mariadb


【解决方案1】:

IN ( SELECT ... ) 优化不佳。

将其转换为 JOIN 可能会使聚合 (SUM) 膨胀。

FROM ( SELECT ... ) 消除了IN 问题,但存在“膨胀”问题的风险。

所以,EXISTS ( SELECT * ... ) 可能是最好的答案:

SELECT  ROUND(SUM(cb.points)) AS points
    FROM  clients_bonuses cb
    WHERE  cb.cb_year = 2016
      AND  EXISTS 
      ( SELECT  *
            FROM  users_contracts uc
            JOIN  users_details ud  ON  ud.id = uc.users_details_id
            WHERE  uc.platform_id = 1
              AND  ud.id=6
              AND  CONCAT('85500/', uc.contract) = cb.account_id 
      ) 

您可以从中受益:

users_contracts : INDEX(users_details_id, platform_id) -- in either order
clients_bonuses : INDEX(cb_year, account_id, points)  -- in that order

看来您可以通过删除 users_details 来进一步加快速度:

SELECT  ROUND(SUM(cb.points)) AS points
    FROM  clients_bonuses cb
    WHERE  cb.cb_year = 2016
      AND  EXISTS 
      ( SELECT  *
            FROM  users_contracts uc
            WHERE  uc.platform_id = 1
              AND  uc.users_details_id = 6
              AND  CONCAT('85500/', uc.contract) = cb.account_id 
      ) 

这些公式可能适用于自 4.1 以来的所有 MySQL/MariaDB 变体。

【讨论】:

  • 感谢您的建议,测试显示重新设计的查询提高了 10% 的性能(uc.users_details_id 上的索引但仍然存在)
  • 这两个索引你也加了吗?
  • 是的,已添加索引
  • 我期待更多的收获。修改查询并添加索引后请提供EXPLAIN
【解决方案2】:

您应该将ud.id=uc.users_details_id AND uc.platform_id = 1 AND ud.id=6 从WHERE 移动到INNER JOIN

为了让它正常工作,添加 INDEX for uc.users_details_id ud.id 是主键,所以它已经被索引了。

【讨论】:

    猜你喜欢
    • 2014-07-31
    • 1970-01-01
    • 1970-01-01
    • 2021-04-06
    • 2014-03-26
    • 1970-01-01
    • 1970-01-01
    • 2015-09-20
    • 2018-02-07
    相关资源
    最近更新 更多