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

Halo数据库-Query树节点可重入读技术

原创 Halo Tech 2023-11-10
264

Query树PostgreSQL中非常重要的一个数据结构。其一个特点是可被序列化/反序列化。这个特性很重要的一个应用是规则系统(Rule System)。例如,PostgreSQL中的视图(View)是构建在规则系统之上的,当我们定义一个视图:

CREATE VIEW v_test AS SELECT * FROM test WHERE grade = 9;
CREATE VIEW
复制

本质上是将AS后的语句的Query树进行序列化并存储到系统里:

SELECT ev_action FROM pg_rewrite WHERE ev_class = (SELECT oid FROM pg_class WHERE relname='v_test');
                                                       ev_action
--------------------------------------------------------------------------------------------------------------------------------
 ({QUERY :commandType 1 :querySource 0 :canSetTag true :utilityStmt <> :resultRelation 0 :hasAggs false :hasWindowFuncs false :hasTargetSRFs false :hasSubLinks false :hasDistinctOn false :hasRecurs
ive false :hasModifyingCTE false :hasForUpdate false :hasRowSecurity false :cteList <> :rtable ({RTE :alias {ALIAS :aliasname old :colnames <>} :eref {ALIAS :aliasname old :colnames ("empno" "grade
" "depno" "name" "sal")} :rtekind 0 :relid 50695 :relkind v :rellockmode 1 :tablesample <> :lateral false :inh false :inFromCl false :requiredPerms 0 :checkAsUser 0 :selectedCols (b) :insertedCols 
...})
复制

通过这个序列化的Query树,我们可以知道这个Query的类型(commandType)、目标表、目标列等查询所需要的详细信息。而经过反序列化后,就可以还原为一棵完整的Query树,这棵Query树便可以直接进行Plan,生成执行计划,然后执行。实际上,我们查询视图的定义时,也会发现系统里的定义和我们原始的定义是有显著的差别的:

SELECT definition FROM pg_views WHERE viewname='v_test';
        definition         
---------------------------
  SELECT test.empno,      +
     test.grade,          +
     test.depno,          +
     test.name,           +
     test.sal             +
    FROM test             +
   WHERE (test.grade = 9);
(1 row)
复制

这是因为系统里的定义实际上就是根据序列化的Query树(如上),经过反序列化再反编译而来的,并不是原始的定义语句。

应该说,PostgreSQL这种机制设计的还是非常优秀的。但是,在实际的代码实现中还存在一些不足。最致命的一个缺陷是,如果一些原因(比如缺陷修复等)导致Query树的结构定义有变动(比如增加了一个节点),这时候系统更新就是个非常麻烦的事。你将不得不重新初始化整个数据库群集!如果只是将程序进行了升级,将会导致很严重的问题,甚至可能导致系统无法连接!

我们来看一下pg_views中的definition是怎么来的。这个definition实际上就是通过backend/nodes/readfuncs.c中提供的_readQuery方法,再经过一些转化而来的。这个__readQuery其实就是反序列化Query树的过程。

static Query *
_readQuery(void)
{
  READ_LOCALS(Query);

  READ_ENUM_FIELD(commandType, CmdType);
  READ_ENUM_FIELD(querySource, QuerySource);
  local_node->queryId = UINT64CONST(0);  /* not saved in output format */
  READ_BOOL_FIELD(canSetTag);
  ...
    READ_DONE();
}
复制

这些READ_XXX方法就是通过不断往前推进字符串(序列化的Query树)指针直到末尾,从而重新构建一棵Query树。所以,如果Query树的定义变动了,比如增加了节点,而原来系统里存在的这些Query树并没有新节点的信息,而READ_XXX方法的指针是不断往前推进的,因而最终会导致空指针的错误,从而引发系统的崩溃。

针对这个问题,我们在Halo产品中研发了Query树节点可重入读的技术。简单来说,就是READ_XXX方法的指针会根据需要重新调整位置,从而避免空指针的错误。其中一个比较关键的技术实现是token可重入读,部分代码如下:

/* reenterable pg_strtok */
const char *
pg_strtok_reentrant(char *token_name, int *length)
{
    ...
  /* Reenterable token read support */
  token_len = strlen(token_name) + 1;
  token1 = malloc(sizeof(char) * (token_len + 1));
  token1[0] = ':';
  token1[1] = '\0';
  strcat(token1, token_name);
  token1[token_len] = '\0';
  token2 = malloc(sizeof(char) * (token_len + 1));
  token2[0] = '\0';
  strncpy(token2, ret_str, token_len);
  token2[token_len] = '\0';
  if (strcasecmp(token1, token2) != 0)
  {
    *length = -1;
    pg_strtok_ptr = prev_strtok_ptr;
    free(token1);
    free(token2);
    return ret_str;
  }
    ...
}
复制

我们通过比较读到的token和传入的token是否相等,如果不相等,工作指针便会回退到工作前的位置,从而实现了可重入读。

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

评论

冷狼
暂无图片
1年前
评论
暂无图片 0
Query树PostgreSQL中非常重要的一个数据结构
1年前
暂无图片 点赞
评论