MySQL查询-将ORDER BY关闭可使查询速度提高100倍

时间:2018-07-08 22:09:27

标签: mysql sorting query-optimization

我在系统中发现很长的查询。 MySQL慢日志显示以下内容:

# Time: 2018-07-08T18:47:02.273314Z
# User@Host: server[server] @ localhost []  Id:  1467
# Query_time: 97.251247  Lock_time: 0.000210 Rows_sent: 50  Rows_examined: 41646378
SET timestamp=1531075622;
SELECT n1.full_name AS sender_full_name, s1.email AS sender_email, 
e.subject, e.body, e.attach, e.date, e.id, r.status, 
n2.full_name AS receiver_full_name, s2.email AS receiver_email, 
r.basket, 
FROM email_routing r 
JOIN email e ON e.id = r.message_id 
JOIN people_emails s1 ON s1.id = r.sender_email_id 
JOIN people n1 ON n1.id = s1.people_id 
JOIN people_emails s2 ON s2.id = r.receiver_email_id 
JOIN people n2 ON n2.id = s2.people_id 
WHERE r.sender_email_id = 21897 ORDER BY e.date desc LIMIT 0, 50;

EXPLAIN查询不显示全表扫描,并且查询使用索引:

id select_type table partitions type    possible_keys key       key_len  ref                  rows filtered Extra
1  SIMPLE      s1    NULL       const   PRIMARY       PRIMARY   4        const                1    100.00   Using temporary; Using filesort
1  SIMPLE      n1    NULL       const   PRIMARY,ppl   PRIMARY   4        const                1    100.00   NULL
1  SIMPLE      n2    NULL       index   PRIMARY,ppl   ppl       771      NULL                 1    100.00   Using index
1  SIMPLE      s2    NULL       index   PRIMARY       s2        771      NULL                 3178 10.00    Using where; Using index; Using join buffer (Block Nested Loop)
1  SIMPLE      r     NULL       ref     bk1,bk2,msgid bk1       4        server.s2.id         440  6.60     Using where; Using index
1  SIMPLE      e     NULL       eq_ref  PRIMARY       PRIMARY   4        server.r.message_id  1    100.00   NULL

这是我对所用表的SHOW CREATE TABLE查询:

CREATE TABLE `email_routing` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `message_id` int(11) NOT NULL,
 `sender_email_id` int(11) NOT NULL,
 `receiver_email_id` int(11) NOT NULL,
 `basket` int(11) NOT NULL,
 `status` int(11) NOT NULL,
 `popup` int(11) NOT NULL DEFAULT '0',
 `tm` int(11) NOT NULL DEFAULT '0',
 KEY `id` (`id`),
 KEY `bk1` (`receiver_email_id`,`status`,`sender_email_id`,`message_id`,`basket`),
 KEY `bk2` (`sender_email_id`,`tm`),
 KEY `msgid` (`message_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1055796 DEFAULT CHARSET=utf8

-

CREATE TABLE `email` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `subject` text NOT NULL,
 `body` text NOT NULL,
 `date` datetime NOT NULL,
 `attach` text NOT NULL,
 `attach_dir` varchar(255) CHARACTER SET cp1251 DEFAULT NULL,
 `attach_subject` varchar(255) DEFAULT NULL,
 `attach_content` longtext,
 `sphinx_synced` datetime DEFAULT NULL,
 PRIMARY KEY (`id`),
 KEY `Index_2` (`attach_dir`),
 KEY `dt` (`date`)
) ENGINE=InnoDB AUTO_INCREMENT=898001 DEFAULT CHARSET=utf8

-

CREATE TABLE `people_emails` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `nick` varchar(255) NOT NULL,
 `email` varchar(255) NOT NULL,
 `key_name` varchar(255) NOT NULL,
 `people_id` int(11) NOT NULL,
 `status` int(11) NOT NULL DEFAULT '0',
 `activity` int(11) NOT NULL,
 `internal_user_id` int(11) NOT NULL,
 PRIMARY KEY (`id`),
 KEY `s2` (`email`,`people_id`)
) ENGINE=InnoDB AUTO_INCREMENT=22146 DEFAULT CHARSET=utf8

-

CREATE TABLE `people` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `fname` varchar(255) CHARACTER SET cp1251 NOT NULL,
 `lname` varchar(255) CHARACTER SET cp1251 NOT NULL,
 `patronymic` varchar(255) CHARACTER SET cp1251 NOT NULL,
 `gender` tinyint(1) NOT NULL,
 `full_name` varchar(255) NOT NULL DEFAULT ' ',
 `category` int(11) NOT NULL,
 `people_type_id` int(255) DEFAULT NULL,
 `tags` varchar(255) CHARACTER SET cp1251 NOT NULL,
 `job` varchar(255) CHARACTER SET cp1251 NOT NULL,
 `post` varchar(255) CHARACTER SET cp1251 NOT NULL,
 `profession` varchar(255) CHARACTER SET cp1251 DEFAULT NULL,
 `zip` varchar(16) CHARACTER SET cp1251 NOT NULL,
 `country` int(11) DEFAULT NULL,
 `region` varchar(10) NOT NULL,
 `city` varchar(255) CHARACTER SET cp1251 NOT NULL,
 `address` varchar(255) CHARACTER SET cp1251 NOT NULL,
 `address_date` date DEFAULT NULL,
 `last_update_ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`),
 KEY `ppl` (`id`,`full_name`)
) ENGINE=InnoDB AUTO_INCREMENT=415040 DEFAULT CHARSET=utf8

这是这4个表的SHOW TABLE STATUS输出:

Name          Engine Version Row_format Rows    Avg_row_length Data_length Max_data_length Index_length Data_free Auto_increment
email         InnoDB 10      Dynamic    753748  12079          9104785408  0               61112320     4194304   898167
email_routing InnoDB 10      Dynamic    900152  61             55132160    0               69419008     6291456   1056033
people        InnoDB 10      Dynamic    9538    386            3686400     0               2785280      4194304   415040
people_emails InnoDB 10      Dynamic    3178    752            2392064     0               98304        4194304   22146

MySQL版本5.7.22 Ubuntu 16.04

但是我注意到一件事-如果我将ORDER BY从查询中删除,但离开LIMIT ,则查询几乎立即运行,耗时不超过0.2秒。因此,我开始考虑在没有ORDER BY的情况下运行查询并通过PHP进行排序,但最终似乎变得很复杂,因为使用没有ORDER BY的LIMIT会导致错误的排序范围。

还有什么我可以做以加快或优化该查询的事情吗?

替代我可以通过我的PHP代码进行排序和分页。我将附加的columnt添加到SELECT ..., UNIX_TIMESTAMP(e.date) as ts中,然后执行以下操作:

<?php
...
$main_query = $server->query($query);
$emails_list = $main_query->fetch_all(MYSQLI_ASSOC);
function cmp($a, $b) {
    return strcmp($a['ts'], $b['ts']);
}

$emails_sorted = usort($emails_list, "cmp");
for ($i=$start;$i<$lenght;$i++)
{
    $singe_email = $emails_sorted[$i]
    // Format the output
}

但是当我这样做的时候

  

致命错误:允许的内存大小为134217728字节已耗尽

$emails_sorted = usort($emails_list, "cmp");行的

4 个答案:

答案 0 :(得分:1)

如果您的数据很快返回,那么如何包装它……但是实际上要返回多少行而没有限制。也许在...之后,您仍然可以获得更好的性能。

select PQ.*
   from ( YourQueryWithoutOrderByAndLimt ) PQ
   order by PQ.date desc 
   LIMIT 0, 50;

答案 1 :(得分:1)

警告,我对MySQL不太熟悉,实际上,我主要是在我(大多数)阅读有关MySQL的知识的基础上投射MSSQL经验。

1)可能的解决方法:可以安全地假设email.id和email.date总是相同的顺序吗?从功能的角度看,随着时间的推移将电子邮件添加到表中并因此具有不断增加的自动编号,这似乎是合乎逻辑的……但是,数据的初始加载是按不同/随机顺序进行的吗?无论如何,如果是library(shiny) ui<- fluidPage( sidebarLayout(position="left", sidebarPanel("Parameters",width = 4, radioButtons("Type","Test", choices= list("Test"="p", "Test"="l")), actionButton("GO","Open Modual") ), mainPanel( plotOutput("Test") ))) server<- function(input,output){ Credential<-function(Test){ showModal(modalDialog( title = "Credentials Required", textInput("Username", "Enter User Name", value = ""), textInput("Password", "Enter Password:", value = ""), footer = actionButton("Submit", "Submit"), modalButton("Cancel")) ) #Use Assigned Username and Password to go fetch data. #Note data must be returned, somehow need to pause or somthing here. } #Call Function observeEvent(input$GO,{ data <- Credential("Test") }) } shinyApp(server=server,ui=ui) 而不是ORDER BY e.id会发生什么?

2)在ORDER BY e.date上添加复合索引(按该顺序!)是否有帮助?

3)如果所有这些都无济于事,请将查询分为两部分可能会对优化器有所帮助。 (您可能需要修复MySQL的语法)

email (id, date)

答案 2 :(得分:1)

我怀疑这是MySQL联接优化器高估了块嵌套循环(BNL)联接的好处的情况。您可以尝试通过以下方法关闭BNL:

set optimizer_switch='block_nested_loop=off';

希望这将提供更好的加入顺序。您也可以尝试:

set optimizer_prune_level = 0;

强制联接优化器探索所有可能的联接顺序。

另一个选择是使用STRAIGHT_JOIN强制执行特定的连接顺序。在这种情况下,查询文本中指定的顺序似乎很好。因此,要强制执行此特定的加入顺序,您可以编写

SELECT STRAIGHT_JOIN ...

请注意,无论您做什么,您都无法期望查询的速度与没有ORDER BY时一样快。只要您需要查找来自特定发件人的最新电子邮件,并且电子邮件表中没有有关发件人的信息,就无法使用索引来避免排序,而无需浏览所有发件人的所有电子邮件。如果您在email_routing表中有关于日期的信息,情况将会有所不同。然后可以使用该表上的索引来避免排序。

答案 3 :(得分:0)

MySQL无法在查询中使用索引排序依据,因为

  

查询联接了许多表,而ORDER BY中的列不是   全部来自用于检索行的第一个非恒定表。   (这是EXPLAIN输出中的第一个表,没有   const连接类型。)

MySQL Order By Optimization