做“small_table LEFT JOIN large_table”是不好的做法吗?

时间:2012-08-31 07:44:41

标签: mysql indexing

我正在连接到一个我无法管理的数据库,并且我编写了一个查询,它在两个表之间进行了左连接 - 一个小表和一个几个数量级的大表。在某些时候,数据库返回了这个错误:

  

表'/tmp/#sql_some_table.MYI'的密钥文件不正确;尝试修复它

我联系了管理员,我被告知我收到了这个错误,因为我正在进行左连接错误,我不应该将一个小表连接到一个大表,我应该反转连接顺序。他们给出的原因是,当我完成我的方式时,MySQL将尝试创建一个太大的临时表,查询将失败。他们的解决方案在其他地方失败了,但这在这里并不重要。

我发现他们的解释很奇怪,所以我对我的查询进行了解释:

           id = '1'
  select_type = 'SIMPLE'
        table = 'small_table'
         type = 'ALL'
possible_keys = NULL
          key = NULL
      key_len = NULL
          ref = NULL
         rows = '23'
        Extra = 'Using temporary; Using filesort'

           id = '1'
  select_type = 'SIMPLE'
        table = 'large_table'
         type = 'ref'
possible_keys = 'ID,More'
          key = 'ID'
      key_len = '4'
          ref = 'their_db.small_table.ID'
         rows = '41983'
        Extra = NULL

(第二个表中的41983行对我来说不是很有趣,我只需要最新的记录,这就是我的查询最后有order by large_table.ValueDateTime desc limit 1的原因。)

我小心翼翼地按照管理员自己告诉我的列进行选择,应该保存唯一值(因此我假设已编入索引),但似乎他们没有索引这些列。

我的问题是 - 正在以我的方式进行连接('small_table LEFT JOIN large_table')一般的不良做法,或者可以通过正确的索引使这些查询成功执行?

编辑: 这是查询的样子(这不是实际查询,但类似):

select large_table.ValueDateTime as LastDate,
       small_table.DeviceIMEI as IMEI,
       small_table.Other_Columns as My_Names,
       large_table.Pwr as Voltage,
       large_table.Temp as Temperature
from small_table left join large_table on small_table.ID = large_table.ID
where DeviceIMEI = 500
order by ValueDateTime desc
limit 1;

基本上我正在做的是尝试获取设备的最新数据,因为电压和温度会随着时间的推移而变化。 DeviceIMEI,ID和ValueDateTime应该是唯一的,但不会被编入索引(就像我之前说过的,我不管理数据库,我只有读取权限)。

编辑2:

请专注于回答我的实际问题,而不是尝试重写原始查询。

1 个答案:

答案 0 :(得分:1)

左连接是一个红色的鲱鱼。

然而,这是临时表空间不足的实际问题。但是你加入的顺序没有区别。唯一重要的是MySQL必须使用多少行。

这让我看到了LIMIT命令:

问题在于:

为了获得你要求的单行,MySQL必须对ENTIRE记录集进行排序,然后获取最高记录集。为了对它进行排序,它必须将其存储在内存或磁盘上。那就是你的空间不足的地方。您请求的每一列都存储在磁盘上,整个表中,然后排序。

这很慢,非常慢,并占用大量磁盘空间。

解决方案:

您希望MySQL能够使用索引进行排序。但在你的查询中它不能。它使用连接引用的索引,MySQL每个查询只能使用一个索引。

你甚至在排序列上有索引吗?先尝试一下。

另一种选择是进行单独的查询,只选择大表的ID,LIMIT 1.然后临时表变小,因为它只有ID而没有其他所有列。

知道ID后,直接从表中检索所需的所有列。您可以使用子查询一次性执行此操作。如果您发布查询我可以重写它以向您显示,但它基本上是ID = (SELECT ID FROM ..... LIMIT 1)