【问题标题】:Use index to speed up query using values from different tables使用索引来加速使用来自不同表的值的查询
【发布时间】:2021-12-17 23:03:15
【问题描述】:

我有一个表 products、一个表 orders 和一个表 orderProducts。

  • 产品的名称为 PK(苹果、香蕉、芒果)和价格。

  • 订单有一个 created_at 日期和一个作为 PK 的 id。

  • orderProducts 连接订单和产品,因此它们有一个 product_name 和一个 order_id。现在我想显示过去 24 小时内发生的给定产品的所有订单。

我使用以下查询:

SELECT 
   orders.id, 
   orders.created_at, 
   products.name,
   products.price
FROM
   orderProducts
JOIN products ON
   products.name=orderProducts.product
JOIN orders ON
   orders.id=orderProducts.order
WHERE
      products.name='banana'
   AND
      orders.created_at BETWEEN NOW() - INTERVAL '24 HOURS' AND NOW()
ORDER BY
   orders.created_at
    

这行得通,但我想用索引优化这个查询。这个索引需要先排序

  • 产品名称,以便过滤
  • 然后是订单的created_at降序排列,所以只能选择24小时前的那些

问题是,据我所见,索引只能在单个表上创建,而不能将其他表的值加入其中。由于两个单独的索引也不能解决这个问题,我想知道是否有另一种方法来优化这个特定的查询。

这里是表格脚本:

CREATE TABLE products
(
    name text PRIMARY KEY,
    price integer,

)

CREATE TABLE orders
(
    id SERIAL PRIMARY KEY,
    created_at TIMESTAMP DEFAULT NOW(),

)

CREATE TABLE orderProducts
(
    product text REFERENCES products(name),
    "order" integer REFERENCES orders(id),

)

【问题讨论】:

  • 理想的索引策略取决于表中的数据。您可以编辑问题并在具有实际数据量的数据库上为该查询添加EXPLAIN (ANALYZE, BUFFERS) 输出吗?
  • 问题是这是一个更复杂的占位符问题。但作为一个比喻,大约有 100 种不同的水果类型,每个订单大约购买 4-10 种不同的水果。它的阅读频率也明显高于写入频率
  • 嗯,答案可能取决于您忽略的细节。正如我所说,您的查询的执行计划会有所帮助。
  • 为什么orderProduct 不包含金额/数量?为什么它没有主键?
  • 因为它是问题的一个例子,不需要主键或数量

标签: postgresql optimization indexing database-indexes


【解决方案1】:

这些规则在性能方面很重要:

  1. 整数索引比字符串索引快,因此,您应该尽量使主键始终为整数。因为连接表也使用主键。
  2. 如果 where 子句中的 when 总是使用两个字段,那么我们必须为这两个字段创建索引。
  3. 外键没有索引,您必须手动为外键字段创建索引。

所以,推荐的表格脚本是:

CREATE TABLE products
(
    id serial primary key, 
    name text,
    price integer
);
CREATE UNIQUE INDEX products_name_idx ON products USING btree (name);


CREATE TABLE orders
(
    id SERIAL PRIMARY KEY,
    created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX orders_created_at_idx ON orders USING btree (created_at);


CREATE TABLE orderProducts
(
    product_id integer REFERENCES products(id),
    order_id integer REFERENCES orders(id)
);
CREATE INDEX orderproducts_product_id_idx ON orderproducts USING btree (product_id, order_id);
---- OR ----  
CREATE INDEX orderproducts_product_id ON orderproducts (product_id);
CREATE INDEX orderproducts_order_id ON orderproducts (order_id);

【讨论】:

  • 这也差不多是我的成绩。问题是,如果我们需要查找 24 小时前给定产品的所有订单,我们需要对 24 小时前的每个条目对 orders_created_at_idx 进行顺序扫描,然后过滤掉那些不属于所需产品的订单,或者创建对 products_name_idx 进行顺序扫描,然后过滤掉 24 小时前未发生的所有条目。所以我想知道我是否可以以某种方式创建一个 orders_created_at_products_name_idx 让我避免对每个请求进行所有过滤
【解决方案2】:

首先。请不要将索引放在任何地方 - 这会导致更改操作变慢...

正如@Laurenz Albe 所提议的那样 - 不要猜测 - 检查。

除此之外。请注意,您知道产品名称,价格重复 - 所以您可以查询一次。询问在您的情况下,两个查询是否会比单个查询更快...检查一下。

请阅读docs。我会试试这个索引:

create index orders_id_created_at on orders(created_at desc, id)

通常 id 应该首先出现,因为它是唯一的,但是这里系统应该能够过滤掉两个谓词 - where/join。这里只是猜测。

orderProducts 我想查看两列的索引,但是对于这个查询,应该只需要一个。在实践中,您将从productsorders,或其他方式 - 两种路径都是可能的,这就是我写关于索引两列的原因。我会使用两个单独的索引:

create index orderproducts_product_id on orderproducts (product_id) include (order_id);
create index orderproducts_order_id on orderproducts (order_id) include (product_id);

可能变化不大,但是......想法是只使用索引,而不是表本身。

【讨论】:

  • 根据这篇文章的语气,我猜测至少有一个连续的过滤器 sqan(过滤产品名称或 created_at 日期)将是不可避免的。您使用包含语义而不是仅仅编写 orderproducts(product_id, order_id) 是否有原因?你是说我应该在我的查询中添加一个 CTE,如果可以,你能举个例子吗?
  • @user2741831 你不需要对这个查询进行 seq-scan。包含意味着数据存储在索引中,因此可以使用它...将两列都编入索引...对于第一个列,它以获取rowid aka tid 的方式工作,并基于该行被获取,所以系统可以得到以后的专栏。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-02-02
  • 1970-01-01
  • 1970-01-01
  • 2015-08-31
  • 1970-01-01
  • 2012-12-31
  • 1970-01-01
相关资源
最近更新 更多