我有一个
tickets
表(Heroku 上的 Postgres),其中包含大约 700 万条记录。
我有一个经常使用
exported
字段 true 或 false 运行的单表查询。 当它为 true 时,它运行得非常快(~30ms)。 当为 false 时,速度非常慢(~7000ms)。
我已经重建了索引并运行了
VACCUM
。看起来它只是无法利用索引。 具体来说,这个:
CREATE INDEX index_tickets_owner_exported_deleted_archived_created
ON public.tickets USING btree
(owner_id, deleted_at, exported, archived, created_at);
这是使用
EXPLAIN
进行双向查询:
慢:导出 = false 需要 7 秒
explain (buffers, analyze, verbose)
SELECT "tickets".*
FROM "tickets"
WHERE "tickets"."deleted_at" IS NULL
AND "tickets"."owner_id" = 1211
AND "tickets"."exported" = false
AND "tickets"."archived" = false
ORDER BY tickets.created_at desc
LIMIT 200 OFFSET 0;
Limit (cost=0.09..1352.68 rows=200 width=1054) (actual time=20.736..7755.424 rows=161 loops=1)
Output: id, order_id, number, gross_amount, tare_amount, status, completed_at, canceled_at, created_at, updated_at, net_amount, driver_id, plan_item_id, verified_at, verifier_id, truck_number, creator_id, is_flagged, company_id, signer_id, started_at, haul_type, deleted_at, terms_url, ruckit_trip_id, origin_name, paper_ticket_image_url, created_at_latitude, completed_at_latitude, created_at_longitude, completed_at_longitude, material_name, material_unit, loading_location_name, unloading_location_name, signed_at, signed_at_latitude, signed_at_longitude, cost_code, hours, signed_at_timezone, concrete_mix_code, concrete_type, concrete_inspection_rejected, concrete_pump_wash_out_bags, concrete_pump_primer, verifier_type, verifier_note, exported, exported_at, exported_guid, group_tag, multi_page_tag, multi_page_pdf_url, material_invoice_item_id, trucking_invoice_item_id, original_created_at, source, rotated, held_for_questioning, approver_id, approved_at, approval_note, approved, trucking_invoice_status, material_invoice_status, qa_air, qa_slump, qa_temp, qc_air, qc_slump, qc_temp, o_air, o_slump, o_temp, ocr_complete, note, cycle_time_data, total_cost, connex_exported_at, connex_export_id, black_flagged, driver_name, owner_id, custom_field_1, custom_field_2, custom_field_3, custom_field_4, site_list, verification_locked_at, archived, inventory_status, ocr_behavior, ocr_behavior_set_date, last_ruckit_update_at, hauler_lookup_status, hauler_id, plan_item_lookup_status, inventory_transaction_id, paper_ticket_date, project_id
Buffers: shared hit=4674873
-> Index Scan Backward using index_tickets_on_created_at on public.tickets (cost=0.09..910705.14 rows=134661 width=1054) (actual time=20.734..7755.402 rows=161 loops=1)
Output: id, order_id, number, gross_amount, tare_amount, status, completed_at, canceled_at, created_at, updated_at, net_amount, driver_id, plan_item_id, verified_at, verifier_id, truck_number, creator_id, is_flagged, company_id, signer_id, started_at, haul_type, deleted_at, terms_url, ruckit_trip_id, origin_name, paper_ticket_image_url, created_at_latitude, completed_at_latitude, created_at_longitude, completed_at_longitude, material_name, material_unit, loading_location_name, unloading_location_name, signed_at, signed_at_latitude, signed_at_longitude, cost_code, hours, signed_at_timezone, concrete_mix_code, concrete_type, concrete_inspection_rejected, concrete_pump_wash_out_bags, concrete_pump_primer, verifier_type, verifier_note, exported, exported_at, exported_guid, group_tag, multi_page_tag, multi_page_pdf_url, material_invoice_item_id, trucking_invoice_item_id, original_created_at, source, rotated, held_for_questioning, approver_id, approved_at, approval_note, approved, trucking_invoice_status, material_invoice_status, qa_air, qa_slump, qa_temp, qc_air, qc_slump, qc_temp, o_air, o_slump, o_temp, ocr_complete, note, cycle_time_data, total_cost, connex_exported_at, connex_export_id, black_flagged, driver_name, owner_id, custom_field_1, custom_field_2, custom_field_3, custom_field_4, site_list, verification_locked_at, archived, inventory_status, ocr_behavior, ocr_behavior_set_date, last_ruckit_update_at, hauler_lookup_status, hauler_id, plan_item_lookup_status, inventory_transaction_id, paper_ticket_date, project_id
Filter: ((tickets.deleted_at IS NULL) AND (NOT tickets.exported) AND (NOT tickets.archived) AND (tickets.owner_id = 1211))
Rows Removed by Filter: 6780764
Buffers: shared hit=4674873
Query Identifier: -4426566847541555156
Planning Time: 0.456 ms
Execution Time: 7755.497 ms
快速导出 = true
explain (buffers, analyze, verbose)
SELECT "tickets".*
FROM "tickets"
WHERE "tickets"."deleted_at" IS NULL
AND "tickets"."owner_id" = 1211
AND "tickets"."exported" = true
AND "tickets"."archived" = false
ORDER BY tickets.created_at desc
LIMIT 200 OFFSET 0;
Limit (cost=0.09..3790.43 rows=200 width=1054) (actual time=30.772..32.322 rows=200 loops=1)
Output: id, order_id, number, gross_amount, tare_amount, status, completed_at, canceled_at, created_at, updated_at, net_amount, driver_id, plan_item_id, verified_at, verifier_id, truck_number, creator_id, is_flagged, company_id, signer_id, started_at, haul_type, deleted_at, terms_url, ruckit_trip_id, origin_name, paper_ticket_image_url, created_at_latitude, completed_at_latitude, created_at_longitude, completed_at_longitude, material_name, material_unit, loading_location_name, unloading_location_name, signed_at, signed_at_latitude, signed_at_longitude, cost_code, hours, signed_at_timezone, concrete_mix_code, concrete_type, concrete_inspection_rejected, concrete_pump_wash_out_bags, concrete_pump_primer, verifier_type, verifier_note, exported, exported_at, exported_guid, group_tag, multi_page_tag, multi_page_pdf_url, material_invoice_item_id, trucking_invoice_item_id, original_created_at, source, rotated, held_for_questioning, approver_id, approved_at, approval_note, approved, trucking_invoice_status, material_invoice_status, qa_air, qa_slump, qa_temp, qc_air, qc_slump, qc_temp, o_air, o_slump, o_temp, ocr_complete, note, cycle_time_data, total_cost, connex_exported_at, connex_export_id, black_flagged, driver_name, owner_id, custom_field_1, custom_field_2, custom_field_3, custom_field_4, site_list, verification_locked_at, archived, inventory_status, ocr_behavior, ocr_behavior_set_date, last_ruckit_update_at, hauler_lookup_status, hauler_id, plan_item_lookup_status, inventory_transaction_id, paper_ticket_date, project_id
Buffers: shared hit=22462
-> Index Scan Backward using index_tickets_on_created_at on public.tickets (cost=0.09..910705.14 rows=48054 width=1054) (actual time=30.771..32.297 rows=200 loops=1)
Output: id, order_id, number, gross_amount, tare_amount, status, completed_at, canceled_at, created_at, updated_at, net_amount, driver_id, plan_item_id, verified_at, verifier_id, truck_number, creator_id, is_flagged, company_id, signer_id, started_at, haul_type, deleted_at, terms_url, ruckit_trip_id, origin_name, paper_ticket_image_url, created_at_latitude, completed_at_latitude, created_at_longitude, completed_at_longitude, material_name, material_unit, loading_location_name, unloading_location_name, signed_at, signed_at_latitude, signed_at_longitude, cost_code, hours, signed_at_timezone, concrete_mix_code, concrete_type, concrete_inspection_rejected, concrete_pump_wash_out_bags, concrete_pump_primer, verifier_type, verifier_note, exported, exported_at, exported_guid, group_tag, multi_page_tag, multi_page_pdf_url, material_invoice_item_id, trucking_invoice_item_id, original_created_at, source, rotated, held_for_questioning, approver_id, approved_at, approval_note, approved, trucking_invoice_status, material_invoice_status, qa_air, qa_slump, qa_temp, qc_air, qc_slump, qc_temp, o_air, o_slump, o_temp, ocr_complete, note, cycle_time_data, total_cost, connex_exported_at, connex_export_id, black_flagged, driver_name, owner_id, custom_field_1, custom_field_2, custom_field_3, custom_field_4, site_list, verification_locked_at, archived, inventory_status, ocr_behavior, ocr_behavior_set_date, last_ruckit_update_at, hauler_lookup_status, hauler_id, plan_item_lookup_status, inventory_transaction_id, paper_ticket_date, project_id
Filter: ((tickets.deleted_at IS NULL) AND tickets.exported AND (NOT tickets.archived) AND (tickets.owner_id = 1211))
Rows Removed by Filter: 28056
Buffers: shared hit=22462
Query Identifier: -4426566847541555156
Planning Time: 0.432 ms
Execution Time: 32.383 ms
这是该表上的索引
schemaname | tablename | indexname | tablespace | indexdef
------------+-----------+-------------------------------------------------------+------------+---------------------------------------------------------------------------------------------------------------------------------------------------------
public | tickets | tickets_pkey | | CREATE UNIQUE INDEX tickets_pkey ON public.tickets USING btree (id)
public | tickets | index_tickets_on_approver_id | | CREATE INDEX index_tickets_on_approver_id ON public.tickets USING btree (approver_id)
public | tickets | index_tickets_on_archived | | CREATE INDEX index_tickets_on_archived ON public.tickets USING btree (archived)
public | tickets | index_tickets_on_company_id | | CREATE INDEX index_tickets_on_company_id ON public.tickets USING btree (company_id)
public | tickets | index_tickets_on_connex_export_id | | CREATE INDEX index_tickets_on_connex_export_id ON public.tickets USING btree (connex_export_id)
public | tickets | index_tickets_on_cost_code | | CREATE INDEX index_tickets_on_cost_code ON public.tickets USING btree (cost_code)
public | tickets | index_tickets_on_created_at | | CREATE INDEX index_tickets_on_created_at ON public.tickets USING btree (created_at)
public | tickets | index_tickets_on_custom_field_1 | | CREATE INDEX index_tickets_on_custom_field_1 ON public.tickets USING btree (custom_field_1)
public | tickets | index_tickets_on_custom_field_2 | | CREATE INDEX index_tickets_on_custom_field_2 ON public.tickets USING btree (custom_field_2)
public | tickets | index_tickets_on_deleted_at | | CREATE INDEX index_tickets_on_deleted_at ON public.tickets USING btree (deleted_at)
public | tickets | index_tickets_on_driver_id | | CREATE INDEX index_tickets_on_driver_id ON public.tickets USING btree (driver_id)
public | tickets | index_tickets_on_exported | | CREATE INDEX index_tickets_on_exported ON public.tickets USING btree (exported)
public | tickets | index_tickets_on_group_tag | | CREATE INDEX index_tickets_on_group_tag ON public.tickets USING btree (group_tag)
public | tickets | index_tickets_on_hauler_id | | CREATE INDEX index_tickets_on_hauler_id ON public.tickets USING btree (hauler_id)
public | tickets | index_tickets_on_hauler_lookup_status | | CREATE INDEX index_tickets_on_hauler_lookup_status ON public.tickets USING btree (hauler_lookup_status)
public | tickets | index_tickets_on_held_for_questioning | | CREATE INDEX index_tickets_on_held_for_questioning ON public.tickets USING btree (held_for_questioning)
public | tickets | index_tickets_on_inventory_status | | CREATE INDEX index_tickets_on_inventory_status ON public.tickets USING btree (inventory_status)
public | tickets | index_tickets_on_inventory_transaction_id | | CREATE INDEX index_tickets_on_inventory_transaction_id ON public.tickets USING btree (inventory_transaction_id)
public | tickets | index_tickets_on_is_flagged | | CREATE INDEX index_tickets_on_is_flagged ON public.tickets USING btree (is_flagged)
public | tickets | index_tickets_on_material_invoice_item_id | | CREATE INDEX index_tickets_on_material_invoice_item_id ON public.tickets USING btree (material_invoice_item_id)
public | tickets | index_tickets_on_multi_page_tag | | CREATE INDEX index_tickets_on_multi_page_tag ON public.tickets USING btree (multi_page_tag)
public | tickets | index_tickets_on_number | | CREATE INDEX index_tickets_on_number ON public.tickets USING btree (number)
public | tickets | index_tickets_on_order_id | | CREATE INDEX index_tickets_on_order_id ON public.tickets USING btree (order_id)
public | tickets | index_tickets_on_original_created_at | | CREATE INDEX index_tickets_on_original_created_at ON public.tickets USING btree (original_created_at)
public | tickets | index_tickets_on_owner_id | | CREATE INDEX index_tickets_on_owner_id ON public.tickets USING btree (owner_id)
public | tickets | index_tickets_on_owner_id_and_deleted_at_and_archived | | CREATE INDEX index_tickets_on_owner_id_and_deleted_at_and_archived ON public.tickets USING btree (owner_id, deleted_at, archived)
public | tickets | index_tickets_on_plan_item_id | | CREATE INDEX index_tickets_on_plan_item_id ON public.tickets USING btree (plan_item_id)
public | tickets | index_tickets_on_plan_item_lookup_status | | CREATE INDEX index_tickets_on_plan_item_lookup_status ON public.tickets USING btree (plan_item_lookup_status)
public | tickets | index_tickets_on_rotated | | CREATE INDEX index_tickets_on_rotated ON public.tickets USING btree (rotated)
public | tickets | index_tickets_on_ruckit_trip_id | | CREATE INDEX index_tickets_on_ruckit_trip_id ON public.tickets USING btree (ruckit_trip_id)
public | tickets | index_tickets_on_signer_id | | CREATE INDEX index_tickets_on_signer_id ON public.tickets USING btree (signer_id)
public | tickets | index_tickets_on_trucking_invoice_item_id | | CREATE INDEX index_tickets_on_trucking_invoice_item_id ON public.tickets USING btree (trucking_invoice_item_id)
public | tickets | index_tickets_on_verification_locked_at | | CREATE INDEX index_tickets_on_verification_locked_at ON public.tickets USING btree (verification_locked_at)
public | tickets | index_tickets_on_verified_at | | CREATE INDEX index_tickets_on_verified_at ON public.tickets USING btree (verified_at)
public | tickets | index_tickets_on_verifier_id_and_verifier_type | | CREATE INDEX index_tickets_on_verifier_id_and_verifier_type ON public.tickets USING btree (verifier_id, verifier_type)
public | tickets | index_tickets_on_approved | | CREATE INDEX index_tickets_on_approved ON public.tickets USING btree (approved)
public | tickets | index_tickets_on_project_id | | CREATE INDEX index_tickets_on_project_id ON public.tickets USING btree (project_id)
public | tickets | index_tickets_owner_exported_deleted_archived_created | | CREATE INDEX index_tickets_owner_exported_deleted_archived_created ON public.tickets USING btree (owner_id, deleted_at, exported, archived, created_at)
我感谢你们提供的任何帮助。
您可以采取两种方法:
改进估计。
首先,
ANALYZE
查看表格,看看这是否会产生影响。如果是,您需要调整 autovacuum 以便它更频繁地分析表:
ALTER TABLE tickets SET (autovacuum_analyze_scale_factor = 0.01);
如果这没有效果,您可以尝试收集更详细的统计数据。您可以使用
ALTER TABLE ... ALTER COLUMN ... SET STATISTICS ...
使 PostgreSQL 计算出更多“最常见的值”和更详细的直方图。然后再次ANALYZE
桌子,看看你是否能有所作为。
最后,还有扩展统计数据来涵盖函数依赖性:
CREATE STATISTICS mystats
ON owner_id, exported, archived, deleted_at
FROM tickets;
ANALYZE tickets;
如果这些都没有帮助:
您可以重写
ORDER BY
条件,使PostgreSQL无法使用created_at
上的索引:
ORDER BY created_at + INTERVAL '0' DESC
然后优化器必须求助于其他索引。
正如其他人提到的,您可能在该表上有太多索引。