Postgres 多连接慢查询,如何存储默认子记录

ber*_*oss 5 postgresql performance pgadmin explain

我在 Postgres 中有一个小型数据库,大约有 10,000 条记录存储公司客户。
我有一个执行非常频繁的“缓慢”执行查询(大约半秒),我的老板希望我改进它。

首先 - 我的代码:

select customer_alias.id, customer_alias.name, site.address, phone.phonenumber
from customer_alias
join customer on customer_alias.customer_id = customer.id
left join site on customer.default_site_id = site.id
left join contact_phonenumbers as phone on site.default_phonenumber = phone.id
Run Code Online (Sandbox Code Playgroud)

(编辑left join customerjoin customer)

让我突然customer想到的是,即使我没有从该记录中选择任何内容,我也正在执行连接。我目前必须加入它才能获得表default_site_id的外键site

每个客户可以有多个站点,但此列表中只能显示一个站点(必须打开一个客户才能查看所有站点)。所以我的问题是,如果我无法优化查询,是否有不同的方式可以为特定客户存储默认站点?默认电话号码也是如此

一个客户可以有多个站点,但一个站点只有一个客户(多对一)。


EXPLAIN 返回:

Hash Join  (cost=522.72..943.76 rows=5018 width=53)
  Hash Cond: (customer.id = customer_alias.customer_id)
  ->  Hash Right Join  (cost=371.81..698.77 rows=5018 width=32)
        Hash Cond: (site.id = customer.default_site_id)
        ->  Hash Right Join  (cost=184.91..417.77 rows=5018 width=32)
              Hash Cond: (phone.id = site.default_phonenumber)
              ->  Seq Scan on contact_phonenumbers phone  (cost=0.00..121.70 rows=6970 width=17)
              ->  Hash  (cost=122.18..122.18 rows=5018 width=23)
                    ->  Seq Scan on site  (cost=0.00..122.18 rows=5018 width=23)
        ->  Hash  (cost=124.18..124.18 rows=5018 width=8)
              ->  Seq Scan on customer  (cost=0.00..124.18 rows=5018 width=8)
  ->  Hash  (cost=88.18..88.18 rows=5018 width=29)
        ->  Seq Scan on customer_alias  (cost=0.00..88.18 rows=5018 width=29)
Run Code Online (Sandbox Code Playgroud)

EXPLAIN ANALYZE 返回:

Hash Join  (cost=522.72..943.76 rows=5018 width=53) (actual time=12.457..26.655 rows=5018 loops=1)
  Hash Cond: (customer.id = customer_alias.customer_id)
  ->  Hash Right Join  (cost=371.81..698.77 rows=5018 width=32) (actual time=8.589..18.796 rows=5018 loops=1)
        Hash Cond: (site.id = customer.default_site_id)
        ->  Hash Right Join  (cost=184.91..417.77 rows=5018 width=32) (actual time=4.499..11.067 rows=5018 loops=1)
              Hash Cond: (phone.id = site.default_phonenumber)
              ->  Seq Scan on contact_phonenumbers phone  (cost=0.00..121.70 rows=6970 width=17) (actual time=0.007..1.581 rows=6970 loops=1)
              ->  Hash  (cost=122.18..122.18 rows=5018 width=23) (actual time=4.465..4.465 rows=5018 loops=1)
                    Buckets: 1024  Batches: 1  Memory Usage: 277kB
                    ->  Seq Scan on site  (cost=0.00..122.18 rows=5018 width=23) (actual time=0.007..2.383 rows=5018 loops=1)
        ->  Hash  (cost=124.18..124.18 rows=5018 width=8) (actual time=4.072..4.072 rows=5018 loops=1)
              Buckets: 1024  Batches: 1  Memory Usage: 197kB
              ->  Seq Scan on customer  (cost=0.00..124.18 rows=5018 width=8) (actual time=0.009..2.270 rows=5018 loops=1)
  ->  Hash  (cost=88.18..88.18 rows=5018 width=29) (actual time=3.855..3.855 rows=5018 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 309kB
        ->  Seq Scan on customer_alias  (cost=0.00..88.18 rows=5018 width=29) (actual time=0.008..1.664 rows=5018 loops=1)
Total runtime: 27.290 ms"
Run Code Online (Sandbox Code Playgroud)

表结构

/* ------------------------------------------------ ---------------------------
 * 表:contacts.customer
 * ------------------------------------------------- ------------------------*/
创建表contacts.customer(
    id 序列号不为空,
    name integer, --contacts.customer_alias 的外键
    default_site_id 整数,--contacts.site 的外键
    -- 其他 12 个与查询无关的字段
    约束 customer_pkey PRIMARY KEY (id )
    约束 default_site_id 外键 (default_site_id)
    参考contacts.site (id) MATCH SIMPLE
    更新无操作删除无操作;
);

-- 客户别名的循环外键稍后添加

-- 索引:id, name, default_site_id (btree)


/*------------------------------------------------ -----------------------------
 * 表:contacts.customer_alias
 *------------------------------------------------- -------------------------*/
创建表contacts.customer_alias(
    id 序列号不为空,
    customer_id 整数,
    名称文字,
    -- 5 个其他字段(未在查询中使用)
    CONSTRAINT customer_alias_pkey PRIMARY KEY (id ),
    约束 customer_alias_customer_id_fkey 外键(customer_id)
         参考contacts.customer (id) MATCH SIMPLE
         ON UPDATE NO Action ON DELETE NO Action DEFERABLE INITIAL DEFERRED
)
-- 索引:id, customer_id (btree)

-- Customer_alias 外键
更改表contacts.customer
    添加约束 customer_name_fkey 外键(名称)
         参考contacts.customer_alias (id) MATCH SIMPLE
         更新无操作删除无操作;


/* ------------------------------------------------ ---------------------------
 * 表:contacts.site
 * ------------------------------------------------- ------------------------*/
创建表contacts.site(
    id 序列号不为空,
    customer_id 整数,
    地址文本,
    default_contact_id 整数,
    default_phonenumber 整数,
    -- 其他 9 个不相关的领域
    约束 site_pkey PRIMARY KEY (id ),
    约束 site_customer_id_fkey 外键 (customer_id)
         参考contacts.customer (id) MATCH SIMPLE
         更新时无操作 删除时无操作
)
索引:id、customer_id、default_contact_id、default_phonenumber(btree)


/* ------------------------------------------------ ---------------------------
 * 表:contacts.contact_phonenumbers
 * ------------------------------------------------- ------------------------*/
创建表contacts.contact_phonenumbers(
    id 序列号不为空,
    site_id 整数,
    电话号码文本,
    -- 4 个其他不相关的领域
    约束 contact_phonenumbers_pkey PRIMARY KEY (id )
)
-- 索引:id, site_id (btree)

如果我通过 ODBC 运行查询客户端,则需要 450-500 毫秒。如果我在 pgAdmin III 中运行查询,它指出查询需要大约 250 毫秒,尽管有时需要 60-100 毫秒(这是我的目标)。
我目前没有对服务器的 SSH 访问权限,因此无法直接运行它。

我只能在屏幕上随时查看这些行中的大约 100 行,是否可以只检索相关行?例如LIMIT 100, OFFSET 2345,我尝试限制结果,但每次都会执行新的搜索。

非常感谢到目前为止的帮助!

Erw*_*ter 12

你写:

每个客户可以有多个站点,但在此列表中只能显示一个站点。

然而,您的查询会检索所有行。这将是一个优化点。但是您也没有定义site要选择哪个。

无论哪种方式,在这里都无关紧要。您EXPLAINsite扫描仅显示 5026 行(扫描显示5018行customer)。因此,几乎没有任何客户实际上拥有多个站点。你是否ANALYZE跑步前的桌子了EXPLAIN吗?

从我在您的EXPLAIN,索引中看到的数字来看,此查询不会为您提供任何信息。顺序表扫描将是最快的方式。不过,对于 5000 行来说,半秒相当慢。也许你的数据库需要一些常规的性能调优

也许查询本身更快,但“半秒”包括网络传输?EXPLAIN ANALYZE会告诉我们更多。

如果此查询是您的瓶颈,我建议您实施实体化视图


在您提供更多信息后,我发现我的诊断几乎成立。

查询本身需要 27 毫秒。那里没有太大问题。“半秒”是我怀疑的那种误会。较慢的部分是网络传输(加上 ssh 编码/解码,可能还有渲染)。您应该只检索 100 行,这将解决大部分问题,即使这意味着每次都执行整个查询。

如果您像我建议的那样使用实体化视图走这条路线,您可以向表中添加一个没有间隙序列号,并在其上添加索引 - 通过添加一列row_number() OVER (<your sort criteria here>) AS mv_id

然后你可以查询:

SELECT *
FROM   materialized_view
WHERE  mv_id >= 2700
AND    mv_id <  2800;
Run Code Online (Sandbox Code Playgroud)

这将执行得非常快。LIMIT/OFFSET无法竞争,需要先计算整个表,然后才能排序和选择 100 行。

pgAdmin3 计时

当您从查询工具执行查询时,消息窗格显示如下内容:

Total query runtime: 62 ms.
Run Code Online (Sandbox Code Playgroud)

并且状态行同时显示。我引用了 pgAdmin3 的帮助:

状态行将显示上次查询完成所需的时间。如果返回了数据集,不仅会显示服务器执行所用的时间,还会显示从服务器检索数据到“数据输出”页面的时间。

如果您想查看服务器上的时间,您需要使用 SQLEXPLAIN ANALYZE或内置的Shift + F7键盘快捷键或Query -> Explain analyze. 然后,在解释输出的底部,您会得到如下内容:

Total runtime: 0.269 ms
Run Code Online (Sandbox Code Playgroud)