尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

MySQL 解析器定制与执行计划深度分析:代码评审该盯住哪些细节

MySQL 解析器定制与执行计划深度分析:代码评审该盯住哪些细节 MySQL 解析器定制与执行计划深度分析代码评审该盯住哪些细节修改sql/sql_yacc.yy、sql/lex.h或sql/item_*.cc会影响 MySQL 的入口路径。内存生命周期、指针使用和递归深度的问题在并发场景下更容易暴露应在评审和测试阶段尽量捕获。下面整理定制 MySQL 解析器的 Code Review 检查项以及可放入 CI/CD 的验证步骤。1. 容易出问题的 Parsing 代码细节常规逻辑检查不够还应针对 C 特性和 MySQL 内存模型做专项检查。1.1THD::mem_root的生命周期陷阱MySQL 使用MEM_ROOTArena 内存池来管理单条 SQL 执行期间的内存。致命误区在定制 Parser 节点时开发者使用了标准的new/malloc分配 ASTItem对象却忘记在解析失败yyerror抛出异常时显式free。后果每次语法报错如客户端传入非法 SQL 格式都会泄露几 KB 内存几天内导致宿主机 RAM 被占满。正确规则必须统一使用new (thd-mem_root) Item_func_custom(...)在MEM_ROOT上分配随THD自动批量释放。1.2 Bison/Yacc 栈溢出与尾递归在sql_yacc.yy中新增语法规则时如果在处理列表如expr_list时写成了右递归Right Recursion/* 错误的右递归定义: 导致 Bison yacc 栈随元素增加呈 O(N) 线性暴涨 */ custom_list: element | element , custom_list ;当输入包含 10,000 个元素的 SQL 时Bison 语法解析栈yystack将发生溢出直接踩爆线程栈Thread Stack Segment Fault。必须强制要求写成左递归Left Recursion/* 正确的左递归定义: Bison 可以边解析边规约栈空间复杂度为 O(1) */ custom_list: element | custom_list , element ;2. MySQL 解析器代码审查清单 (Review Checklist)评审人员在 Review 涉及 MySQL 解析器的 PR 时必须逐项核对以下条目检查维度审查要点 (Checklist)违规代码特征风险等级内存分配必须使用MEM_ROOT禁止直接调用std::make_shared或mallocItem *item new Item_string(...)致命 (内存泄漏/Crash)递归结构Bison 规则必须严格为左递归rule: itemitem , rule符号表锁争用解析器辅助 Data-Structure 必须避免全局std::mutexstatic std::mutex parser_lock;高 (高并发卡死)字符集转换处理字符串 Token 时必须校验CHARSET_INFO指针非空直接读取cs-mbmaxlen无 NULL 判空中 (空指针解引用)NULL 语义处理定制函数Item::val_str()必须设置null_value标记忘记写null_value true; return nullptr;高 (计算逻辑错误/脏数据)3. 自动化工程质量门禁构建仅靠人工 Code Review 难以杜绝偶发的内存越界。必须在 CI 流水线中引入自动化门禁工具3.1 内存与线程诊断 (ASan TSan)在编译 CMake 参数中强制开启 AddressSanitizercmake .. -DWITH_ASANON -DWITH_TSANON -DCMAKE_BUILD_TYPEDebug确保任何未释放的MEM_ROOT节点或越界访问在跑单元测试mysql-test-run时能够精准抛出 Traceback 并阻断 PR 合并。3.2 模糊测试 (LLVM libFuzzer SQLsmith)构建专门针对定制 Parser 的 Fuzzing Target。传入随机生成的变异 SQL 字符串连续压测 12 小时。若发现任何触发SIGSEGV或SIGABRT的 InputCI 自动提取 Minimal Reproducible Example (MRE) 挂载至 PR 评论区。4. 代码示例C 解析器内存与防护以下 C 代码模拟了 MySQL 内核解析器节点的标准内存分配机制、递归安全保护以及 NULL 语义处理。#include iostream #include string #include vector #include cassert #include stdexcept // 模拟 MySQL MEM_ROOT 简易 Arena 内存池 class MemRoot { private: std::vectorchar* allocated_blocks; size_t total_allocated 0; public: void* Alloc(size_t size) { // 对齐到 8 字节 size_t aligned_size (size 7) ~7; char* block new char[aligned_size]; allocated_blocks.push_back(block); total_allocated aligned_size; return block; } void Clear() { for (char* ptr : allocated_blocks) { delete[] ptr; } allocated_blocks.clear(); total_allocated 0; } ~MemRoot() { Clear(); } size_t GetTotalAllocated() const { return total_allocated; } }; // placement new 重载确保只能在 MEM_ROOT 上分配 void* operator new(size_t size, MemRoot mem_root) { return mem_root.Alloc(size); } // 模拟 MySQL AST 基类 Item class Item { public: bool null_value false; virtual ~Item() default; virtual std::string ValStr() 0; }; // 定制语法树节点安全处理内存与 NULL 语义 class ItemCustomFunc : public Item { private: Item* arg1; Item* arg2; public: ItemCustomFunc(Item* a, Item* b) : arg1(a), arg2(b) {} std::string ValStr() override { assert(arg1 ! nullptr Code Review Line 45: arg1 不能为空); assert(arg2 ! nullptr Code Review Line 46: arg2 不能为空); std::string res1 arg1-ValStr(); if (arg1-null_value) { this-null_value true; return ; } std::string res2 arg2-ValStr(); if (arg2-null_value) { this-null_value true; return ; } this-null_value false; return res1 _ res2; } }; // 解析器辅助类校验递归深度与内存分配 class CustomParserReviewGuard { private: MemRoot mem_root; size_t current_depth 0; const size_t MAX_PARSER_DEPTH 100; public: explicit CustomParserReviewGuard(MemRoot root) : mem_root(root) {} Item* ParseCustomFunction(Item* left, Item* right) { // 深度检查 current_depth; if (current_depth MAX_PARSER_DEPTH) { throw std::runtime_error(ER_STACK_OVERFLOW: SQL 语法树深度超过安全门限 100); } // 使用 placement new 在 MEM_ROOT 上安全分配禁止裸 new ItemCustomFunc* func new (mem_root) ItemCustomFunc(left, right); current_depth--; return func; } }; // 模拟测试 class DummyItemString : public Item { private: std::string val; public: DummyItemString(std::string v) : val(std::move(v)) {} std::string ValStr() override { this-null_value false; return val; } }; int main() { MemRoot thd_mem_root; try { CustomParserReviewGuard guard(thd_mem_root); // 构造子节点 Item* str1 new (thd_mem_root) DummyItemString(Param1); Item* str2 new (thd_mem_root) DummyItemString(Param2); // 构建定制函数节点 Item* func_item guard.ParseCustomFunction(str1, str2); std::string result func_item-ValStr(); std::cout AST 执行结果: result | 内存池占用: thd_mem_root.GetTotalAllocated() bytes std::endl; } catch (const std::exception e) { std::cerr [拦截失败警报] e.what() std::endl; } // 单次 SQL 执行完毕后THD 统一清理零内存泄漏 thd_mem_root.Clear(); std::cout MemRoot 清理完毕当前占用: thd_mem_root.GetTotalAllocated() bytes std::endl; return 0; }5. 总结在针对 MySQL 解析器进行 Code Review 时必须贯彻“零裸指针分配”、“绝对左递归”与“严密 NULL 判空”三项准则。通过在流水线中集成 ASan 与 Fuzzing 自动化测试才能防患于未然避免因代码细节缺陷导致生产数据库崩溃。这里有个容易被忽略的前提解析器扩展必须沿用 MySQL 自身的对象生命周期和错误传播方式。评审时先查节点是谁释放、异常会不会跨过 C 接口、错误是否正确写回当前 THD。测试也不应只喂合法 SQL空参数、嵌套函数、截断字符串和重复释放路径都值得覆盖。这样发现的是具体边界而不是把“安全”写成一句笼统口号。新函数挂入语法树前还要核对权限检查落在哪一层。不能因为解析成功就默认执行阶段允许访问相关对象。若扩展要读取会话变量或影响执行计划应把默认值、作用域和审计方式写清。评审人拿一个权限不足的账号跑最小 SQL通常能比阅读大量分支更早发现漏检。这类用例应进入回归集避免以后改语法时被遗漏。
返回列表