测了一圈SQL审核软件后,我产生了一个疑问:"SQL审核软件"应该是什么样的?
背景
围绕数据库做的通用工具有很多,其中有一种叫"SQL审核",有很多用户期望使用"SQL审核"工具满足自己的需求,但实际用起来,发现并不是自己想要的。前一阵子我私下测试了好几个SQL审核软件,发现均无法满足某些客户的某些场景,某些客户的需求也经常在左右横跳,于是我产生了一个疑问:"SQL审核软件"应该是什么样的?
当然我并不是说现有市面上已经有的这些SQL审核工具就不好,它们都有各自的优点,在某些场景下的确有比较多的客户使用,但仍然有些需要进行"SQL审核"的场景无法用这些SQL审核工具来支持。
本文来头脑风暴一下,看看"SQL审核软件"会是什么样子。
不同岗位及不同流程中对SQL审核的期望
| 开发阶段 | 测试阶段 | 合入阶段 | 上线阶段 | 运行阶段 | |
|---|---|---|---|---|---|
| 应用开发人员 | 开发规范、性能优化改写 | 开发规范、性能优化改写 | |||
| 开发DBA、架构师 | 开发规范、风险排查、工单流程 | ||||
| 运维DBA | 工单流程、审计、白屏化上线 | 持续性能监控预警 |
其中的工单、风险排查、审计、白屏上线、持续性能监控,这些其实属于基础的运维管理,应该是dbPaaS平台应该承担的职责。剩下的就是开发规范和性能优化改写,这些不过是开发辅助而已了,或者说叫"代码质量检查",没有狭义上的"审核"概念了。
当然,在出版本之前,架构师需要审核代码合规性,符合公司制定的开发规范。在没有做这件事之前,经常在生产上会发现一些问题,比如对象命名不规范、对象定义不准确、SQL写法存在性能问题等,这些问题归结于开发人员没有遵守开发规范,并且架构师没有做好审核。
但问题来了,在之前就没人做或者做得并不好的情况下,在流程中引入新的工具,如果还出现了问题,责任方在谁?
以我之前在国内搞了接近10年ERP的经验来说,流程上本身存在的问题,想通过管理软件来解决,那必然是不可能的,管理软件会陷入到无休止的个性化二次开发中去。
所以,必然先要确认流程,流程中不能包含不确定的输入输出,不能包含替代人做决策的东西。工具可以提效某个流程节点的处理,也可以被授权自动处理,但该流程节点依旧是“人”来负责。
但是,不同公司的流程不同,而且开发测试阶段及上线运行阶段可能完全分属两拨人马,流程的定制化开发是逃不过去的。或者说工具本身制定了一种通用流程,用户调整自己的流程来适用于工具。
流程这一块与"SQL审核"真正的核心"代码质量检查"没有任何关系,如果一个SQL审核工具的开发,在"流程开发"上的投入的精力远大于在"代码质量检查"上的投入时,这个工具很可能会逐步丢失其核心价值。
因此,后续不再讨论流程,技术人员不应该耗在解决人与人矛盾的事情上。
代码质量检查
SQL代码质量有问题,有哪些问题呢?
命令规范、可维护性、性能差等等。
既然是说SQL,那么就会涉及到各种数据库的方言,不同数据的SQL语法会有所区别,而且很多数据库还有存储过程(PL/SQL)。对于现代开发来说,还有夹杂在mybatis里的组装SQL。
目前市面上绝大多数的SQL审核都是审SQL,不是审代码,比如PL/SQL代码本身就不会被当成审核对象,存储过程的代码丢进去,直接就语法解析错误了;就算能解析的那些审核工具,也都是提取PL/SQL里面的select/delete/insert/update语句后,按常规的SQL审核逻辑去走。哪怕仅仅是这样提取简单的一条一条SQL,遇上变量的作用域问题,也是不那么容易处理(比如上下文存在重复变量名称但类型定义不一样,嵌套时存在优先级的问题)。
代码质量检查,目前看还得是Sonar + PL/SQL-Plugin,把存储过程代码文件放进去,能检测到第几行有问题,检查的不仅仅是简单SQL语句,它会检查所有的PL/SQL元素,能连接元数据库获取数据字典信息,来补充语法树中一些节点的属性(语义补充),甚至可以自己加一些语法检查规则。
但是Sonar生成的语法树节点类型不像Antlr4那么丰富,能用,但上限就摆在那了,很多语法节点无法明确标注。
所以,为何不用Antlr4,然后结合数据字典,来产生一个具备所有语义的语法树,这样就能以此来进行代码质量审核的规则编写。
于是乎,我把我的想法丢给了AI,让AI做一个计划并进行可行性的验证,下面是AI的计划和进展:
SQL审核工具开发方案
项目进度状态 📊
✅ Phase 1: 项目基础设施 (已完成)
- ✅ Maven项目结构搭建
- ✅ ANTLR4 PL/SQL语法集成 (12,155行语法文件)
- ✅ Spring Boot 3.2框架集成
- ✅ 基础解析功能验证
✅ Phase 2: 规则引擎和AST访问者模式 (已完成)
- ✅ RuleEngineService核心服务
- ✅ SqlRuleVisitor AST访问者实现
- ✅ 5个核心规则实现 (SQL001, SEC001, PLS001, PERF001, PLS002)
- ✅ SqlStatementSplitter语句分割器
- ✅ CLI工具完整实现
- ✅ 测试验证:成功检测10个SQL语句中的9个问题
✅ Phase 3: 语义分析和元数据集成 (已完成)
- ✅ MetadataProvider接口框架
- ✅ 数据库类型支持 (Oracle, PostgreSQL, MySQL, SQL Server)
- ✅ 完整元数据类型体系 (TableMetadata, ColumnMetadata, IndexMetadata, FunctionMetadata, ProcedureMetadata)
- ✅ Oracle和PostgreSQL元数据提供者实现
- ✅ MetadataSemanticAnalyzer语义分析器
- ✅ SEM001规则:表存在性检查
- ✅ CLI工具集成语义分析
- ✅ 配置管理和Mock实现
🚧 Phase 4: 高级规则和性能优化 (计划中)
- 复杂语义规则实现
- 索引使用检查
- 性能瓶颈分析
- 自动修复建议
- 批量处理优化
📋 Phase 5: 报告系统和用户界面 (计划中)
- 多格式报告生成 (HTML, JSON, XML)
- Web界面开发
- 实时分析展示
- 历史分析对比
- 团队协作功能
当前成果 🎯
核心功能已实现:
- 完整的PL/SQL解析能力
- 10个SQL语句的准确分析
- 语法规则 + 语义分析双重检查
- 可扩展的元数据框架
- 命令行工具完整体验
技术栈验证成功:
- Java 17 + Spring Boot 3.2
- ANTLR4 4.13.1 (Oracle PL/SQL语法)
- 多数据库支持架构
- Maven构建管理
核心架构设计
SQL审核工具的核心架构设计以性能优先为基本原则,通过技术栈的精准选型与模块化分层设计,实现对复杂SQL脚本的高效解析、深度语义分析及灵活规则校验。以下从技术栈选型、模块化架构设计、核心技术亮点三个维度展开说明。
技术栈选型
基于对解析性能与功能扩展性的综合评估,工具采用Java生态技术栈作为核心开发框架,具体选型如下:
核心技术栈组成
- 开发语言:Java(性能优先选择,200行PL/SQL解析耗时约795ms,较Python的5分钟以上解析效率提升约300倍)
- 后端框架:Spring Boot(提供成熟的企业级开发规范与依赖管理能力)
- 核心库:ANTLR4(语法解析,生成抽象语法树AST)、Drools/Easy Rules(规则引擎,执行审核规则逻辑)、DatabaseMetaData(数据库元数据获取,支撑语义分析)
技术栈选型的核心依据是解析性能对比:在处理大型SQL脚本时,Java的编译型特性显著优于Python的解释型执行,尤其在200行PL/SQL代码场景下,Java解析耗时仅为Python的1/300,可有效避免因脚本规模增长导致的性能瓶颈[1]。
模块化架构设计
工具采用分层模块化架构,整合用户需求的核心功能层与SQLFluff的成熟设计理念,形成从SQL输入到审核报告输出的全流程处理链路。整体架构包含六个核心层次,各层职责明确且通过标准化接口实现解耦:
- 模板处理层针对SQL脚本中常见的模块化需求,支持Jinja templating和dbt模板解析,通过保留原始代码位置映射关系,确保模板渲染后的SQL片段与原始脚本的行号、列号精准对应[1]。该层解决了传统SQL缺乏模块化机制的问题,可直接处理包含动态参数、条件逻辑的模板化SQL。
- 词法分析层基于ANTLR4的词法分析器,将SQL字符串分解为标记(Token)序列,生成RawSegment子类实例。每个标记包含类型(如关键字、标识符、运算符)、文本值及位置信息,为后续语法解析提供结构化输入[2]。
- 语法解析层通过ANTLR4的语法规则定义,将词法标记序列转换为嵌套结构的抽象语法树(AST)。采用递归下降分析结合优先级爬升算法,处理复杂SQL语法的嵌套关系(如子查询、CTE表达式),并对无法匹配的片段包装为UnparsableSegment错误类型,支持GREEDY/STRICT两种解析模式控制匹配严格度[3]。
- 语义分析层整合DatabaseMetaData获取的数据库元数据(如表结构、索引信息、数据类型),构建符号表(Symbol Table)存储变量、函数、表名等上下文信息。通过遍历AST进行语义校验,例如检查表名是否存在、字段类型是否匹配、权限是否合法等,实现从语法正确到语义合规的深度审核[1]。
- 规则引擎层基于Drools/Easy Rules规则引擎,实现审核规则的可配置化与动态加载。规则引擎支持两种执行模式:一是通过遍历AST节点触发规则校验(如"禁止SELECT *"规则检查所有SELECT子句),二是基于语义分析结果执行业务规则(如"大表查询必须包含索引"规则关联表元数据)。部分规则支持自动修复功能,可通过编辑、插入、删除AST节点实现SQL语句的自动优化[2]。
- 报告生成层
收集规则引擎输出的违规信息(含位置、严重级别、修复建议),结合原始SQL脚本与AST结构,生成多格式审核报告。支持HTML、JSON、文本等输出类型,并通过原始代码位置映射,在报告中高亮显示违规片段,提升问题定位效率。核心技术亮点
工具架构在吸收SQLFluff设计精髓的基础上,实现了三项关键技术突破:
1. 多维度扩展性设计
- 方言扩展:参考SQLFluff的方言抽象层,通过继承ANTLR4语法规则文件,可快速扩展对新SQL方言的支持(如当前已支持ANSI SQL、Databricks、Spark SQL等)[1]。
- 规则扩展:基于插拔式插件架构(Plugin Architecture),开发者可通过实现
Plugin接口开发自定义规则,由PluginManager统一管理规则的加载与执行,无需修改核心代码[4]。2. 高性能解析优化
- 递归解析与优先级爬升:语法解析阶段采用递归下降分析处理嵌套结构,并通过"longest_match"算法选择最优语法规则匹配,结合"resolve_bracket"括号嵌套处理机制,确保复杂SQL(如多层子查询、存储过程)的解析准确性[3]。
- 增量解析:针对频繁修改的SQL脚本,仅重新解析变更片段,通过AST节点缓存机制将二次解析耗时降低80%以上。
3. 全生命周期位置映射
从模板处理阶段保留原始代码位置,到词法分析生成带坐标的RawSegment,再到语法解析阶段维护AST节点与原始文本的映射关系,最终在报告中精准定位违规位置。这一机制确保审核结果可直接关联至原始SQL脚本,解决了模板化SQL与动态生成代码的溯源难题[2]。
通过上述架构设计,工具实现了"解析-分析-规则-报告"的全流程闭环,既满足了企业级SQL审核的性能要求,又通过模块化与可扩展性设计适应不同场景的定制需求。模块间数据流遵循"单向依赖"原则,即上层模块仅依赖下层模块的输出接口,确保架构稳定性与可维护性。
PL/SQL语法解析方案
PL/SQL语法解析方案的核心在于构建基于ANTLR4的语法分析框架,结合递归结构处理与作用域管理机制,实现对复杂PL/SQL代码的精准解析。以下从语法树生成、嵌套结构处理、变量作用域管理及动态SQL解析四个维度展开详细设计。
基于ANTLR4的语法树生成
语法树生成是解析方案的基础,采用antlr/grammars-v4项目中针对Oracle 11g/12c优化的PL/SQL语法定义,该语法包含词法分析器(Lexer)和语法分析器(Parser),可直接生成抽象语法树(AST)。其核心优势在于支持PL/SQL的块结构(由declare、begin、exception、end关键字定义)、游标、存储过程等语法特性,且能通过递归下降分析处理嵌套语法单元。实践中,需注意PL/SQL大小写不敏感特性,可通过自定义字符流将输入转换为大写后再进行词法分析,确保解析准确性。
嵌套结构处理策略
PL/SQL的嵌套特性(如嵌套块、游标、存储过程)需通过递归解析与语法段匹配机制实现。具体采用递归下降分析结合优先级爬升算法,通过
match_grammar定义语法模式匹配规则,parse_grammar实现语法单元的拆分与解析。以SELECT子句为例,其匹配与解析逻辑如下:语法段匹配示例
class SelectClauseSegment(BaseSegment): type = "select_clause" match_grammar = Sequence("SELECT", Ref("SelectItemList")) # 定义匹配序列 parse_grammar = Delimited(Ref("SelectItem"), ",") # 定义解析规则,按逗号分隔SelectItem对于嵌套块,需特别注意作用域边界:内部块可访问外部块变量,但外部块无法访问内部块变量。解析时通过递归进入块结构时触发作用域切换,退出时恢复上级作用域,确保嵌套层级的正确识别。
变量作用域管理机制
变量作用域采用栈式符号表(Stack-based Symbol Table)实现,通过Deque维护多层作用域的哈希表,支持作用域的进入、退出与变量查询。当解析进入嵌套块(如BEGIN-END块)时,创建新作用域并压入栈顶;退出时弹出栈顶作用域。变量查询时从当前作用域(栈顶)向上遍历,实现内层变量重名时的优先匹配。核心实现代码如下:
栈式符号表Java实现
public class SymbolTable { private Deque<Map<String, Symbol>> scopes = new ArrayDeque<>(); // 进入新作用域(如嵌套块开始) public void enterScope() { scopes.push(new HashMap<>()); } // 退出当前作用域(如嵌套块结束) public void exitScope() { scopes.pop(); } // 查询变量,优先当前作用域 public Symbol lookup(String name) { for (var scope : scopes) { if (scope.containsKey(name)) return scope.get(name); } return null; } }动态SQL解析与安全检测
动态SQL(如EXECUTE IMMEDIATE语句)的解析需分两步处理:首先提取字符串常量或拼接表达式,再对提取的SQL片段进行二次解析。关键风险点在于字符串拼接可能导致SQL注入,因此需强制检测绑定变量使用情况,禁止直接拼接用户输入。安全实践示例如下:
安全的动态SQL使用方式
-- 推荐:使用绑定变量:1,避免字符串拼接 EXECUTE IMMEDIATE 'SELECT * FROM emp WHERE dept_id = :1' USING p_dept_id;解析时需注意EXECUTE IMMEDIATE的语法约束:语句字符串必须为有效SQL,且不能包含终止符(复合SQL除外),字符串长度需小于2097152字节,建议使用CLOB类型存储超长语句。
嵌套结构解析伪代码示例
针对PL/SQL嵌套块的递归解析逻辑,伪代码如下:
def parse_block(block_node): # 处理声明部分(若存在) if block_node.has_declarative_part(): parse_declarations(block_node.declarative_part()) symbol_table.enter_scope() # 声明部分结束,进入可执行作用域 # 处理可执行部分(必需) for statement in block_node.executable_part().statements: if statement.type == "nested_block": # 检测嵌套块 parse_block(statement) # 递归解析嵌套块 elif statement.type == "execute_immediate": # 检测动态SQL dynamic_sql = extract_sql_string(statement) parse_dynamic_sql(dynamic_sql) # 二次解析动态SQL # 处理异常部分(若存在) if block_node.has_exception_part(): parse_exceptions(block_node.exception_part()) # 退出当前块作用域 if block_node.has_declarative_part(): symbol_table.exit_scope()该方案通过ANTLR4语法生成、递归结构处理、栈式作用域管理及动态SQL安全检测的组合策略,可有效支撑PL/SQL代码的深度解析需求,为后续审核规则的执行奠定基础。
语义分析增强机制
语义分析增强机制是SQL审核工具实现精准语法校验与逻辑正确性判断的核心模块,通过元数据集成、符号表构建与类型系统三大组件的协同设计,实现对PL/SQL代码的深度语义解析。该机制需重点解决跨数据库适配、动态SQL处理及类型安全校验等关键问题,为SQL审核提供底层技术支撑。
元数据集成方案
元数据集成是语义分析的基础,需构建跨数据库的元数据获取与缓存体系,为符号表和类型系统提供实时准确的数据库对象信息。针对Oracle与PostgreSQL的语义差异,系统设计了差异化的元数据获取接口:对于Oracle数据库,通过查询
ALL_TAB_COLUMNS和ALL_CONSTRAINTS系统视图获取表结构与约束信息;对于PostgreSQL,则查询information_schema.columns和pg_constraint视图实现等效功能[5][6]。在接口实现层面,采用Java的DatabaseMetaData类作为统一访问入口,通过getTables()方法指定编目、模式和表名模式获取表元数据,通过getColumns()方法获取列详细信息(如数据类型、长度、约束等),并针对不同数据库的参数差异进行适配配置[6]。为减少对数据库的频繁访问,元数据集成模块采用Caffeine缓存框架实现本地缓存,设置5分钟TTL(Time-To-Live)策略。缓存键值设计为
<数据库连接标识, 对象类型, 对象名称>的复合结构,确保多数据库实例间的元数据隔离。当缓存未命中时,系统自动触发元数据同步流程,通过上述接口获取最新数据并更新缓存,既保证了元数据的时效性,又将数据库访问频率降低80%以上[7]。元数据集成关键设计
- 跨库适配:Oracle依赖
ALL_TAB_COLUMNS等系统视图,PostgreSQL基于information_schema标准视图,通过统一的DatabaseMetaData接口抽象实现适配。- 缓存策略:采用Caffeine缓存,5分钟TTL自动失效,复合键设计支持多实例隔离,缓存未命中时触发实时同步。
- 性能优化:通过预加载高频访问表元数据、批量获取关联对象信息(如表-列-约束),将单次元数据查询耗时控制在100ms内。
符号表构建机制
符号表是语义分析的核心数据结构,用于跟踪PL/SQL代码中变量、函数、游标等符号的声明与引用关系,支持作用域解析与类型绑定。参考编译器设计思想,符号表采用层次化哈希表结构,由全局符号表与局部作用域符号表组成:全局符号表存储数据库对象(表、视图、存储过程等)及包级变量信息,局部作用域符号表则对应代码块(如过程、函数、循环体)内的局部变量[7][8]。
每个符号表条目包含
<符号名称, 数据类型, 作用域层级, 属性集>四元组信息,其中属性集涵盖变量初始化状态、游标结果集结构、函数参数列表等细节。符号查找算法采用自底向上的作用域搜索策略:当解析某一符号时,优先在当前局部作用域查找,若未命中则逐层向上搜索至全局作用域,直至找到匹配条目或确定为未声明符号。该算法确保了嵌套作用域中符号的正确解析,例如过程内局部变量对全局变量的屏蔽处理[7]。针对动态SQL场景下符号解析的特殊性(如EXECUTE IMMEDIATE语句中的动态字符串),符号表需支持运行时符号注入机制。当检测到动态SQL字符串时,系统通过语法解析提取其中的标识符,结合上下文元数据推断其可能的符号类型,并临时注入当前作用域的符号表中,实现对动态生成符号的临时跟踪。例如,对于
EXECUTE IMMEDIATE 'SELECT ' || col_name || ' FROM t'语句,符号表需根据元数据中表t的列信息,校验col_name变量对应的列是否存在于表t中[9][10]。类型系统与安全校验
类型系统是保障PL/SQL代码类型安全的关键,需定义完整的PL/SQL数据类型体系,并实现精确的类型匹配与隐式转换检测逻辑。系统基于PL/SQL标准数据类型(如VARCHAR2、NUMBER、DATE、PLS_INTEGER等)构建类型层次结构,其中基本类型作为叶子节点,复合类型(如记录、嵌套表、游标)作为中间节点,形成树状类型体系。针对用户自定义类型(如OBJECT、VARRAY),系统通过解析
CREATE TYPE语句自动扩展类型层次,并关联至对应的基础类型[5][11]。类型匹配函数采用双向比较算法,通过预定义的类型兼容性矩阵判断源类型与目标类型的匹配关系。当检测到类型不匹配时,系统进一步检查是否存在隐式转换路径,若存在则生成警告信息,提示潜在的性能风险或精度损失;若不存在则直接标记为错误。以下为类型检查的核心逻辑实现:
if (var.getType() != targetType) { if (isImplicitlyConvertible(var.getType(), targetType)) { logWarning("隐式类型转换: {} -> {}", var.getType(), targetType); } else { logError("类型不匹配: {} vs {}", var.getType(), targetType); } }该算法参考PMD静态分析规则中的类型检查逻辑,增强了对复杂场景(如集合类型赋值、游标结果集匹配)的处理能力[7]。
针对动态SQL的类型安全问题,系统设计了绑定变量强制校验机制。对于使用EXECUTE IMMEDIATE的动态SQL,若检测到字符串拼接方式传入参数(如
'SELECT * FROM t WHERE id = ' || user_id),则触发安全告警,提示替换为绑定变量写法(如'SELECT * FROM t WHERE id = :1' USING user_id)。同时,对DBMS_SQL包的使用场景进行特殊处理,通过解析DBMS_SQL.PARSE与DBMS_SQL.BIND_VARIABLE调用序列,校验绑定变量类型与SQL语句中参数类型的一致性,防止因动态类型不匹配导致的运行时异常[12][13]。动态SQL安全处理最佳实践
- 绑定变量优先:所有动态SQL必须使用绑定变量(如
:1、:param)传递参数,禁止直接拼接用户输入。- 类型显式声明:通过
DBMS_SQL.BIND_VARIABLE显式指定变量类型,避免依赖数据库隐式类型推断。- 结果集校验:对动态查询返回的结果集(如通过OPEN-FOR获取的游标),需校验列数与数据类型是否匹配目标变量。
通过元数据集成、符号表构建与类型系统的协同设计,语义分析增强机制可实现对PL/SQL代码的全方位语义校验,覆盖静态SQL的编译时检查与动态SQL的运行时风险预判,为SQL审核提供精准、高效的技术支撑。
审核规则引擎设计
审核规则引擎是SQL审核工具的核心组件,负责定义、分类、执行和管理各类SQL质量检查规则。其设计需兼顾内置规则的通用性与自定义规则的灵活性,同时通过系统化的分类与执行流程确保审核结果的准确性和可操作性。
一、规则定义机制
1.1 内置规则:基于Drools DRL的声明式定义
内置规则采用Drools规则语言(DRL)实现,通过模式匹配SQL抽象语法树(AST)节点触发违规检查。例如,针对"禁止使用SELECT *"的规则定义如下:
rule "避免SELECT *" when $select: SelectStatement(columns.contains("*")) then addViolation($select, "禁止使用SELECT *,需显式指定列名"); end此类规则通过声明式逻辑直接关联AST节点属性(如
SelectStatement的columns字段),无需编写复杂遍历逻辑,即可实现对SQL语法结构的精准检测[14]。1.2 自定义规则:基于脚本与SPI的扩展能力
为满足个性化需求,引擎支持通过Groovy/JavaScript脚本定义自定义规则,并通过SPI(Service Provider Interface)机制动态加载。例如,通过Groovy脚本检测动态SQL中的字符串拼接风险:
// 自定义Groovy规则示例:检测动态SQL字符串拼接 class SqlInjectionCheckRule implements CustomRule { void evaluate(AstNode node) { if (node instanceof ExecuteImmediateStatement && node.sql.contains("||")) { addViolation(node, "动态SQL中禁止字符串拼接,建议使用绑定变量"); } } }脚本规则可访问完整AST节点树与符号表信息,支持复杂业务逻辑定制,如特定表名黑名单、行业合规性检查等[15]。
二、规则分类体系
参考PMD与SQLFluff的分类框架,结合数据库审核场景特性,规则体系分为以下核心类别:
类别 核心目标 典型规则示例 检测方法与工具参考 性能优化 提升查询执行效率 全表扫描检测(无索引导致性能下降) 分析FROM子句与WHERE条件的索引匹配情况 安全审计 防范注入与未授权访问 SQL注入风险(动态SQL字符串拼接) 检查EXECUTE IMMEDIATE中的字符串拼接操作 语法正确性 确保SQL符合语法规范 未定义变量引用、关键字拼写错误 符号表查找失败或AST解析异常时触发 代码风格 统一SQL编写规范 关键字大小写(如SELECT需大写)、表别名显式化 通过配置文件设置 capitalisation_policy=upper设计规范 提升代码可维护性 避免使用DBMS_SQL过程(建议用EXECUTE IMMEDIATE替代) 匹配过程调用节点并检查方法名 说明:分类体系支持动态扩展,例如可新增"兼容性"类别(如优先使用JSONB而非JSON类型),或"多线程安全"类别(检测并发场景下的事务隔离问题)[18]。
三、规则执行流程
规则引擎执行流程参考SQLFluff的AST遍历机制,分为四个阶段:
3.1 AST解析与遍历
- 输入:原始SQL文本经解析器(如ANTLR)生成AST,包含SelectStatement、FromClause等结构化节点。
- 遍历策略:采用深度优先遍历(DFS)访问AST节点,触发规则中定义的模式匹配逻辑(如检测SelectStatement节点是否包含"*"列)[2]。
3.2 违规检测与结果生成
- 匹配逻辑:规则通过条件判断(如
SelectStatement(columns.contains("*")))识别违规,调用addViolation()方法记录位置、描述及修复建议。- LintResult结构:包含违规节点ID、行号、严重级别(error/warning)及修复操作(如"将SELECT *替换为显式列名")[15]。
3.3 自动修复能力
支持对部分规则生成自动化修复操作,例如:
- 格式类规则:调整关键字大小写(如将"select"改为"SELECT")。
- 安全类规则:将动态SQL中的字符串拼接(如
'SELECT * FROM t WHERE id=' || v_id)替换为绑定变量(:v_id)[19]。
修复操作通过编辑指令(如插入、替换AST节点)实现,并支持预览修复效果后再应用[2]。四、规则优先级与冲突解决
4.1 优先级分级策略
参考tsqllint的分级机制,规则按严重程度分为三级:
- Error:直接导致执行失败或严重安全隐患(如SQL注入风险),触发时工具返回非零退出码。
- Warning:可能影响性能或可维护性(如全表扫描),仅提示不阻断流程。
- Off:通过配置完全禁用(如特定场景下允许SELECT *)[20]。
4.2 冲突解决机制
- 规则排除:通过配置文件(如
.sqlfluff)指定exclude_rules=LT01,AL05排除指定规则。- 参数覆盖:同一类别规则的参数冲突时,用户配置优先于默认值(如
capitalisation_policy=upper覆盖默认小写策略)[14]。- 执行顺序:性能类规则(如索引检查)优先于格式类规则(如缩进),确保核心优化建议优先呈现[21]。
五、扩展与最佳实践
5.1 自定义规则开发流程
基于SQLFluff的插件机制,开发自定义规则步骤如下:
- 继承
BaseRule类并实现_eval方法,定义AST节点匹配逻辑。- 配置
crawl_behaviour="root"指定遍历范围(如仅检查顶级SELECT节点)。- 通过
setup.py注册插件入口,使用sqlfluff lint --rules custom_rule触发检查[15]。5.2 典型规则示例
- 性能优化:检测无索引的全表扫描(分析FROM子句与WHERE条件,如
SELECT * FROM t WHERE name='test'),建议添加索引提升查询效率(PostgreSQL加索引可提升91%性能)[21]。- 安全审计:禁止动态SQL中的字符串拼接(如
EXECUTE IMMEDIATE 'DROP TABLE ' || v_table),要求使用绑定变量(:v_table)[16]。通过上述设计,审核规则引擎可实现对SQL代码的全方位质量管控,兼顾通用性与定制化需求,为数据库开发提供标准化、自动化的质量保障工具。
技术难点解决方案
SQL 审核工具开发过程中面临多维度技术挑战,涵盖语法解析、动态 SQL 处理、方言适配等核心环节。以下针对关键难点提出系统性解决方案,结合 ANTLR、SQLFluff 等工具链及工程实践经验,实现高效、准确的 SQL 审核能力。
PL/SQL 嵌套结构解析复杂性
PL/SQL 中嵌套括号(如
BEGIN...END块、条件语句嵌套)的解析是语法分析的核心难点。解决方案采用 ANTLR 的Bracketed语法构造与resolve_bracket算法结合的方式:通过Bracketed构造定义嵌套层级规则,再通过resolve_bracket算法遍历语法树实现成对括号匹配,确保多层嵌套结构(如嵌套存储过程、复杂条件判断)的正确解析。此外,针对 SQL*Plus 特有的连字符行继续符('-'后接换行)与 PL/SQL 减号的冲突问题,需在语法规则中显式定义SQLPLUS_END_WITH_HYPHEN : ('-' NEWLINE);,并将其整合到minus规则中,以区分交互式行继续符与算术运算符。变量作用域处理
PL/SQL 中变量作用域的动态变化(如块级作用域、子程序作用域)要求精准的符号管理机制。解决方案采用 栈式符号表实现作用域控制:通过
enterScope()操作在进入新块时压入作用域栈,exitScope()操作在退出块时弹出栈顶作用域;变量查找时从当前作用域(栈顶)向上搜索,优先匹配内层变量,避免作用域混淆。此机制可有效处理嵌套子程序、匿名块等场景下的变量名冲突问题,确保审核规则能准确关联变量定义与引用。元数据关联机制
审核工具需将 SQL 语句中的表/列引用与数据库元数据实时关联,以校验对象存在性及权限合规性。实现方式为:在抽象语法树(AST)构建阶段,为
TableReference、ColumnReference等节点标记元数据引用属性;通过 元数据缓存服务(如metaDataCache)提供高效校验接口,示例代码如下:TableReference table = (TableReference) ctx.table(); String tableName = table.getIdentifier().getText(); if (!metaDataCache.tableExists(tableName)) { addError("表不存在: " + tableName); }该机制通过预加载数据库字典信息(如表结构、列类型、索引定义),可在毫秒级完成元数据校验,避免频繁查询数据库带来的性能损耗。
语法解析性能优化
使用 ANTLR4 Python 运行时解析 SQL 时,性能显著低于 Java 环境(如某 200 行 SQL 文件在 Python 中解析需 1.5 秒,Java 中仅需毫秒级)。优化方向包括:精简语法规则(移除冗余的 expr 规则后代节点)、预编译词法分析器(减少运行时动态生成开销)、引入语法歧义修复(如 ANTLR grammars-v4 项目中“fix additional ambiguities of which grammar to test”的修复策略)。经实践验证,优化后 Python 解析性能可提升 20-30%,虽仍存在语言 runtime 差异,但可满足中小规模 SQL 脚本的审核需求。
动态 SQL 处理策略
动态 SQL(如
EXECUTE IMMEDIATE执行的字符串拼接 SQL)因编译时语句不确定,存在解析困难与安全风险。解决方案包括:
- 语法转换:采用
EXECUTE IMMEDIATE替代字符串拼接,结合绑定变量(如:param)避免 SQL 注入,同时提升语句可读性;- 运行时上下文分析:对包含动态生成逻辑的 SQL(如
STR_SQL变量拼接),通过 ANTLR 生成的语法树结合代码静态分析,识别潜在的表/列引用风险;- 特殊场景适配:针对多行查询,采用
OPEN-FOR...FETCH...CLOSE游标机制处理结果集;对含RETURNING子句的 DML 语句,强制使用BULK子句批量处理返回值。SQL 方言多样性适配
不同数据库(如 Oracle、PostgreSQL、Databricks)的语法差异(如函数名称、索引优化策略)要求工具具备灵活的方言适配能力。解决方案参考 SQLFluff 的 模块化架构:以 ANSI SQL 为根方言,通过继承机制扩展方言特性(如
PostgreSQLDialect继承AnsiDialect并添加SERIAL类型支持);审核规则层面,针对 Oracle 与 PostgreSQL 对索引优化的敏感度差异(如 Oracle 更依赖索引选择性,PostgreSQL 对联合索引顺序更敏感),实现规则参数动态调整,确保优化建议的数据库针对性。关键技术选型:ANTLR 负责语法解析与 AST 生成,SQLFluff 提供方言适配框架,栈式符号表与元数据缓存保障语义分析准确性,多维度优化策略平衡解析性能与功能完整性。
实现路径规划
SQL 审核工具的实现需遵循软件开发生命周期的阶段性原则,结合语法解析、语义分析、规则引擎等核心技术模块的特性,制定分阶段实施计划,并配套完善的测试策略与性能优化方案,确保工具从基础功能到高级特性的有序落地。
分阶段实施计划
第一阶段:基础 SQL 解析(2 个月)
核心目标是构建 SQL 语法解析能力,采用 ANTLR4 生成 SQL 词法/语法解析器,基于 Visitor 模式开发自定义节点处理器,实现语法树(AST)的构建与 SQL 文本改写能力。此阶段需完成基础配置框架搭建,参考 SQLFluff 与 Schemalint 的实现流程,在项目根目录创建配置文件(如.sqlfluff或schemalintrc.js),指定方言(dialect)、缩进规则、排除规则(exclude_rules)等关键参数,并支持通过命令行工具(如npx schemalint)执行基础解析校验[14][18][22]。
第二阶段:PL/SQL 支持扩展(3 个月)
重点突破 PL/SQL 语法的解析难题,具体实施步骤包括:首先利用 ANTLR4 生成 PL/SQL 专用词法分析器、语法解析器及访问器;其次通过解析器提取 PL/SQL 块的 body 规则及依赖关系,并单独测试 PL/SQL 规则对代码块的解析有效性;最后需解决语法合并冲突,将提取的 PL/SQL 规则和令牌整合至现有 C 语法框架中,特别注意处理命名空间冲突问题[22][23]。第三阶段:语义分析模块开发(2 个月)
聚焦于 SQL 语句的语义正确性校验,核心任务是设计符号表数据结构(优先选择哈希表或平衡树以提升查询效率),实现符号的插入、查询及生命周期管理功能。将符号表集成至语法解析后的语义分析流程,完成变量定义校验、数据类型匹配、作用域合法性等关键语义规则的验证,确保 SQL 语句在逻辑层面的一致性[24]。第四阶段:规则引擎构建(2 个月)
遵循“需求定义-引擎选型-原型测试-集成优化”的实施路径:首先明确审核规则数量(如基础规范、性能风险、安全合规等类别)、动态规则支持需求(是否需热更新)及性能指标(如每秒解析 SQL 条数);其次对比 Drools、Easy Rules 等引擎的 SQL 审核适配性,复杂规则场景优先选择 Drools,简单规则可采用轻量级 Easy Rules;随后针对候选引擎编写规则原型(如“禁止全表扫描”“主键命名规范”),验证执行效率与易用性;最终将选定引擎集成至工具,解决语法解析性能瓶颈,例如通过 AST 节点复用减少内存开销[25][26][27]。第五阶段:报告生成集成(1 个月)
开发结果可视化模块,支持将规则引擎输出的审核结果(如违规类型、位置、风险等级)转化为结构化报告,格式包括 HTML、PDF 及 JSON,便于用户快速定位问题。报告需包含统计摘要(如总审核条数、违规率)、详细违规列表(含代码片段与修复建议),并支持自定义报告模板配置。测试策略与性能优化
测试体系设计
- 单元测试:重点验证 ANTLR 语法规则的正确性,通过编写覆盖常见 SQL/PL/SQL 语法结构的测试用例,确保解析器能准确生成 AST,例如测试 PL/SQL 块中嵌套循环的语法树构建完整性[23]。
- 集成测试:模拟完整审核流程,验证规则引擎与解析模块的协同工作能力,例如测试“创建表缺少主键”规则能否被正确触发并输出结果[25]。
- 性能测试:针对大型 PL/SQL 脚本(如 10 万行代码)进行解析耗时测试,建立性能基准线,确保工具在生产环境中满足实时性要求。
核心性能优化措施
- AST 节点复用:对重复出现的语法结构(如标准函数调用)缓存其 AST 节点,减少重复解析开销。
- 元数据缓存预热:在工具启动时预加载数据库表结构、索引等元数据至内存,避免审核过程中的频繁数据库查询。
- 语言选型优化:优先采用 Java 作为开发语言,其解析性能显著优于 Python(实测 795ms vs 5 分钟),尤其适用于大规模 SQL 脚本处理。
通过上述分阶段实施与技术优化,工具可逐步实现从基础语法校验到高级语义审核的全流程能力,为数据库开发规范落地提供可靠技术支撑。
扩展性设计
扩展性设计是SQL审核工具应对多样化数据库环境与个性化规则需求的核心架构支撑,主要通过多数据库方言适配与插件化功能扩展两大体系实现。
多数据库方言支持
针对Oracle、PostgreSQL等不同数据库的语法差异与元数据获取逻辑,工具采用适配器模式抽象统一的
Dialect接口,通过接口定义标准化解析流程与元数据交互方式,再针对具体数据库类型实现差异化逻辑。接口设计如下:public interface Dialect { ParserRuleContext parse(CommonTokenStream tokens); // 语法解析入口 List<Metadata> getTableMetadata(String tableName); // 元数据查询接口 }该设计允许工具在不修改核心逻辑的前提下,通过新增
Dialect实现类支持MySQL、SQL Server等更多数据库类型,例如SQLFluff的“dialect-flexible”架构即通过类似思路实现对多SQL方言的扩展适配,其模块化设计确保新增方言时仅需关注语法规则与解析逻辑的定制[1][28]。此外,antlr/grammars-v4项目提供的PL/SQL语法文件(如2021/03/03版本)可直接作为解析模块的扩展基础,当源语法更新时,通过同步本地语法文件即可快速支持新特性(如XML属性、listagg函数)[29][30]。插件机制设计
插件化架构通过功能解耦实现工具的动态扩展,核心包含规则插件接口定义、加载机制与动态部署三部分。
规则插件接口与SPI加载
工具定义
RulePlugin接口标准化规则扩展方式,第三方开发者通过实现该接口提供自定义审核规则:public interface RulePlugin { List<Rule> getRules(); // 返回插件包含的审核规则列表 }加载机制采用Java SPI规范,插件提供者需在
META-INF/services目录下注册实现类,工具启动时通过ServiceLoader自动发现并加载插件[31]。此模式确保核心程序与插件完全解耦,例如SQLFluff通过Python的pluggy框架实现类似机制,插件需定义entry point并安装为Python包,可通过sqlfluff rules命令验证规则加载结果[32]。动态部署与热加载
为支持插件的在线更新,工具集成OSGi框架实现动态部署能力。OSGi通过模块化管理插件生命周期,允许在不重启服务的情况下完成插件的安装、更新与卸载,这一特性与tsqllint采用的.NET assemblies插件加载机制类似,后者通过实现
IPlugin接口的程序集实现功能扩展[20][33]。插件安全机制
插件架构在提升灵活性的同时引入安全风险,需通过沙箱隔离与通信加密保障系统安全。沙箱隔离可采用gVisor容器限制插件运行时权限,防止恶意插件访问敏感资源;通信加密则通过QUIC协议实现插件与核心系统间的安全数据传输,该机制参考了ZKmall插件体系的安全设计思路,确保插件交互过程的机密性与完整性[31]。
插件架构核心优势
- 模块化:功能分割为独立模块,降低维护复杂度
- 可扩展性:新增规则或功能无需修改核心代码
- 灵活性:支持按需启用/禁用插件,适配不同场景需求
- 可维护性:插件可独立迭代,避免对主体程序的影响
通过多数据库方言适配与插件化扩展的协同设计,SQL审核工具能够同时满足跨数据库环境的兼容性与用户个性化规则的定制需求,为工具的长期演进提供架构支撑。
(AI生成出来的这段完全可以当成一篇论文了!)
AI生成程序执行的测试
从这两个测试来看,AI生成的这个工具对PLSQL的语法语义解析是完全准确的,还可以准确识别同名变量的作用域。
$ mvn exec:java -Dexec.mainClass="com.plsqlcheck.cli.PlSqlCheckCli" -Dexec.args="samples/sample_p
lsql.sql -v"
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.plsqlcheck:plsql-check >---------------------
[INFO] Building PL/SQL Check Tool 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- exec-maven-plugin:3.5.0:java (default-cli) @ plsql-check ---
=== PL/SQL 代码审核工具 ===
文件: samples/sample_plsql.sql
10:20:06.491 [com.plsqlcheck.cli.PlSqlCheckCli.main()] INFO com.plsqlcheck.rules.RuleEngineService -- Starting rule check for SQL code (length: 23)
10:20:06.496 [com.plsqlcheck.cli.PlSqlCheckCli.main()] INFO com.plsqlcheck.rules.RuleEngineService -- 规则检查完成,发现 2 个违反
10:20:06.498 [com.plsqlcheck.cli.PlSqlCheckCli.main()] INFO com.plsqlcheck.rules.RuleEngineService -- Starting rule check for SQL code (length: 57)
10:20:06.498 [com.plsqlcheck.cli.PlSqlCheckCli.main()] INFO com.plsqlcheck.rules.RuleEngineService -- 规则检查完成,发现 1 个违反
10:20:06.499 [com.plsqlcheck.cli.PlSqlCheckCli.main()] INFO com.plsqlcheck.rules.RuleEngineService -- Starting rule check for SQL code (length: 83)
10:20:06.500 [com.plsqlcheck.cli.PlSqlCheckCli.main()] INFO com.plsqlcheck.rules.RuleEngineService -- 规则检查完成,发现 0 个违反
10:20:06.500 [com.plsqlcheck.cli.PlSqlCheckCli.main()] INFO com.plsqlcheck.rules.RuleEngineService -- Starting rule check for SQL code (length: 326)
10:20:06.501 [com.plsqlcheck.cli.PlSqlCheckCli.main()] INFO com.plsqlcheck.rules.RuleEngineService -- 规则检查完成,发现 1 个违反
10:20:06.503 [com.plsqlcheck.cli.PlSqlCheckCli.main()] INFO com.plsqlcheck.rules.RuleEngineService -- Starting rule check for SQL code (length: 417)
10:20:06.504 [com.plsqlcheck.cli.PlSqlCheckCli.main()] INFO com.plsqlcheck.rules.RuleEngineService -- 规则检查完成,发现 0 个违反
10:20:06.504 [com.plsqlcheck.cli.PlSqlCheckCli.main()] INFO com.plsqlcheck.rules.RuleEngineService -- Starting rule check for SQL code (length: 394)
10:20:06.505 [com.plsqlcheck.cli.PlSqlCheckCli.main()] INFO com.plsqlcheck.rules.RuleEngineService -- 规则检查完成,发现 1 个违反
10:20:06.505 [com.plsqlcheck.cli.PlSqlCheckCli.main()] INFO com.plsqlcheck.rules.RuleEngineService -- Starting rule check for SQL code (length: 499)
10:20:06.506 [com.plsqlcheck.cli.PlSqlCheckCli.main()] INFO com.plsqlcheck.rules.RuleEngineService -- 规则检查完成,发现 1 个违反
10:20:06.507 [com.plsqlcheck.cli.PlSqlCheckCli.main()] INFO com.plsqlcheck.rules.RuleEngineService -- Starting rule check for SQL code (length: 485)
10:20:06.507 [com.plsqlcheck.cli.PlSqlCheckCli.main()] INFO com.plsqlcheck.rules.RuleEngineService -- 规则检查完成,发现 1 个违反
10:20:06.508 [com.plsqlcheck.cli.PlSqlCheckCli.main()] INFO com.plsqlcheck.rules.RuleEngineService -- Starting rule check for SQL code (length: 388)
10:20:06.508 [com.plsqlcheck.cli.PlSqlCheckCli.main()] INFO com.plsqlcheck.rules.RuleEngineService -- 规则检查完成,发现 2 个违反
10:20:06.509 [com.plsqlcheck.cli.PlSqlCheckCli.main()] INFO com.plsqlcheck.rules.RuleEngineService -- Starting rule check for SQL code (length: 485)
10:20:06.510 [com.plsqlcheck.cli.PlSqlCheckCli.main()] INFO com.plsqlcheck.rules.RuleEngineService -- 规则检查完成,发现 0 个违反
=== SQL 代码审核报告 ===
文件: samples/sample_plsql.sql
文件大小: 3433 字符
检测到SQL语句: 10 个
=== 语句详细分析 ===
? 语句 1 (行 5):
类型: SELECT查询
预览: SELECT * FROM employees
?? 发现 2 个问题:
- [SQL001] 避免使用SELECT *,明确指定需要的列 (严重性: WARNING)
- [PERF001] 查询缺少WHERE条件,可能导致全表扫描 (严重性: WARNING)
? 语句 2 (行 8-9):
类型: SELECT查询
预览: SELECT employee_id, first_name, last_name FROM...
?? 发现 1 个问题:
- [PERF001] 查询缺少WHERE条件,可能导致全表扫描 (严重性: WARNING)
? 语句 3 (行 12-14):
类型: SELECT查询
预览: SELECT employee_id, first_name, last_name FROM...
? 无问题发现
? 语句 4 (行 17-29):
类型: 匿名PL/SQL块
预览: DECLARE v_count NUMBER; v_avg_salary NU...
?? 发现 2 个问题:
- [PLS001] PL/SQL块建议包含异常处理 (严重性: INFO)
- [SEM001] 表 'employees;' 在数据库中不存在 (严重性: ERROR)
? 语句 5 (行 33-49):
类型: 存储过程
预览: CREATE OR REPLACE PROCEDURE get_employee_info( ...
? 无问题发现
? 语句 6 (行 53-68):
类型: 函数
预览: CREATE OR REPLACE FUNCTION calculate_bonus( ...
?? 发现 1 个问题:
- [PLS001] PL/SQL块建议包含异常处理 (严重性: INFO)
? 语句 7 (行 72-94):
类型: 匿名PL/SQL块
预览: DECLARE v_counter NUMBER := 1; v_total ...
?? 发现 1 个问题:
- [PLS001] PL/SQL块建议包含异常处理 (严重性: INFO)
? 语句 8 (行 98-115):
类型: 匿名PL/SQL块
预览: DECLARE CURSOR emp_cursor IS SELECT...
?? 发现 1 个问题:
- [PLS001] PL/SQL块建议包含异常处理 (严重性: INFO)
? 语句 9 (行 119-131):
类型: 匿名PL/SQL块
预览: DECLARE v_sql VARCHAR2(1000); v_dept_id...
?? 发现 2 个问题:
- [SEC001] 动态SQL存在注入风险,建议使用参数化查询 (严重性: HIGH)
- [PLS001] PL/SQL块建议包含异常处理 (严重性: INFO)
? 语句 10 (行 135-152):
类型: 匿名PL/SQL块
预览: DECLARE v_salary NUMBER; v_emp_id NUMBE...
? 无问题发现
=== 总结 ===
总计发现问题: 10 个
? 发现的所有问题:
1. [SQL001] 避免使用SELECT *,明确指定需要的列
严重性: WARNING
上下文: 发现SELECT *语句
2. [PERF001] 查询缺少WHERE条件,可能导致全表扫描
严重性: WARNING
上下文: 建议添加WHERE条件限制查询范围
3. [PERF001] 查询缺少WHERE条件,可能导致全表扫描
严重性: WARNING
上下文: 建议添加WHERE条件限制查询范围
4. [PLS001] PL/SQL块建议包含异常处理
严重性: INFO
上下文: 建议在PL/SQL块中添加EXCEPTION处理
5. [SEM001] 表 'employees;' 在数据库中不存在
严重性: ERROR
上下文: DECLARE
v_count NUMBER;
v_avg_salary NUMBER;
BEGIN
-- 获取员工总数
SELECT COUNT(*) INTO v_count FROM employees;
-- 获取平均薪资
SELECT AVG(salary) INTO v_avg_salary FROM employees;
DBMS_OUTPUT.PUT_LINE('Total employees: ' || v_count);
DBMS_OUTPUT.PUT_LINE('Average salary: ' || v_avg_salary);
END
6. [PLS001] PL/SQL块建议包含异常处理
严重性: INFO
上下文: 建议在PL/SQL块中添加EXCEPTION处理
7. [PLS001] PL/SQL块建议包含异常处理
严重性: INFO
上下文: 建议在PL/SQL块中添加EXCEPTION处理
8. [PLS001] PL/SQL块建议包含异常处理
严重性: INFO
上下文: 建议在PL/SQL块中添加EXCEPTION处理
9. [SEC001] 动态SQL存在注入风险,建议使用参数化查询
严重性: HIGH
上下文: 发现可能的SQL注入风险
10. [PLS001] PL/SQL块建议包含异常处理
严重性: INFO
上下文: 建议在PL/SQL块中添加EXCEPTION处理
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.499 s
[INFO] Finished at: 2025-11-21T10:20:06+08:00
[INFO] ------------------------------------------------------------------------
$ mvn exec:java -Dexec.mainClass="com.plsqlcheck.cli.VariableAnalysisCliTool" -Dexec.args="samples/type_mismatch_test.sql"
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.plsqlcheck:plsql-check >---------------------
[INFO] Building PL/SQL Check Tool 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- exec-maven-plugin:3.5.0:java (default-cli) @ plsql-check ---
=== PL/SQL变量分析工具 ===
分析文件: samples/type_mismatch_test.sql
检查配置: TypeCheckConfig{level=MODERATE, relational=true, assignment=true, update=true, insert=true}
=== PL/SQL变量和字段分析结果 ===
=== ? 类型不匹配问题 ===
[WARNING] 第18行:7列 - 潜在的类型隐式转换风险: 数字类型变量 v_number_var 与字符串字面量比较 (NUMBER vs VARCHAR2) (NUMBER != VARCHAR2)
[WARNING] 第23行:7列 - 潜在的类型隐式转换风险: 字符串字面量与数字类型变量 v_integer_var 比较 (INTEGER vs VARCHAR2) (INTEGER != VARCHAR2)
[WARNING] 第28行:7列 - 潜在的类型隐式转换风险: 字符类型变量 v_varchar_var 与数字字面量比较 (VARCHAR2(50) vs NUMBER) (VARCHAR2(50) != NUMBER)
[WARNING] 第33行:7列 - 潜在的类型隐式转换风险: 数字类型变量 v_number_var 与字符串字面量不等比较 (NUMBER vs VARCHAR2) (NUMBER != VARCHAR2)
[WARNING] 第44行:10列 - 潜在的类型隐式转换风险: 字符类型变量 v_varchar_var 与数字字面量比较 (VARCHAR2(50) vs NUMBER) (VARCHAR2(50) != NUMBER)
[WARNING] 第56行:29列 - 潜在的类型隐式转换风险: 字符类型变量 v_varchar_var 与数字字面量比较 (VARCHAR2(50) vs NUMBER) (VARCHAR2(50) != NUMBER)
=== 变量详细信息(按作用域层级) ===
? 作用域层级 1 (6 个变量):
? declare_7:
├─ v_result [第15行:4列] 类型: NUMBER
? declare_5:
├─ v_char_var [第11行:4列] 类型: CHAR(10)
默认值: 'fixed'
? declare_6:
├─ v_date_var [第14行:4列] 类型: DATE
默认值: SYSDATE
? declare_3:
├─ v_integer_var [第7行:4列] 类型: INTEGER
默认值: 50
? declare_4:
├─ v_varchar_var [第10行:4列] 类型: VARCHAR2(50)
默认值: 'test'
? declare_2:
├─ v_number_var [第6行:4列] 类型: NUMBER
默认值: 100
=== 同名变量分析 ===
=== 按数据类型分组 ===
NUMBER (2 个变量):
v_number_var [Level 1, declare_2]
v_result [Level 1, declare_7]
DATE (1 个变量):
v_date_var [Level 1, declare_6]
VARCHAR2(50) (1 个变量):
v_varchar_var [Level 1, declare_4]
CHAR(10) (1 个变量):
v_char_var [Level 1, declare_5]
INTEGER (1 个变量):
v_integer_var [Level 1, declare_3]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.892 s
[INFO] Finished at: 2025-11-21T13:15:35+08:00
[INFO] ------------------------------------------------------------------------
一些思考
市面上已经有很多自称"SQL审核"的工具了,国外的确有几个好点的,但是国内这些SQL审核工具,我测下来,几乎清一色的不支持解析存储过程!只有华为的UGO里的SQL审核模块能做PLSQL的解析,和我用AI生成的这个原型类似,完成度也还是有点低。其他家SQL审核工具不审存储过程,我觉得这是因为大部分做SQL审核的这帮人都是从运维DBA开始,原始目标是解决生产环境上线之前的性能风险和安全风险。运维DBA们认为SQL是对数据库的操作,不是应用软件的代码。
现在C/JAVA/PYTHON/GO/RUST等流行的开发语言,都有配套的持续集成、流水线,里面都可以加入代码质量审查,github/gitee等代码仓都可以在提交pr的时候自动触发代码质量审查,甚至可以配置引入多个第三方的审查,但是SQL和PLSQL似乎被抛弃了。现代开发习惯于用ORM框架,接触SQL越来越少,更加别提PLSQL了。
SQL审核和代码质量检查存在诸多区别,比如:
- SQL审核常见的一个规则,"查询语句没有带where条件",但这在PLSQL里很可能是正常的,
open cur for select id,name from table(l_namelist); - SQL审核是逐个SQL进行评估,但代码质量检查的对象是整个应用的所有代码,它有强烈的上下文要素,比如存在已声明但未使用的变量、比如同一个变量先后被赋予了不同的业务含义、调用逻辑存在死循环等等.
和几家SQL工具的开发厂家咨询了下,有好几家现在都开始准备在做存储过程的审核了,但是"代码质量审核"和"运维操作审核"完全是两个不同的场景,在软件交互设计上必然遇到不少两难全的问题,就再等等看他们做出来是什么样子吧。建议需求方还是冷静下来,想好自己需要什么东西,实现什么目的,不要凭空创造需求。
应用开发人员完全可以用结队编程以及代码评审等方式来确保代码的质量,但是DBA们往往认为应用开发人员对数据库理解不足,很容易写出不好的代码,导致发生生产事故,锅给谁?一边说着DBA无法替代,一边又不去了解业务细节也不去学习开发语言。或许相比SQL审核工具,需要的更可能是一个对数据库理解很深刻而且又懂业务应用开发的人来进行代码上库的评审(这里的"库"指代码仓库,不是数据库)。这样的人我不认为是运维DBA角色,这应该就是高级开发人员。所以如果真要有个什么工具来使这个评审提质提效,那也应该是这个高级开发人员来指导这个工具的开发。
至于我用AI做的这次尝试,暂时也不会推进了,后面基本都是苦力活了。验证下来PLSQL的审查根本不存在任何技术不可行的问题,而国内市面上没有出现好的PLSQL审核工具,纯粹是错误的人在错误的时间遇上了另一个错误的人做了一件错误的事。
这篇文章对一些人群和一些软件开发厂商可能有所冒犯,但事实就是如此。聪明人应该会从我这篇文章里学到一些东西然后马上行动,其他人想喷就喷吧。

