跨多个表优化mysql查询

Posted

技术标签:

【中文标题】跨多个表优化mysql查询【英文标题】:optimize mysql query across multiple tables 【发布时间】:2015-03-22 04:42:54 【问题描述】:

我在优化以下连接多个表的查询时遇到了一些问题。

SELECT count(*) as count, `snapshot_id`, `snapshot_guid`, `image`, `subject`, `name`, `brands`.`facebook`, `brands`.`brand_id`, `brand_guid`, `date_sent`
FROM (`snapshots`)
INNER JOIN `brands` ON `snapshots`.`brand_id` = `brands`.`brand_id`
WHERE `snapshots`.`status` =  1
AND `brands`.`status` =  1
AND `brands`.`archive` =  0
GROUP BY `snapshots`.`brand_id`, `snapshots`.`subject`
ORDER BY `date_sent` desc
LIMIT 20

执行时间:4.9 秒

使用解释:

id  select_type  table       type   possible_keys                           key                   key_len    ref                                   rows Extra
1     SIMPLE         brands      ALL    PRIMARY,brand_id,status,brand_status    NULL                    NULL       NULL                                338  Using where; Using temporary; Using filesort
1     SIMPLE         snapshots   ref    brand_id,status,snapshot_brand_status   snapshot_brand_status   5          mockd_catalog.brands.brand_id,const 166  

描述品牌和快照的表格:

描述品牌

Field   Type    Null    Key Default Extra
brand_id    int(11) NO  PRI NULL    auto_increment
brand_guid  char(12)    NO  MUL NULL    
friendly    varchar(128)    YES     NULL    
name    varchar(128)    NO      NULL    
url varchar(2048)   YES     NULL    
logo    text    YES     NULL    
cover   text    YES     NULL    
facebook    varchar(2048)   YES     NULL    
address_1   varchar(128)    YES     NULL    
address_2   varchar(128)    YES     NULL    
city    varchar(50) YES     NULL    
state   varchar(50) YES     NULL    
postal  varchar(20) YES     NULL    
country_code    varchar(128)    YES     NULL    
date_created    datetime    YES     NULL    
date_modified   datetime    YES     NULL    
hp_snapshot tinyint(1)  NO      0   
status  tinyint(1)  YES MUL 0   
archive tinyint(1)  NO      0   

描述快照

Field   Type    Null    Key Default Extra
snapshot_id int(11) NO  PRI NULL    auto_increment
snapshot_guid   char(36)    YES MUL NULL    
brand_id    int(11) NO  MUL 0   
email   varchar(256)    NO  MUL NULL    
seed_email  varchar(256)    NO      NULL    
date_sent   datetime    NO  MUL NULL    
date_created    datetime    NO      NULL    
date_modified   datetime    YES     NULL    
content_type    varchar(10) YES     NULL    
subject varchar(256)    NO      NULL    
source  longtext    YES     NULL    
html    longtext    NO      NULL    
html_error  text    YES     NULL    
thumbnail   text    YES     NULL    
image   text    YES     NULL    
status  tinyint(1)  NO  MUL 0   
archive tinyint(1)  NO      0   
tags    text    YES     NULL    

我已经尝试了所有我能想到的方法,但似乎无法进一步优化此查询。任何帮助表示赞赏。

瑞克

编辑 2015 年 3 月 22 日@美国东部时间上午 10:27:

    CREATE TABLE `snapshots` (
  `snapshot_id` int(11) NOT NULL AUTO_INCREMENT,
  `snapshot_guid` char(36) DEFAULT NULL,
  `brand_id` int(11) NOT NULL DEFAULT '0',
  `email` varchar(256) NOT NULL,
  `seed_email` varchar(256) NOT NULL,
  `date_sent` datetime NOT NULL,
  `date_created` datetime NOT NULL,
  `date_modified` datetime DEFAULT NULL,
  `content_type` varchar(10) DEFAULT NULL,
  `subject` varchar(256) NOT NULL,
  `source` longtext,
  `html` longtext NOT NULL,
  `html_error` text,
  `thumbnail` text,
  `image` text,
  `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '-1= error, 0 = new, 1 = approved, 2 = review ',
  `archive` tinyint(1) NOT NULL DEFAULT '0',
  `tags` text,
  PRIMARY KEY (`snapshot_id`),
  KEY `snapshot_id` (`snapshot_id`) USING BTREE,
  KEY `brand_id` (`brand_id`),
  KEY `email` (`email`(255)),
  KEY `status` (`status`),
  KEY `snapshot_guid` (`snapshot_guid`) USING BTREE,
  KEY `subject` (`subject`(255)),
  KEY `archive` (`archive`),
  KEY `archive_status` (`archive`,`status`),
  KEY `date_sent` (`date_sent`) USING BTREE,
  KEY `recent_snapshots` (`snapshot_id`,`snapshot_guid`,`archive`,`status`,`brand_id`,`date_sent`,`subject`(255)) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=95002 DEFAULT CHARSET=utf8;

品牌表:

CREATE TABLE `brands` (
  `brand_id` int(11) NOT NULL AUTO_INCREMENT,
  `brand_guid` char(12) NOT NULL,
  `friendly` varchar(128) DEFAULT NULL,
  `name` varchar(128) NOT NULL,
  `url` varchar(2048) DEFAULT NULL,
  `logo` text,
  `cover` text,
  `facebook` varchar(2048) DEFAULT NULL,
  `address_1` varchar(128) DEFAULT NULL,
  `address_2` varchar(128) DEFAULT NULL,
  `city` varchar(50) DEFAULT NULL,
  `state` varchar(50) DEFAULT NULL,
  `postal` varchar(20) DEFAULT NULL,
  `country_code` varchar(128) DEFAULT NULL,
  `date_created` datetime DEFAULT NULL,
  `date_modified` datetime DEFAULT NULL,
  `hp_snapshot` tinyint(1) NOT NULL DEFAULT '0',
  `status` tinyint(1) DEFAULT '0',
  `archive` tinyint(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`brand_id`),
  KEY `brand_guid` (`brand_guid`) USING BTREE,
  KEY `brand_id` (`brand_id`),
  KEY `status` (`status`) USING BTREE,
  KEY `archive_status` (`archive`,`status`),
  KEY `archive` (`archive`)
) ENGINE=InnoDB AUTO_INCREMENT=423 DEFAULT CHARSET=utf8;

选择语句:

SELECT 
    COUNT(*) AS count,
    `snapshot_id`,
    `snapshot_guid`,
    `image`,
    `subject`,
    `name`,
    `brands`.`facebook`,
    `brands`.`brand_id`,
    `brand_guid`,
    `date_sent`
FROM
    (`snapshots`)
        INNER JOIN
    `brands` ON `snapshots`.`brand_id` = `brands`.`brand_id`
WHERE
    `snapshots`.`archive` = 0
        AND `snapshots`.`status` = 1
        AND `brands`.`archive` = 0
        AND `brands`.`status` = 1
GROUP BY `snapshots`.`brand_id` , `snapshots`.`subject`
ORDER BY `date_sent` DESC
LIMIT 20;

解释:

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  snapshots   ref brand_id,status,archive,archive_status  status  1   const   48304   Using where; Using temporary; Using filesort
1   SIMPLE  brands  eq_ref  PRIMARY,brand_id,status,archive_status,archive  PRIMARY 4   mockd_catalog.snapshots.brand_id    1   Using where

【问题讨论】:

能不能把上面两张表的SQL DDL贴出来,而不是“DESC table_name”? 您在寻找创建语句吗? 是的,我正在寻找“创建表”语句。 请参阅上面的编辑。谢谢! 非常感谢任何帮助或指导。 【参考方案1】:

有几个问题:

date_send 上的密钥在 order by date_sent 中使用时定义为 using hash。 您有一个带有brand.statusbrand.archive 的where 子句。 archive 仅在组合键 brand_id, status, archive 中,但由于 where 子句中未使用第一列 (brand_id),因此无法使用。仅为archive 或复合(status, archive) 创建索引。 subject 需要自己的索引。目前其指数深埋在综合指数中。

一般来说,复合索引中列的顺序很重要。该索引仅在使用的列是第一个列时才有用。此外,第一列越宽,它们的效果就越差。这意味着

KEY `snap_indx` (`snapshot_id`,`snapshot_guid`,`email`(255),`date_sent`,`subject`(255),`status`,`archive`)

效率低,因为 subject 的 255 个字节位于 statusarchive 之前,它们每个只有 4 个字节。

【讨论】:

您好 Lorenz,根据您的反馈进行了一些更改,但仍有问题。请看我上面的编辑。 关于我应该添加什么索引的任何想法?此时查询仍然需要 4 - 5 秒。 清理你的索引,保留你需要的。然后,使用解释尝试创建最佳索引。避免使用Using temporaryUsing filesort

以上是关于跨多个表优化mysql查询的主要内容,如果未能解决你的问题,请参考以下文章

MySQL - 添加多个派生表时查询慢 - 优化

Mysql学会查看sql的执行计划

在日志表上按顺序使用多个连接优化 MySQL 查询以找到最大差异(最佳记录)

170727MySQL查询性能优化

mysql 查询优化 ~ 多表查询改写思路

mysql中,如何向测试人员介绍连接查询和子查询的优劣势?