一个jsqlparse+git做的小工具帮我节省时间摸鱼
背景
前些时间做了个小工具解决了团队内数据库脚本检验&多测试环境自动执行的问题,感觉挺有意思,在这跟大家分享一下。
工具诞生之前的流程是这样:
1.开发人员先在开发环境编写脚本&执行;
2.执行没问题之后记录到代码目录下的upgrade目录;
3.提测时手动将upgrade目录下的脚本文件在测试库执行。
大概长这样
这套流程在我之前就有了,刚进来的时候感觉有点low,毕竟老东家解决这类问题是通过一款自研的数据库自动化运维平台-iDB,其诞生的目的是“解决绝大部分重复、复杂的数据库运维工作 ,满足业务对数据库信息查询和快速变更需求,借此提升研发效率,保证数据库操作符合审计要求,有可追溯的变更和审核日志”,内心一度排斥过一段时间,后来转念一想这套手工机制对于目前的团队规模来说是够用,况且没有配备dba,搞一套iDB上来谁审核谁呢?想到这,心里自然也就释怀了,不用过于追求太时尚的工具、技术,够用就可以了。
问题出现
伴随着近两年业务快速发展,团队也迎来了扩编,往往在这种时候就容易出一些低级故障,俗话说越忙越乱。
遇到过哪些问题呢?
0.代码提测了,但是脚本忘记执行了,测试走流程的时候发现有报错然后反馈给开发处理,耽误进度;
1.刷数据未加where条件,导致测试环境崩溃,有一次还波及了线上,幸好只是一张配置表,从其他私有化环境快速同步一份即可,但也是心惊肉跳,要是业务表可能团队要团灭了;
2.一些高危sql,比如drop table if exists,原意是想顺畅的建一张新表,但是谁能保证同样的语句不会再出现?
3.一些高级语法导致部分私有化环境不兼容,一般情况下开发执行一些数据库操作都是直接通过navicat等工具在开发库执行,然后再把工具生成的脚本记录下来,但是也有当时没整理,发版时再整理的情况,这时只能手写sql了,不排除会写一些高级的语法,导致发版时部分环境失败的情况,因为数据库版本有差异(有些客户分配的库,统一版本比较难);
人肉解决
最开始研发经理是专门安排了一个测试同学去处理这事,他需要定时做以下事项:
1.检查是否有新脚本提交,如果有就继续后续流程;
2.检查是否为高危脚本,如果高危就线下告知相关的开发整改,否则就继续后续流程;
3.在navicat等工具中执行,如果执行失败了就线下告知相关的开发处理。
起初几天确实是解决了前面提到的一些问题,但是人毕竟不是机器,会忘记、会疲倦、会烦躁、会走神,两周以后“人肉”方案又出现一些新问题:
1.忙的时候会忘;
2.频率不好把握,几小时一次太慢了,几分钟一次人会崩溃;
3.这个活太low了,临时干几天还行,长期没人愿意干;
机器解决
机器不知疲倦、一丝不苟、戒骄戒躁,最适合干这类重复性而且枯燥的活,鉴于此我利用半天时间构思&开发了一个小工具用来解决这一问题,解救了那个悲惨的测试同学。
这个工具需要具备以下特点:
1.定时拉取代码判断脚本文件是否有变化;
2.如果脚本文件变化了解析脚本看是否有语法错误,如果有语法错误发送邮件给提交人;
3.如果没有语法错误判断是否有高危语句,如果有高危发邮件给提交人和研发组长&经理们;
4.一切正常,开始执行sql语句,执行结果需要发邮件给相关人员,需要避免重复执行;
接下来一步步看如何解决上面的问题:
1.定时拉取代码
这个比较简单,因为是运行在我的开发机器上,定时使用Runtime执行git pull即可。
- Process process = Runtime.getRuntime().exec("git pull ",null,new File(代码目录));
2.判断脚本文件是否发生变化
记录脚本文件的md5,拉取代码以后计算md5是否发生变化。
3.语法解析
利用jsqlparser工具将脚本文件内容解析为Statements对象,代表一组解析之后的sql语句对象,如果有语法错误jsqlparser会抛出异常,异常信息中包含具体的行号和错误信息,以下面这组sql语句为例:
- String sql = "ALTER TABLE `wf_position` ADD COLUMN `c1` VARCHAR (10);" +
- "ALTER TABLE `wf_position` ADD COLUNM `c2` VARCHAR (10)";
- CCJSqlParserUtil.parseStatements(sql);
解析的时候会抛出如下异常,很明显是因为COLUNM写错了
- net.sf.jsqlparser.JSQLParserException: Encountered unexpected token: "`c2`" <S_QUOTED_IDENTIFIER>
- at line 1, column 93.
- Was expecting:
- "COMMENT"
- at net.sf.jsqlparser.parser.CCJSqlParserUtil.parseStatements(CCJSqlParserUtil.java:188)
至此我们已经做完了语法解析,但是怎么根据抛出的语法错误找到对应的提交人呢?这里分两步完成:
3.1正则匹配出异常堆栈中的line
- Pattern pattern = Pattern.compile("line (\\d+), column (\\d+)");
- try{
- CCJSqlParserUtil.parseStatements(sql);
- }catch(exception){
- String message = exception.getMessage();
- Matcher m = pattern.matcher(message);
- int line = -1;
- int column = -1;
- while(m.find()){
- int groupCount = m.groupCount();
- if(groupCount > 0){
- line = Integer.parseInt(m.group(1));
- column = Integer.parseInt(m.group(2));
- break;
- }
- }
- }
3.2前一步的line可以对应到脚本文件中的行,利用git blame命令可以获得对应行的提交记录,里面包含提交者的姓名和邮箱
- String blameParams = scriptFile.getName()+" -L "+lineNum+","+lineNum;
- Process process = Runtime.getRuntime().exec("git blame "+blameParams,null,new File(scriptFile所在目录));
输出格式如下,红框所示区域就是提交者的邮箱(组内约定git user.name必须携带邮箱,所以这里能拿到)
接下来就各种的截取,最终提取邮箱,比较简单,这里就不啰嗦了。
4.高危判断
遍历所有的Statement对象,目前主要识别三类:
1.drop table
2.update不带where条件
3.delete不带where条件
- Statements statements = CCJSqlParserUtil.parseStatements(sql);
- List<RiskScript> riskScripts = new ArrayList<>()
- for(Statement statement : statements.getStatements()){
- RiskScript riskScript = new RiskScript();
- //drop table
- if(statement instanceof Drop
- && (((Drop) statement).getType().equals("table")
- || ((Drop) statement).getType().equals("TABLE"))){
- riskScript.setErrorMsg("drop table高危,放弃自动执行,请确认,如有需要请手动执行");
- riskScript.setSql(statement.toString());
- this.riskScripts.add(riskScript);
- continue;
- }
- //update不带where条件
- if(statement instanceof Update
- && ((Update) statement).getWhere() == null){
- riskScript.setErrorMsg("update 不带where条件,放弃自动执行,请确认,如有需要请手动执行");
- riskScript.setSql(statement.toString());
- this.riskScripts.add(riskScript);
- continue;
- }
- //delete不带where条件
- if(statement instanceof Delete &&
- ((Delete) statement).getWhere() == null){
- riskScript.setErrorMsg("delete 不带where条件,放弃自动执行,请确认,如有需要请手动执行");
- riskScript.setSql(statement.toString());
- this.riskScripts.add(riskScript);
- continue;
- }
- }
5.避免重复执行
这个比较简单,每次执行完以后记录下每条sql的执行历史,执行前判断。
效果展示
总结
是不是应该引入一个高大上的数据库自动化运维平台呢?我的判断是暂时不需要,究其原因我认为有以下几点:
1.虽说没有专人审核那么精细,但依赖工具把一些高危的sql已经排除在外,已然是够用了,想想老东家为什么需要dba严格审核是因为toC的数据量较大,字段类型、索引等对性能的影响不容小觑,而目前toB的业务,数据量不会特别大,字段类型、索引等因素对性能的影响姑且可以忽略,起码现阶段差别不大;
2.每个迭代产生的脚本变更较多,如果引入太繁琐的流程,对开发效率是一种制约,不求设计出精妙的表结构,只愿你不要写出“团灭”的脚本;
推荐阅读
https://jsqlparser.sourceforge.net/home.php
https://www.w3cschool.cn/doc_git/git-git-blame.html
https://www.cnblogs.com/zhengyun_ustc/p/idb.html
小区里随手一拍
一个jsqlparse+git做的小工具帮我节省时间摸鱼的更多相关文章
- VC++:制作一个控件注册的小工具
在平时的工作中,时常需要注册与反注册ActiveX控件,有时需要判断控件是否已经注册. 所以通过查找资料编写了一个控件注册的小工具,欢迎学习交流,不当之处请多多交流. 先直接上图: 主要代码: ...
- 开源一个IE下获取XPath小工具,支持32/64位
背景是曾经友情支持了测试组一小段时间,发现他们使用selenium做页面的自动化测试,需要用到XPath,但IE下没有获取XPath的工具,只能在Firefox和chrome下获取,步骤还比较麻烦.而 ...
- 想用Electron做个小工具?这个或许是终极版
故事背景 之前在网上有看到很多小伙伴基于 electron 实现了非常多好用的桌面端工具,比如图床管理工具 PicGo,就专门做图床工具.也有一些其他的类似的小工具,比如 saladict-deskt ...
- 初学Python-搞了一个linux用户登录监测小工具
这几天突发奇想,想学习一下Python.看了点基础,觉得有点枯燥,所以想搞点什么.想了想,就随便弄个检测Linux用户登录的小工具吧~ 首先,明确一下功能: 1.能够捕获 linux 用户登录的信息. ...
- 帮哥们做的一个整理文档的小工具(C++ string的标准函数还是很给力的,代码在最下)
其实把程序用到生活中,真的能节约不少时间!程序的力量是无穷滴! 哥们的毕业设计是要做法律文书匹配之类的东东,有一步是要抽取所有的法律法规名称,而刚好我们要处理的文件中,法规的名称之前都有个‘.‘,所以 ...
- 一个小工具帮你搞定实时监控Nginx服务器
Linux运维工程师的首要职责就是保证业务7 x 24小时稳定的运行,监控Web服务器对于查看网站上发生的情况至关重要.关注最多的便是日志变动,查看实时日志文件变动大家第一反应应该是'tail -f ...
- 使用node.js开发一个生成逐帧动画小工具
在实际工作中我们已经下下来不下于一万个npm包了,像我们熟悉的 vue-cli,react-native-cli 等,只需要输入简单的命令 vue init webpack project,即可快速帮 ...
- 发布一个关于SharePoint的管理小工具
源码地址: https://github.com/GavinHacker/SiteCollectionManager 这是一个C#可执行程序,用于添加,删除,备份,还原SharePoint站点,可以 ...
- Python学习之旅:用Python制作一个打字训练小工具
一.写在前面 说道程序员,你会想到什么呢?有人认为程序员象征着高薪,有人认为程序员都是死肥宅,还有人想到的则是996和 ICU. 别人眼中的程序员:飞快的敲击键盘.酷炫的切换屏幕.各种看不懂的字符代码 ...
- 快速入门PaddleOCR,并试用其开发一个搜题小工具
介绍 PaddleOCR 是一个基于百度飞桨的OCR工具库,包含总模型仅8.6M的超轻量级中文OCR,单模型支持中英文数字组合识别.竖排文本识别.长文本识别.同时支持多种文本检测.文本识别的训练算法. ...
随机推荐
- KingbaseES 与 Oracle 用户口令管理与资源管理
一.概述 KingbaseES可以对用户口令与用户占用资源进行必要的管理.其管理方式,在这里与Oracle数据库进行参考比较. KingbaseES 使用扩展插件建立的系统参数,这组参数可以对数据库资 ...
- mac_VMWare安装总结
MacOS 安装VmWare 总结 如果之前安装过virtualBox,virtualBox的内核扩展会影响到VmWare的使用 *比如会导致VMWare虽然可以安装,却无法创建虚拟机 这是需要执行以 ...
- Ros入门21讲
一.ROS是什么? ROS=通信机制+开发工具+应用功能+生态系统 目的:提高机器人研发中的软件复用率. 1.ROS中的通信机制 松耦合分布式通信: 注意:什么是耦合.紧耦合.松耦合? 1.1 耦合 ...
- 《Java基础——循环语句》
Java基础--循环语句 1. while语句: 规则: 1. 首先计算表达式的值. 2. 若表达式为真,则执行循环语法,直至表达式为假,循环结束. 格式: while(表达式) 语句 ...
- Kratos漫游指南 1 - 概览
您好,地球人,欢迎来到Kratos漫游指南. 对于刚开始研究Kratos框架的开发者来说,目前的文档有些零散,这与我们的模块化设计有一些关系,不过Don't panic,从这篇文章开始,我将试图打破这 ...
- es分片数相关知识
分片数量 总分片数=主分片数 *(副分片数+1) 如下创建索引配置表示,总分片数=1 *(1+4),表示总共5个分片. "settings": { "number_of_ ...
- 21. Fluentd输出插件:rewrite_tag_filter用法详解
我们在做日志处理时,往往会从多个源服务器收集日志,然后在一个(或一组)中心服务器做日志聚合分析. 源服务器上的日志可能属于同一应用类型,也可能属于不同应用类型.我们可能需要在聚合服务器上对这些不同类型 ...
- PAT乙级 1024 科学计数法
思路 1.尝试失败:一开始想打算把结果直接存在一个字符串中,后来发现当指数大于0的时候还需要分别考虑两种情况,工程量巨大,尝试失败,于是借鉴了其他大佬思路,写出了ac代码 2.ac思路:首先取指数的绝 ...
- ETL工具Datax、sqoop、kettle 的区别
一.Sqoop主要特点: 1.可以将关系型数据库中的数据导入到hdfs,hive,hbase等hadoop组件中,也可以将hadoop组件中的数据导入到关系型数据库中: 2.sqoop在导入导出数据时 ...
- ArrayList LinkedList Vector之间的区别
List主要有ArrayList,LinkedList和vector三种实现.这三种都实现了List接口,使用方式也很相似,主要区别在于其实现方式的不同! 这三种数据结构中,ArrayList和Vec ...