从抓包发现并解决 Navicat 编辑 TiDB 视图报错的问题
作者: leeray 原文来源:https://tidb.net/blog/57768a45
【是否原创】是
【首发渠道】知乎 - 神州数码云基地
【首发渠道链接】https://zhuanlan.zhihu.com/p/428374317
【正文】
从抓包发现并解决 Navicat 编辑 TiDB 视图报错的问题
一、引言
TiDB 是一个高度兼容 MySQL 协议的分布式数据库。无论是连接协议还是语法语法上,TiDB 都在不断完善对于 MySQL 的兼容性。但对于 MySQL 周边工具的兼容上,TiDB 并非尽善尽美。今天要说的就是知名的数据库连接工具 Navicat 或者是 Navicat Premium 连接到 TIDB 使用其编辑视图功能时遇到的问题。
二、问题发现
Navicat 提供可视化的方法给我们来操作连接到的数据库。这是一个非常方便的功能,我们不需要牢记创建表,视图,索引等具体的 SQL 语法。使用 Navicat 右键选项即可完成这些操作。但当笔者使用其连接到 TIDB,并准备编辑试图时,却提示某个函数不存在的报错信息。具体是 “ERROR 1305 - FUNCTION load_file does not exist”。其实这个报错把问题说的很明白。也就是 Navicat 编辑视图时会调用 load_file 这个函数。而 TIDB 目前还没有实现这个函数,从而导致编辑试图报错。
从官网文档 (https://docs.pingcap.com/zh/tidb/stable) 以及 asktug(https://asktug.com/) 搜索相关内容,得到两条线索:
1、TiDB 目前确实没有实现 load_file 函数 (https://docs.pingcap.com/zh/tidb/stable/string-functions# 不支持的函数)
2、Navicat 连接 TiDB 编辑试图会报错的问题早已有之,在 asktug 上有人反馈相同问题。(Tidb 3.0.2 通过 navicat 查看 view 报 load_file 函数不存在?)
通过以上的内容确定 Navicat 报这个错的原因在于 TiDB 目前还没有实现 load_file 函数。通过查阅 MySQL 官方文档可以得知 load_file 的定义:”Reads the file and returns the file contents as a string. “(https://dev.mysql.com/doc/refman/8.0/en/string-functions.html#function_load-file)。也就是说传入文件地址,返回文件的字符串形式的内容即可。实现这个函数的逻辑非常 easy。正当笔者准备打开 TIDB 源码大改特改时,笔者不禁想问,Navicat 编辑视图需要 load_file 方法是为什么?基于笔者一年多以来在 TIDB 和 MySQL 使用差异上踩坑的经验来看,不能盲目实现函数,要考虑到 TiDB 作为分布式数据库,其文件组织方式已与单机 MySQL 有天差地别。所以现在最好的方式就是通过抓包的方式看看 Navicat 使用 load_file 函数干了什么事。
话不多说,立马开干。一开始笔者使用最新 master 源码在本地编译运行。抓包时发现数据都是 tls 加密的。没办法看到具体内容。想了想办法,通过切到 4.0 版本的某个分支,再次本地编译启动,抓到的数据包就是非加密的。使用 show 命令查看当前版本是否默认开启 ssl 加密。如果 have_openssl,have_ssl 都是 “DISABLED”,那么说明已经关闭 ssl 加密,可以抓到报文的明文内容。
在报文中终于发现 Navicat 使用 load_file 的操作。它读取由系统变量 “datadir” 和一系列字符串组成的文件地址映射到 source 列。
这里的 .frm 后缀文件是 MySQL 的表结构定义文件。从这里也可以大概了解到 Navicat 使用 load_file 的原因了。可能就是在 edit view 时需要读取相关表定义。另外一个问题。系统变量 datadir 在 TIDB 和 MySQL 中代表不同含义。在 TiDB 中查询 datadir,返回的是 pd 的 2379 端口地址(比如:127.0.0.1:2379)。而 MySQL 中则是真正存储数据的目录地址。
三、问题解决
从第二点的内容来看,由于 TiDB 和 MySQL 文件组织形式的巨大差别。load_file 的结果肯定没办法满足预期。按照 MySQL 对 load_file 的定义去实现方法似乎已经没有意义。想要解决办法首先需要给 load_file 一个最简单的实现。起码保证不会因为缺少函数实现而报错。紧接着根据具体的报错信息,修改 load_file 函数,一步一步实现满足 Navicat 的需求。这是解决问题的一个思路,一步一步暴露问题层级,逐步分治。基于这种想法,首先给 TiDB 的 load_file 一个空实现,看看效果如何。
相关源码位于 expression/builtin_string.go 中。可以看到 load_file 的实现中抛出了一个 FunctionNotExists 的 error。
参考其他字符串函数的实现逻辑,照猫画虎给 load_file 一个空实现。
通过几十行模式化的代码,给了 load_file 一个空实现。也就是说再去调用 load_file 时,直接返回 null 结果。
保存这些修改,本地源码编译启动 TiDB。
首先测试单独使用 load_file 时的结果。结果满足预期。
接着测试当使用 Navicat 连接 TIDB 然后编辑视图时会发生什么样的问题。
令人惊喜的是,经过这样简单的修改,Navicat 不仅不会再报 load_file not found 的错误,并且编辑视图的功能已经能够正常使用。
效果如下图,右键编辑视图不报错并且正常显示 sql editor 栏的 sql 语句。
四、结语
这次通过抓包发现 TiDB 对 Navicat 兼容性问题并通过大胆假设,小心印证的策略。通过 load_file 的空实现兼容了 Navicat 编辑视图功能。由此也可以猜测,Navicat 连接到 TiDB 并编辑视图时,获取表结构的操作并不是必要的,或者也有可能 TIDB 通过其他的方式将表结构信息返回给 Navicat。从而导致 load_file 这一步操作即使是返回 null 也能够保障编辑视图功能的正常工作。
笔者经过手动测试,发现编辑视图功能确实已经修复。就给 load_file 方法的实现加上了单元测试代码。一并提交 PR (https://github.com/pingcap/tidb/pull/28216) 到 tidb 的 github 仓库。并在前不久已经合并到 master 分支。但 load_file 毕竟有原本的函数功能定义,目前给出的空实现仅是为了兼容 Navicat。后续既要满足 Navicat 编辑视图不报错,也要实现 load_file 的实际功能,需要更多开发者的不断摸索和实践。
版权声明: 本文为 InfoQ 作者【TiDB 社区干货传送门】的原创文章。
原文链接:【http://xie.infoq.cn/article/110708f1a3aea99dd6fa3d597】。文章转载请联系作者。
评论