PostgreSQL 中大表的 ANALYZE 策略

sqb*_*ell 8 postgresql postgresql-9.4

在我们的 PostgreSQL 9.4.4 数据库中,我们有一个每天接收大约 60 万条新记录的表。每天,每晚,我们都会从表中执行一些 ETL 导出。如果在导出之前没有分析过,真的很慢。如果我们运行ANALYZE,速度会更快,因为规划器使用具有我们查询的字段的多列索引。解决慢查询问题的首选方法是什么?我看到三个选项:

  • ANALYZE 在出口之前,
  • 使用自动真空/分析功能,
  • 添加查询特定索引。

第二个选项要求我们指定每个表的自动清理/分析设置,因为默认设置不适用于大表。即使我们正确设置了它,也有一些边缘情况,当它仍然会引起麻烦时。例如:

autovacuum_analyze_scale_factor = 0.1
Run Code Online (Sandbox Code Playgroud)

现在是默认值 - 所以如果我们的表有大约 2500 万条记录,它会在 250 万条记录后分析,这对我们来说不够频繁(我们每天有大约 60 万笔新交易)。但是,如果我们将其设置为 0.02(约 500k 条记录),则可能会发生这样的情况,对于有许多事务(例如 900k)的一天,我们将在 500k 之后运行 ANALYZE,但 400k 将保持未分析状态,这将影响查询性能。

表结构:

                                             Table "public.bet_transactions"
           Column           |            Type             |                           Modifiers
----------------------------+-----------------------------+---------------------------------------------------------------
 id                         | integer                     | not null default nextval('bet_transactions_id_seq'::regclass)
 account_id                 | integer                     | not null
 amount_cents               | integer                     | not null default 0
 money_amount_cents         | integer                     | not null default 0
 bonus_amount_cents         | integer                     | not null default 0
 wager_amount_cents         | integer                     | not null default 0
 currency                   | character varying(3)        | not null default 'EUR'::character varying
 total_balance_before_cents | integer                     | not null default 0
 total_balance_after_cents  | integer                     | not null default 0
 money_balance_before_cents | integer                     | not null default 0
 money_balance_after_cents  | integer                     | not null default 0
 bonus_balance_before_cents | integer                     | not null default 0
 bonus_balance_after_cents  | integer                     | not null default 0
 wager_balance_before_cents | integer                     | not null default 0
 wager_balance_after_cents  | integer                     | not null default 0
 status                     | character varying(255)      | not null
 reason                     | character varying(255)      |
 provider                   | character varying(255)      | not null
 external_unique_id         | character varying(255)      |
 external_group_id          | character varying(255)      |
 external_reason            | character varying(255)      |
 external_data              | hstore                      | not null default ''::hstore
 search_vector              | tsvector                    |
 created_at                 | timestamp without time zone |
 updated_at                 | timestamp without time zone |
 exchange_rate              | double precision            | not null default 1
 original_currency          | character varying(3)        | not null default 'EUR'::character varying
 original_amount_cents      | integer                     | not null default 0
 game_id                    | integer                     | not null
 external_game_id           | character varying(255)      |
 big_win                    | boolean                     |
 contribution_factor        | integer                     |
Indexes:
    "bet_transactions_pkey" PRIMARY KEY, btree (id)
    "ux_bet_transactions_provider_external_unique_id" UNIQUE, btree (account_id, provider, external_unique_id) WHERE external_unique_id IS NOT NULL
    "ix_bet_transactions_account_game_status_and_date" btree (account_id, game_id, created_at) WHERE status::text = 'accepted'::text AND created_at >= '2015-01-01 00:00:00'::timestamp without time zone
    "ix_bet_transactions_account_id_external_group_id" btree (account_id, external_group_id)
    "ix_bet_transactions_date_created_at" btree (date(created_at))
Inherits: game_transactions
Run Code Online (Sandbox Code Playgroud)

示例查询(批处理):

SELECT  "bet_transactions".*, "bet_transactions".tableoid::regclass::text as "table_name" FROM "bet_transactions" INNER JOIN "accounts" ON "accounts"."id" = "bet_transactions"."account_id" INNER JOIN "players" ON "players"."id" = "accounts"."player_id" WHERE (bet_transactions.tableoid::regclass::text = 'bet_transactions'::text) AND ("bet_transactions"."created_at" >= '2015-07-02 22:00:00.000000' AND "bet_transactions"."created_at" < '2015-07-03 22:00:00.000000') AND "bet_transactions"."status" = 'accepted'  ORDER BY "bet_transactions"."created_at" ASC LIMIT 5000 OFFSET 0
Run Code Online (Sandbox Code Playgroud)

你看到任何替代品吗?这种情况通常如何处理?

编辑:添加了表结构和示例查询。

小智 6

正如上面评论中已经指出的,隐藏了一些细节。我从你的问题中了解到,查询计划在ANALYZE. 这可能表明查询规划器使用的统计数据没有反映数据的真实分布。

ANALYZE无论如何,只需要一个样本 - 它不会调查整个表。这意味着autovacuum_analyze_threshold只有当新行会显着改变整个表中的分布时,调整对我来说才有意义。这取决于您的用例。

在我看来,调整样本的大小似乎更重要ANALYE您可以通过设置统计目标来影响表的样本大小(不幸的是问题中没有提到)。这篇博文展示了如何statistics target影响ANALYZE.