如何在sql中使连接表更新高效(从我的ruby代码中提高效率!!)

时间:2012-10-05 18:35:23

标签: sql postgresql sql-execution-plan

这是我无法理解的奇怪事情,我在PostGreSql9.2中工作......

我有这个数据库:

movies (id, title, votes)
infos (id, movie_id, info_type, value)

我想用infos.value更新movies.votes,加入movies.id = infos.movi​​e_if,并且只有在info_type = 100(这是投票的类型......)时才会加入。

我尝试了两种不同的查询:

update movies
set votes = cast(i.value as integer)
from movies m inner join infos i on m.id = i.movie_id
where i.info_type = 100

(使用说明)预测大约1100万秒的运行时间(太多了!)

第二次尝试:

update movies m
set votes = cast(
(
  select value
  from infos i
  where i.info_type = 100 and i.movie_id = m.id
  limit 1
) AS integer);

这一个“仅”20万秒......仍然太多了

我真的不知道查询计划是如何工作的,所以我尝试使用ruby脚本(使用active_record)...这是:

Info.find_in_batches(:conditions => "info_type = 100") do |group|
    group.each{ |info|
        movie = Movie.find(info.movie_id)
        movie.votes = info.value.to_i
        movie.save
    }
end

对于那些没有读过ruby的人来说,这个查询只是循环遍历所有满足info_type = 100条件的信息,然后每个信息搜索相应的电影并更新它。

而且速度非常快!只需几分钟,所有的红宝石/ orm开销!!

现在,为什么?知道电影大约是600k的记录,但只有200k(三分之一)有一个带有投票数的信息记录。这仍然无法解释发生了什么。

2 个答案:

答案 0 :(得分:2)

EXPLAIN

正如@ruakh已经解释的那样,你可能误解了EXPLAIN告诉你的内容。如果您想要以秒为单位的实际时间,请使用EXPLAIN ANALYZE

但请注意,这实际上是执行语句。我引用手册here

  

重要提示:请记住,当ANALYZE时,该语句实际执行   使用选项。虽然EXPLAIN会丢弃任何   SELECT将返回的输出,该语句的其他副作用   将照常发生。如果您希望在EXPLAIN ANALYZE上使用INSERT,   UPDATEDELETECREATE TABLE ASEXECUTE声明   该命令会影响您的数据,请使用以下方法:

BEGIN;
EXPLAIN ANALYZE ...;
ROLLBACK;

尽管如此,对第一个查询的估计仍然很高并且表明存在严重问题。

怎么了?

对于第三种方法:对于大型表格,始终会更快一个数量级,以使数据库服务器更新整个(大)表格一次,而不是每行向服务器发送指令 - 如果新值来自数据库,则更是如此。更多this related answer。如果您的测试显示不正确,那么您的(测试)设置可能出现问题。事实上,它是......

第一次查询 完全错误。可怕的性能估计表明它是多么可怕的错误。当您将表movies加入infos子句中的表FROM时,您忘记了WHERE条件将结果行绑定到UPDATE中的行}表。这导致CROSS JOIN,i。即 movies(600k)中的每个行都会在values(200k)中以每次单一投票进行更新,从而导致 120 000 000 000次更新即可。好吃。一切都错了。从不执行此操作。即使在可以回滚的事务中也是如此。

第二次查询 错误。它运行相关子查询,i。即它为每一行运行一个单独的查询。这是600k子查询而不是1,因此性能很差。

这是正确的600k子查询。不是200k。你指示Postgres更新每部电影,无论如何。那些没有匹配infos.value(没有info_type = 100)的人会在NULL中获得votes值,覆盖之前的任何内容。

另外,我想知道LIMIT 1在那里做了什么?

  • (infos.movie_id, infos.info_type)UNIQUE,您不需要LIMIT
  • 或者它不是UNIQUE。如果您打算保留结构,请将UNIQUE index添加到infos

正确查询

UPDATE movies m
SET    votes = i.value::int
FROM   infos i
WHERE  m.id = i.movie_id
AND    i.info_type = 100
AND    m.votes IS DISTINCT FROM i.value::int;

这很像您的第一个查询,只是简化并正确执行,加上增强功能。

  • 无需第二次加入movies。您只需infos子句中的FROM

  • 实际上将要更新的行绑定到携带新值的行,从而避免(非预期的)CROSS JOIN

    WHERE  m.id = i.movie_id
    
  • 避免空的更新,它们带来了无成本的成本。这就是最后一行的目的。

应该是几秒或更短的时间,而不是数百万秒 BTW,索引不会对此查询有所帮助,因为您使用所有(或三分之一)所涉及的表,所以表扫描对于所描述的数据分发更快。

答案 1 :(得分:1)

  

[...](使用说明)预测大约1100万秒的运行时间(太多了!)

     

[...]这个“只有”20万秒......仍然太多了

我认为你误解了EXPLAIN的输出。如its documentation中所述,“估计语句执行成本”(即“计划者猜测运行语句需要多长时间”)不是在中测量的,而是“在成本单位是任意的,但通常意味着磁盘页面提取“。

因此,PostgreSQL猜测第二个语句的运行速度比第一个语句快500倍,但是没有人会在你认为的时间内接近任何一个语句。 : - )