将一个查询拆分为四个以避免大规模连接?

时间:2010-08-09 04:38:39

标签: php mysql optimization

所以我的查询看起来像这样:

SELECT col1, col2, col3 ...
FROM action_6_members m
LEFT JOIN action_6_5pts f ON f.member_id = m.id
LEFT JOIN action_6_10pts t ON t.member_id = m.id
LEFT JOIN action_6_weekly w ON w.member_id = m.id
WHERE `draw_id` = '1' ORDER BY m.id DESC LIMIT 0, 20;

现在这是一次大规模的加入(350万* 4万* 2万)

所以我的想法是:

执行SELECT * FROM action_6_members WHERE draw_id = '1' ORDER BY id DESC LIMIT 0, 20;

然后使用php构建循环 $in = "IN(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)";

然后运行
select * from action_6_5pts where member_id in $in
select * from action_6_10pts where member_id in $in
select * from action_6_weekly where member_id in $in

然后使用php,

将它们全部一起刷掉

这意味着,虽然我使用了四种不同的查询,但我只选择了20行,而不是全部加入。

我会注意到一个重要的绩效奖金吗?

<小时/> 的更新
所以,普遍的共识是,“不要这样做!”

这是应用程序的一般概述

它收到一个代码,

代码是5pt,10pt或每周代码,

所有三种代码类型都在单独的表中。 这三个表有代码,member_id

member_id链接到action_6_members表中的id。

当声明代码时,数据将填入action_6_members表中。

然后在表中填写该成员的id以获取所声明的代码。

以上查询选择前20个成员。

那么我的问题是。

我可以做些什么来改善这个?

目前所有内容都在查询完成之前超时。

action_6_members

CREATE TABLE `action_6_members` (
  `id` int(11) NOT NULL auto_increment,
  `draw_id` int(11) NOT NULL,
  `mobile` varchar(255) NOT NULL,
  `fly_buys` varchar(255) NOT NULL,
  `signup_date` datetime NOT NULL,
  `club` int(11) NOT NULL default '0' COMMENT '1 = yes, 2 = no',
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1337 DEFAULT CHARSET=latin1

action_6_ 5和10pts

CREATE TABLE `action_6_5pts` (
  `code` varchar(255) NOT NULL,
  `member_id` int(11) NOT NULL,
  PRIMARY KEY  (`code`),
  KEY `member_id` (`member_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1

action_6_weekly

CREATE TABLE `action_6_weekly` (
  `id` int(11) NOT NULL auto_increment,
  `code` varchar(255) NOT NULL,
  `member_id` int(11) NOT NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `id` (`id`),
  KEY `member_id` (`member_id`)
) ENGINE=MyISAM AUTO_INCREMENT=3250001 DEFAULT CHARSET=latin1

<小时/> 更新2:解释查询

id select_type table type possible_keys key       key_len ref  rows   Extra  
1  SIMPLE      m     ALL  \N            \N        \N      \N   1390   Using temporary; Using filesort  
1  SIMPLE      f     ALL  member_id     \N      \N      \N   36000  
1  SIMPLE      t     ALL  member_id     \N      \N      \N   18000  Using where  
1  SIMPLE      w     ref  member_id     member_id 4    m.id 525820 Using where  

刚刚通过: 来自DB 7.26,4.60,2.45的最新负载数据

1.0是正常的最大负载......任何上述意味着它必须“爆发”并调用其他进程来处理。即7.26表示负载是刀片服务器最大值的7倍,并且不得不呼叫其他人来帮助

所以目前这个查询不仅仅是一个怪物,它吃怪物作为零食...

8 个答案:

答案 0 :(得分:7)

作为一般规则,如果您的SQL查询可以完全模拟您想要做的事情,那么它可能比将其拆分为以PHP(或任何其他语言)粘合在一起的部分更快,边界

这些界限是:

  1. 对于这种情况,一定不能在MySQL中隐藏奇怪的病态行为。
  2. 您必须在所有必要的列上都有合理的索引。
  3. 没有(或没有可能的)情况,您只能在PHP中合理地检测/处理您希望在中途中止查询的情况。
  4. 您的结果集在病态上并不大(例如,它适合内存,并且不超过max_allowed_packetmy.cnf的大小。
  5. 现在,这并没有解决您的SQL(或PHP中提议的替代实现)是否最佳您正在做什么,但只有在有关您的应用程序的更多信息时才能解决这个问题确实是你真正想要到达的终点。它可能没问题,也可能没有。


    快速浏览一下表格结构的更新,没有任何事情可能导致我出现大型性能问题,但是:

    • 除非您确定需要,否则请勿使用MyISAM。 InnoDB是你的朋友,特别是如果表有大量的写入流量。 MyISAM的全桌锁可以真正咬你。拥有FOREIGN KEYS以获得参考完整性也很不错。
    • action_6_weekly idPRIMARY KEYUNIQUE KEYid。这是多余的。 PRIMARY KEY实际上是UNIQUE KEY的超集,您无需创建单独的UNIQUE KEY
    • 您查询的EXPLAIN输出会很有趣。

答案 1 :(得分:1)

多次旅行,返回;在应用程序和数据库之间?不,与单个查询相比,这不会提供性能加成。

答案 2 :(得分:1)

您不必使用PHP来执行此操作,您可以使用子查询或多查询存储过程在一个查询中执行此操作。

要查看哪一个更快,请对它们进行基准测试。

答案 3 :(得分:1)

奇怪的是,我将不同意共识,至少部分是这样。

首先,你永远不应该使用LEFT JOIN。这很诱人,但这几乎总是一个坏主意。在您的情况下,我假设action_6_5pts,action_6_10pts和action_weekly表可能不包含所有成员ID。 (我猜测你的数据,所以如果每个表都保证包含所有成员id,那么请从你的查询中删除LEFT,你应该没问题。)

我怀疑最初可能有更好的方式来布置数据。通常,将相同类型的数据组合到单个表中是个好主意。我不想对你的数据进行猜测,所以我会给你一个伪示例。我已经看过很多次人们采用类似的数据并将其分成多个表(较小的表更好吗?)。不总是。例如,如果您正在构建发票系统,那么可能很容易想到将发票按月分成单独的表。所以你创建invoice_Jan2010,invoice_Feb2010 ......等等但是如果你想搜索怎么办?同一个客户可能并非在所有月份都是如此,因此很难仅使用该客户获得列表,而不使用LEFT JOIN。呸。我们不喜欢LEFT JOIN!这很慢!

更好的方法是拥有一张带有日期(索引!)和每个客户ID的发票表。任何JOIN都保证找到发票,除非客户不存在(这无关紧要)

现在在你的情况下,也许你可以在一张桌子上制作5分和10分旗,每周是约会?我在做假设,不知道更多,很难给你“正确”的答案。

现在我说我不同意这个共识。如果你不改变你的数据,通常如果你有一个像你说的非常大的表,使用IN语句分成4个查询比LEFT JOIN更好。如果你想加快速度,你可以使用UNION将所有4个组合成1。这应该比LEFT JOIN快。

您也可以轻松证明这一点。接受您的查询并将关键字EXPLAIN放在它前面并直接在Mysql上执行它(使用其中一个工具:命令行,Mysql GUI甚至phpmyadmin)。这将为您提供计划如何将表格加在一起的细分。

对于此答案,输出的解释太长,但通常每行输出将告诉您查询将加入多少行。越小越好。它还会告诉你如何加入。如果可能的话,“使用临时”或“使用文件排序”是您想要避免的(尽管如果您排序它将会出现以便做好准备)。还有一列用于使用哪些键来连接行。如果该列为空,则应尝试创建索引以使其更好地工作。

希望有所帮助!祝你好运!

答案 4 :(得分:0)

不要那样做。数据库在连接表和选择相关行方面非常快 - 就像你进行大量单个查询一样快。

答案 5 :(得分:0)

在您尝试之前,您不会知道这种方法会给您带来多少性能提升。根据我的经验,将这种查询更改为离散查询并不是您可以预测的。您正在寻找的是MySQL中的一个引爆点,在这个临界点中创建超过一定大小的内部表是一个杀手。一旦你知道安装中的那个点,你就可以玩查询拼接和后期处理的游戏。

答案 6 :(得分:0)

您应该使用带有Join的in子句,而不是使用Limit。限制在加入后运行,而不是作为查询的一部分。

答案 7 :(得分:0)

我可能会生气,但我在原始查询中action_6_members上过滤的字段{$ 1}}上无法看到索引。

这意味着查询必须扫描draw_id表中的所有数据,然后加入其他数据。

action_6_members列添加索引可能会有所帮助。

您可以创建(draw_iddraw_id)的组合键,但除非您没有从id表中提取任何数据,否则这可能不会给您带来太多帮助(如果你不是,那么可以使用多字段索引而不是读取数据表)

希望有帮助...