这是我无法理解的奇怪事情,我在PostGreSql9.2中工作......
我有这个数据库:
movies (id, title, votes)
infos (id, movie_id, info_type, value)
我想用infos.value更新movies.votes,加入movies.id = infos.movie_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(三分之一)有一个带有投票数的信息记录。这仍然无法解释发生了什么。
答案 0 :(得分:2)
EXPLAIN
正如@ruakh已经解释的那样,你可能误解了EXPLAIN
告诉你的内容。如果您想要以秒为单位的实际时间,请使用EXPLAIN ANALYZE
。
但请注意,这实际上是执行语句。我引用手册here:
重要提示:请记住,当
ANALYZE
时,该语句实际执行 使用选项。虽然EXPLAIN会丢弃任何 SELECT将返回的输出,该语句的其他副作用 将照常发生。如果您希望在EXPLAIN ANALYZE
上使用INSERT
,UPDATE
,DELETE
,CREATE
TABLE AS
或EXECUTE
声明 该命令会影响您的数据,请使用以下方法: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倍,但是没有人会在你认为的时间内接近任何一个语句。 : - )