我想知道以下脚本是否可以以某种方式进行优化。它确实向磁盘写了很多,因为它删除了可能的最新行并重新插入它们。我正在考虑应用类似“插入...重复密钥更新”的内容,并发现了单行更新的一些可能性,但我不知道如何在INSERT INTO ... SELECT query
的上下文中应用它。
CREATE OR REPLACE FUNCTION update_member_search_index() RETURNS VOID AS $$
DECLARE
member_content_type_id INTEGER;
BEGIN
member_content_type_id :=
(SELECT id FROM django_content_type
WHERE app_label='web' AND model='member');
DELETE FROM watson_searchentry WHERE content_type_id = member_content_type_id;
INSERT INTO watson_searchentry (engine_slug, content_type_id, object_id
, object_id_int, title, description, content
, url, meta_encoded)
SELECT 'default',
member_content_type_id,
web_member.id,
web_member.id,
web_member.name,
'',
web_user.email||' '||web_member.normalized_name||' '||web_country.name,
'',
'{}'
FROM web_member
INNER JOIN web_user ON (web_member.user_id = web_user.id)
INNER JOIN web_country ON (web_member.country_id = web_country.id)
WHERE web_user.is_active=TRUE;
END;
$$ LANGUAGE plpgsql;
编辑:web_member
,watson_searchentry
,web_user
,web_country
,http://pastebin.com/3tRVPPVi的模式。
重点是更新title
中的列content
和watson_searchentry
。表格上有一个触发器,用于根据这些列设置列search_tsv
的值。
(content_type_id, object_id_int)
中的{p> watson_searchentry
是表中的唯一对,但atm索引不存在(没有用)。
此脚本每天最多应运行一次,以便完全重建搜索索引,有时还会导入一些数据。
答案 0 :(得分:3)
如果您确实需要这些列为NOT NULL
,并且您确实需要字符串'default'
作为engine_slug
的默认值,我建议引入列默认值:
COLUMN | TYPE | Modifiers
-----------------+-------------------------+---------------------
id | INTEGER | NOT NULL DEFAULT ...
engine_slug | CHARACTER VARYING(200) | NOT NULL DEFAULT 'default'
content_type_id | INTEGER | NOT NULL
object_id | text | NOT NULL
object_id_int | INTEGER |
title | CHARACTER VARYING(1000) | NOT NULL
description | text | NOT NULL DEFAULT ''
content | text | NOT NULL
url | CHARACTER VARYING(1000) | NOT NULL DEFAULT ''
meta_encoded | text | NOT NULL DEFAULT '{}'
search_tsv | tsvector | NOT NULL
...
DDL声明将是:
ALTER TABLE watson_searchentry ALTER COLUMN engine_slug DEFAULT 'default';
等
然后您不必每次都手动插入这些值。
另外:object_id text NOT NULL, object_id_int INTEGER
?那很奇怪。我想你有理由......
我会按照您的更新要求:
重点是更新
中的列title
content
和watson_searchentry
当然,您必须添加 UNIQUE 约束来强制执行您的要求:
ALTER TABLE watson_searchentry
ADD CONSTRAINT ws_uni UNIQUE (content_type_id, object_id_int)
将使用随附的索引。通过这个查询开始。
顺便说一下,我几乎从不在Postgres中使用varchar(n)
。只需text
。 Here's one reason.
这可以被重写为具有数据修改公共表表达式的单个SQL查询,也称为“可写”CTE。需要Postgres 9.1或更高版本 此外,此查询仅删除必须删除的内容,并更新可更新的内容。
WITH ctyp AS (
SELECT id AS content_type_id
FROM django_content_type
WHERE app_label = 'web'
AND model = 'member'
)
, sel AS (
SELECT ctyp.content_type_id
,m.id AS object_id_int
,m.id::text AS object_id -- explicit cast!
,m.name AS title
,concat_ws(' ', u.email,m.normalized_name,c.name) AS content
-- other columns have column default now.
FROM web_user u
JOIN web_member m ON m.user_id = u.id
JOIN web_country c ON c.id = m.country_id
CROSS JOIN ctyp
WHERE u.is_active
)
, del AS ( -- only if you want to del all other entries of same type
DELETE FROM watson_searchentry w
USING ctyp
WHERE w.content_type_id = ctyp.content_type_id
AND NOT EXISTS (
SELECT 1
FROM sel
WHERE sel.object_id_int = w.object_id_int
)
)
, up AS ( -- update existing rows
UPDATE watson_searchentry
SET object_id = s.object_id
,title = s.title
,content = s.content
FROM sel s
WHERE w.content_type_id = s.content_type_id
AND w.object_id_int = s.object_id_int
)
-- insert new rows
INSERT INTO watson_searchentry (
content_type_id, object_id_int, object_id, title, content)
SELECT sel.* -- safe to use, because col list is defined accordingly above
FROM sel
LEFT JOIN watson_searchentry w1 USING (content_type_id, object_id_int)
WHERE w1.content_type_id IS NULL;
django_content_type
上的子查询总是返回一个值?否则,CROSS JOIN
可能会造成麻烦。
第一个CTE sel
收集要插入的行。请注意我如何选择匹配列名以简化操作。
在CTE del
中,我避免删除可以更新的行。
在CTE up
中,这些行会更新。
因此,我避免在最终INSERT
之前插入未删除的行。
可以轻松地将其包装到SQL或PL / pgSQL函数中以供重复使用。
对于大量并发使用不安全。比你的功能要好得多,但对并发写入仍然不是100%健壮。但根据您的最新信息,这不是问题。
使用DELETE和INSERT替换UPDATE可能会或者可能不会更昂贵。在内部,每个UPDATE都会产生新的行版本,因为MVCC model。
如果您不关心保留旧行,则更简单的方法可能更快:删除所有内容并插入新行。此外,包装到plpgsql函数可以节省一些计划开销。你的功能基本上是一些简单的简化,并观察上面添加的默认值:
CREATE OR REPLACE FUNCTION update_member_search_index()
RETURNS VOID AS
$func$
DECLARE
_ctype_id int := (
SELECT id
FROM django_content_type
WHERE app_label='web'
AND model = 'member'
); -- you can assign at declaration time. saves another statement
BEGIN
DELETE FROM watson_searchentry
WHERE content_type_id = _ctype_id;
INSERT INTO watson_searchentry
(content_type_id, object_id, object_id_int, title, content)
SELECT _ctype_id, m.id, m.id::int,m.name
,u.email || ' ' || m.normalized_name || ' ' || c.name
FROM web_member m
JOIN web_user u USING (user_id)
JOIN web_country c ON c.id = m.country_id
WHERE u.is_active;
END
$func$ LANGUAGE plpgsql;
我甚至不使用concat_ws()
:它可以安全地抵御NULL
值并简化代码,但比简单连接慢一点。
此外:
表格上有一个触发器,用于设置列
search_tsv
的值 基于这些专栏。
将逻辑合并到此函数中会更快 - 如果这是唯一需要触发器的时间。否则,它可能不值得大惊小怪。