暂无图片
暂无图片
2
暂无图片
暂无图片
暂无图片

pg_hint_plan技术内幕--05 hint_table

原创 NickYoung 2024-11-30
392

前言

前边几篇介绍了pg_hint_plan的主要原理,以及部分hint的原理。
pg_hint_plan技术内幕–01核心原理
pg_hint_plan技术内幕–02 Scan method
修改disable_cost为guc参数–增强干预执行计划
pg_hint_plan技术内幕–03 Join method
pg_hint_plan技术内幕–04 Join order

本文将继续分析hint_table的原理。

什么时候使用hint_table?为什么要使用hint_table?
我们使用hint时,hint是作为sql文本的一部分去执行,那么意味着必须要修改sql文本了。但是往往有些场景下我们无法修改sql文本,例如定制化SAAS软件服务的SQL等,终端用户无法自定义修改。所以我们可以将sql对应的hint写入hint_table,这样在执行sql时就可以从hint_table查询到对应的hint_str,然后走hint的逻辑去影响执行计划。

所以hint_table有两个能力:
1、不改写sql文本影响执行计划;
2、实现全局共享执行计划。

hint_table对应文档:hint_table

比如以下这个sql,默认pu为inner。
image.png

我们使用hint指定du为inner,并且写入到hint_table。
image.png

当打开enable_hint_table,可以看到执行计划中确实已将du指定为inner。
image.png

原理

在planner里,当我们开启pg_hint_plan插件时,在pg_hint_plan_planner函数中调用get_current_hint_string获取当前hint_str,且当pg_hint_plan.enable_hint_table为on时,进入get_hints_from_table函数查询hint_table,寻找当前query_id的hint_str。
image.png

在get_hints_from_table中,可以看到是使用SPI的方式查询hint_plan.hints表,获取当前query_id的hint_str。

/* * Get hints from table by client-supplied query ID and application name. */ static const char * get_hints_from_table(uint64 queryId, const char *client_application) { const char *search_query = "SELECT hints " " FROM hint_plan.hints " " WHERE query_id = $1 " " AND ( application_name = $2 " " OR application_name = '' ) " " ORDER BY application_name DESC"; static SPIPlanPtr plan = NULL; char *hints = NULL; Oid argtypes[2] = { INT8OID, TEXTOID }; Datum values[2]; char nulls[2] = {' ', ' '}; text *app; Oid namespaceId; bool hints_table_found = false; /* * Make sure that hint_plan.hints is found before we attempt to look for * a hint. */ namespaceId = LookupExplicitNamespace("hint_plan", true); if (OidIsValid(namespaceId) && OidIsValid(get_relname_relid("hints", namespaceId))) hints_table_found = true; if (!hints_table_found) { ereport(WARNING, (errmsg ("cannot use the hint table"), errhint("Run \"CREATE EXTENSION pg_hint_plan\" to create the hint table."))); return NULL; } PG_TRY(); { bool snapshot_set = false; hint_inhibit_level++; if (!ActiveSnapshotSet()) { PushActiveSnapshot(GetTransactionSnapshot()); snapshot_set = true; } SPI_connect(); if (plan == NULL) { SPIPlanPtr p; p = SPI_prepare(search_query, 2, argtypes); plan = SPI_saveplan(p); SPI_freeplan(p); } app = cstring_to_text(client_application); values[0] = Int64GetDatum(queryId); values[1] = PointerGetDatum(app); SPI_execute_plan(plan, values, nulls, true, 1); if (SPI_processed > 0) { char *buf; hints = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1); /* * Here we use SPI_palloc to ensure that hints string is valid even * after SPI_finish call. We can't use simple palloc because it * allocates memory in SPI's context and that context is deleted in * SPI_finish. */ buf = SPI_palloc(strlen(hints) + 1); strcpy(buf, hints); hints = buf; } SPI_finish(); if (snapshot_set) PopActiveSnapshot(); hint_inhibit_level--; } PG_CATCH(); { hint_inhibit_level--; PG_RE_THROW(); } PG_END_TRY(); return hints; } /*
复制

查询到hint_str后,接下来就是按照hint处理逻辑去修改执行计划。

小结

当我们无法改写sql文本时,可以将hint文本写入到hint_table,在执行sql时会查询hint_table对应当前query_id的hint_str,接着去指定执行计划。因此,也可以说hint_table有两个重要能力:1、不改写sql文本影响执行计划;2、实现全局共享执行计划。

当然hint_table还有优化空间,当前是使用SPI的方式查询插件自定义表,当并发高访问时性能可能不会太好;可以为其创建专用的cache,就像访问其他系统表一样。感兴趣的朋友可以尝试优化下。

最后修改时间:2024-12-01 00:14:07
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论