为了优化 AlloyDB for PostgreSQL 数据库查询性能,您可以使用 AI 驱动的分析洞见来调整 SQL 查询。ai.tune_sql 函数由大语言模型 (LLM) 提供支持,可通过分析查询执行计划来识别复杂的瓶颈,并提供可据以采取行动的优化建议。
调用 ai.tune_sql 函数时,它会分析您的查询,并在以下方面提供 Query Insights 和建议:
- 查询摘要:分析查询性能瓶颈。
- 查询重写:建议使用优化技术将原始查询重写为等效且更高效的查询。
- 索引建议:建议索引类型,例如表达式索引、通用倒排索引 (GIN) 和可扩缩最近邻 (ScaNN) 索引。
- 统计信息收集建议:根据查询执行计划,建议对多个列执行表
ANALYZE和统计信息收集。 - 全局统一配置 (GUC) 标志调优:通过分析执行计划和系统信息(例如内存和缓冲区用量及大小)来推荐新的 GUC 值。
- 分析和清理:根据分析和清理历史记录以及活动和不活动元组计数,建议对表运行
ANALYZE和VACUUM。
限制
SQL 查询调优具有以下限制:
- 预准备语句和参数化语句:对于预准备语句和参数化语句,
ai.tune_sql不会提供查询重写建议,但仍会提供其他调优建议。如需针对包含参数占位符(例如从 Query Insights 或pg_stat_statements复制的$1或$2)的查询接收查询重写建议,请在运行ai.tune_sql之前将占位符替换为代表性的字面量值。 - 仅当
explain_analyze为TRUE时,才支持只读SELECT语句:当explain_analyze设置为TRUE时,仅支持单个只读SELECT语句。在其他语句(例如INSERT、UPDATE、DELETE、SELECT INTO或SELECT FOR UPDATE)上运行ai.tune_sql并将explain_analyze设置为TRUE会返回出错提示。
准备工作
验证您是否具备以下条件:
- 运行 PostgreSQL 18 或更高版本的 AlloyDB 实例。如需了解详情,请参阅创建集群及其主实例。
- 连接到实例的
psql客户端,或对 AlloyDB Studio 的访问权限。 - 已启用 Vertex AI 集成。
- 安装了
google_ml_integration扩展程序的 1.6.2 或更高版本,并启用了google_db_advisor和hypopg扩展程序。如需启用hypopg扩展程序(ai.tune_sql使用该扩展程序来评估和生成索引建议),请运行CREATE EXTENSION IF NOT EXISTS hypopg;。
启用 SQL 调优
注册 LLM 并启用所需的数据库标志。
通过在实例区域中注册全局模型端点或区域模型端点,为
ai.tune_sql函数创建 Gemini 模型端点:如需在模型在实例区域中不可用时注册全球模型端点(例如
locations/global上的gemini-3.5-flash),请运行以下语句:CALL google_ml.create_model( model_id => 'gemini-3.5-flash-global', model_request_url => 'https://aiplatform.googleapis.com/v1/projects/PROJECT_ID/locations/global/publishers/google/models/gemini-3.5-flash:generateContent', model_qualified_name => 'gemini-3.5-flash', model_provider => 'google', model_type => 'llm', model_auth_type => 'alloydb_service_agent_iam');将
PROJECT_ID替换为模型所在项目的 ID。如需在实例区域(例如
locations/REGION_ID上的gemini-2.5-pro)中注册区域级模型端点,请运行以下语句:CALL google_ml.create_model( model_id => 'gemini-2.5-pro', model_request_url => 'https://REGION_ID-aiplatform.googleapis.com/v1/projects/PROJECT_ID/locations/REGION_ID/publishers/google/models/gemini-2.5-pro:generateContent', model_qualified_name => 'gemini-2.5-pro', model_provider => 'google', model_type => 'llm', model_auth_type => 'alloydb_service_agent_iam');替换以下内容:
REGION_ID:AlloyDB 实例的区域,例如us-central1。PROJECT_ID:模型所在项目的 ID。
在当前数据库会话中设置 SQL 查询调优建议的模型 ID。指定您在上一步中注册的
model_id,例如'gemini-3.5-flash-global'或'gemini-2.5-pro':SET google_db_advisor.llm_model_id = 'gemini-3.5-flash-global';通过设置
google_db_advisor.enable_ai_tune_sql标志,为当前数据库会话启用 SQL 查询调优:SET google_db_advisor.enable_ai_tune_sql = on;由于
SET仅适用于当前数据库会话,因此您可以通过为这两个标志运行ALTER DATABASE DATABASE_NAME SET FLAG_NAME = VALUE;或配置实例的数据库标志,在会话之间保留这些设置。
手动调优 SQL 查询
如需手动调优 SQL 查询,请使用 SELECT 语句运行 ai.tune_sql 函数。
将
psql客户端连接到 AlloyDB 实例,或打开 AlloyDB Studio。如果您使用
SET配置了google_db_advisor.llm_model_id和google_db_advisor.enable_ai_tune_sql,请在调用ai.tune_sql之前在同一数据库会话中运行这些SET语句。如需调优 SQL 查询,请运行
ai.tune_sql函数。此函数会检索与查询相关的架构,并将其与查询一起发送到 LLM 进行性能分析:SELECT ai.tune_sql(query => 'SQL_QUERY', explain_analyze => TRUE);将
SQL_QUERY替换为您要分析的 SQL 查询。默认情况下,执行参数 (
explain_analyze) 设置为TRUE。设置为TRUE时,该函数会运行EXPLAIN ANALYZE来执行查询并捕获执行计划。这种方法可提供更准确、更有用的查询调优建议。如果您将explain_analyze设置为FALSE,则该函数仅运行EXPLAIN来生成计划,而不执行查询。
查看包含性能建议的查询计划
输出首先显示标准查询计划,然后显示 AI 生成的详细建议。这些建议包括查询摘要、建议的重写,以及有关统计信息收集、GUC 标志调整和清理的建议。ai.tune_sql 函数可能需要 2 到 4 分钟才能生成建议。
以下示例展示了反联接查询的 ai.tune_sql 输出:
SELECT ai.tune_sql(
'SELECT *
FROM store_sales
WHERE ss_customer_sk NOT IN (
SELECT sr_customer_sk
FROM store_returns
WHERE sr_item_sk = ss_item_sk)');
tune_sql
--------------------------------------------------
NOTE
--------------------------------------------------
Advices generated with the assistance of an LLM. Independent verification of these advices is required due to the potential for inaccuracies.
--------------------------------------------------
ORIGINAL QUERY PLAN
--------------------------------------------------
Seq Scan on store_sales (cost=0.00..111292742.52 rows=1440384 width=115) (actual time=0.092..103093.207 rows=1306312.00 loops=1)
Filter: (NOT (ANY (ss_customer_sk = (SubPlan 1).col1)))
Rows Removed by Filter: 1574198
Buffers: shared hit=72987539
SubPlan 1
-> Index Scan using idx_sr_item_sk on store_returns (cost=0.42..76.74 rows=18 width=4) (actual time=0.005..0.034 rows=19.38 loops=2880510)
Index Cond: (sr_item_sk = store_sales.ss_item_sk)
Index Searches: 2880510
Buffers: shared hit=72933670
Planning Time: 0.257 ms
--------------------------------------------------
QUERY SUMMARY
--------------------------------------------------
The original query plan shows a Sequential Scan on store_sales with a correlated SubPlan for the NOT IN condition. For each of the 1.44 million rows in store_sales, it executes an Index Scan on store_returns, resulting in over 2.88 million index searches. This nested loop execution is the primary bottleneck, taking over 103 seconds. Converting the correlated NOT IN subquery to an anti-join (NOT EXISTS) will allow the optimizer to use a Hash Anti Join, processing both tables in bulk and significantly reducing execution time.
--------------------------------------------------
AI SUGGESTED QUERY 1
--------------------------------------------------
SELECT * FROM store_sales WHERE NOT EXISTS (SELECT 1 FROM store_returns WHERE store_returns.sr_item_sk = store_sales.ss_item_sk AND (store_returns.sr_customer_sk = store_sales.ss_customer_sk OR store_returns.sr_customer_sk IS NULL OR store_sales.ss_customer_sk IS NULL));
Reason: The original query uses a NOT IN subquery which is executed as a correlated subquery (SubPlan), leading to millions of index scans. Rewriting to NOT EXISTS allows the optimizer to use a Hash Anti Join, which is much more efficient for large datasets. The OR IS NULL conditions ensure strict semantic equivalence with NOT IN when dealing with NULL values.
--------------------------------------------------
REWRITTEN QUERY PLAN
--------------------------------------------------
Gather (cost=8541.96..139088.40 rows=154194 width=115)
Workers Planned: 2
-> Parallel Hash Anti Join (cost=7541.96..122669.00 rows=64248 width=115)
Hash Cond: (store_sales.ss_item_sk = store_returns.sr_item_sk)
Join Filter: ((store_returns.sr_customer_sk = store_sales.ss_customer_sk) OR (store_returns.sr_customer_sk IS NULL) OR (store_sales.ss_customer_sk IS NULL))
-> Parallel Seq Scan on store_sales (cost=0.00..65872.20 rows=1200320 width=115)
-> Parallel Hash (cost=6044.76..6044.76 rows=119776 width=8)
-> Parallel Seq Scan on store_returns (cost=0.00..6044.76 rows=119776 width=8)
--------------------------------------------------
AI SUGGESTED INDEX 1
--------------------------------------------------
CREATE INDEX idx_sr_item_customer ON store_returns (sr_item_sk, sr_customer_sk);
Reason: A composite index on (sr_item_sk, sr_customer_sk) allows index-only scans for the subquery or early aggregation, reducing table access and speeding up the anti-join condition evaluation.
Note: Indexes are suggested solely based on the given query, but they don't consider the overall system workload.
The suggested indexes improve the performance of the original query.
--------------------------------------------------
GUC FLAGS TUNING
--------------------------------------------------
SET work_mem = '64MB'; The current work_mem of 4MB is too small for a Hash Anti Join on tables with hundreds of thousands of rows. Increasing it to 64MB allows the hash table for store_returns to fit entirely in memory, preventing costly disk spills during the join.
Execution Time: 103167.475 ms
(50 rows)