1

这是我无法理解的奇怪事情,我正在使用 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 (这是投票的类型..)

我尝试了 2 个不同的查询:

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);

这应该是“只有”2万秒..还是太多了

我真的不知道查询计划是如何工作的,所以我尝试使用 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 条件的信息,然后为每个它搜索相应的电影并更新它。

而且速度非常快!只需几分钟,还有所有的 ruby​​/orm 开销!!

现在,为什么?知道电影大约有 60 万条记录,但只有 20 万条(三分之一)有带有票数的信息记录。这仍然不能解释正在发生的事情。

4

2 回答 2

2

EXPLAIN

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

但请注意,这实际上执行了语句。我在这里引用手册:

ANALYZE 重要提示:请记住,当使用该选项时,该语句实际上会被执行。尽管 EXPLAIN 将丢弃 SELECT 将返回的任何输出,但该语句的其他副作用将照常发生。如果您希望EXPLAIN ANALYZEINSERT, UPDATE, DELETE, CREATE TABLE AS, orEXECUTE语句上使用而不让命令影响您的数据,请使用以下方法:

BEGIN;
EXPLAIN ANALYZE ...;
ROLLBACK;

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

怎么了?

至于您的第三种方法:对于大表,让数据库服务器一次更新整个(大)表总是比为每一行向服务器发送指令快一个数量级 - 如果新的值来自数据库内部。更多在这个相关的答案中。如果您的测试结果并非如此,那么您的(测试)设置可能有问题。而事实上,它是...

您的第一个查询完全出错了。糟糕的性能估计表明它是多么的错误。当您在子句中将表连接movies到表时,您忘记了将结果行绑定到表中的行的条件。这导致 a ,即(600k) 中的每一行都会随着 (200k) 中的一次投票而更新,从而导致120 000 000 000 次更新。好吃。而且都错了。永远不要执行这个。甚至在可以回滚的事务中也不行。infosFROMWHEREUPDATECROSS JOINmoviesvalues

您的第二个查询也出错。它运行一个相关子查询,即它为每一行运行一个单独的查询。那是 60 万个子查询,而不仅仅是 1 个,因此性能很差。

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

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

  • 要么(infos.movie_id, infos.info_type)UNIQUE,那你就不需要LIMIT
  • 或者不是UNIQUE。如果您打算保留该结构,请添加UNIQUE indexto 。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第二次加入。你只需要infosFROM子句中。

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

    WHERE  m.id = i.movie_id
    
  • 避免空更新,它们会付出代价而没有收获。这就是最后一行的用途。

应该是几秒钟或更短的时间,而不是几百万秒。
顺便说一句,索引对这个查询没有帮助,表扫描对于所描述的数据分布来说更快,因为您使用了所有(或三分之一)所涉及的表。

于 2012-10-05T21:06:55.420 回答
1

[…] 其中(使用解释)预测运行时间约为 1100 万秒(太多了!)

[…] 这应该是“只有”20 000 秒.. 还是太多了

我认为您误解了EXPLAIN. 正如其文档中所解释的那样,“估计的语句执行成本”(即“计划者对运行语句需要多长时间的猜测”)不是以为单位,而是“以任意成本单位,但通常意味着磁盘页面获取”。

所以 PostgreSQL 猜测第二条语句的运行速度将比第一条语句快大约 500 倍,但两者都不会像你想象的那么长。:-)

于 2012-10-05T19:30:16.130 回答