SQL 审核系统体验

   为了方便高效的完成SQL审核任务,我尝试了一次淘宝开源的SQLautoReview。

项目地址: https://github.com/taobao/sqlautoreview
简单概括下其功能:
1、 解析sqlmapfile,利用dom4j从xml文件解析SQL语句,并插入数据库中
2、 分析SQL语句,创建该语句索引脚本
3、 对新的索引与“线上索引”进行合并,生成最终更新脚本
该工具的难点:
1、 对SQL语句的解析,尤其很复杂的sql
2、 SQL语句的提取,基本上规律性是不是很大,索引的建立规则,索引的合并这些是值得大家关注的
整体结构图:

建立索引规则:

1、 如果where等值查询字段中,是primary key的第一个字段,使用主键索引

2、 如果where等值查询字段中,是unique key的第一个字段,使用唯一键索引

3、如果前两个条件不满足,则需要对所有条件字段分成三类,一类是等值操作字段,第二类是非等值操作字段,第三类是排序字段.对于前两类字段,会在这两类columns分别进行Cardinality排序,Cardinality越大,越有可能排在索引字段顺序的最前面. 索引字段的组合顺序( 等值条件操作字段, 排序操作字段, 非等值条件操作字段)
4、 如果SQL含有排序字段,会对排序字段的类型,及长度进行检查,如果排序字段中有一个column的type是varchar类型,并且定义的长度超过200,会出现一个Warnning
5、 所生成的创建索引的脚本是最优的.但局部最优,并不代表着全局最优.
合并索引的规则:

1、所有新建索引进行最简单的去重

2、新建索引的索引字段是否是已存在的索引字段的子集,并且索引字段顺序相同.比如一个新建的索引为ind_test_user(user,status),而原来表上存在一个索引ind_test_user(user,status,type),那么这个新建的索引就不需要建了,直接重用原来的索引

3、新建索引的内部也进行2的方式合并

4、以新建索引为标准,如果老的索引是新建索引的子集,老的索引会删除,然后创建一个新的索引

存在缺陷:
1、 对于sqlmapfile的解析仍可能出错,精准度不够
2、 只支持MySQL
3、 对于SQL类型,不支持子查询,以及外连接。
这套开源系统对于采用Java程序非常合适。那对于非Java应用呢?经过@丹臣 大神的指导,对于非Java应用。只需跳过第一步即可。
大致简单介绍下:

自己的环境: linux, jdk1.6,配置java_home, path 变量。

解压程序包,创建log地址,修改配置变量。(具体请参考文档)
        productdb.xml 主要指示“线上”数据库的连接:
如:
<?xml version="1.0" encoding="UTF-8"?>
<database>
     <ip>127.0.0.1</ip> <port>3306</port>
     <dbname>dragon</dbname>    <user>root</user>    <password>xxxxxxx</password>
</database>
sqlmapfile.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<sqlmapfile>
<file_id>100</file_id><file_name>/opt/abc.xml</file_name>
</sqlmapfile>

(对于Java应用这个就如何填好,对于非Java应用。这个可以随便写,但是file_id必须和数据库中xmltosql 中的sqlmapfile_id 相对应)

sqlreviewdb.xml 文件指示操作结果
<?xml version="1.0" encoding="UTF-8"?>
<database>
 <ip>127.0.0.1</ip>
 <port>3306</port>
 <dbname>sqlreviewdb</dbname>
 <user>root</user>
 <password>xxxxx</password>
</database>
在sqlreviewdb中创建的表:
  CREATE TABLE `xmltosql` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `sqlmap_file_id` int(11) NOT NULL,
 `java_class_id` varchar(200) NOT NULL,
 `sql_xml` varchar(4000) NOT NULL,
 `sql_comment` varchar(200) DEFAULT NULL,
 `real_sql` varchar(4000) NOT NULL,
 `real_sql_hash` varchar(32) DEFAULT NULL,
 `table_name` varchar(100) DEFAULT NULL,
 `status` int(11) DEFAULT NULL,
 `auto_review_err` varchar(200) DEFAULT NULL,
 `auto_review_tip` varchar(200) DEFAULT NULL,
 `auto_review_time` datetime DEFAULT NULL,
 `sql_auto_index` varchar(200) DEFAULT NULL,
 `dba_review_time` datetime DEFAULT NULL,
 `sql_dba_index` varchar(200) DEFAULT NULL,
 `dba_advice` varchar(200) DEFAULT NULL,
 `gmt_create` datetime NOT NULL,
 `gmt_modified` datetime NOT NULL,
 PRIMARY KEY (`id`),
 KEY `idx_java_class_id` (`java_class_id`)
) ENGINE=InnoDB DEFAULT CHARSET=gbkCREATE TABLE `mergeresult` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `sqlmap_file_id` int(11) NOT NULL,
 `tablename` varchar(30) DEFAULT NULL,
 `real_tablename` varchar(30) DEFAULT NULL,
 `exist_indexes` varchar(4000) DEFAULT NULL,
 `new_indexes` varchar(4000) DEFAULT NULL,
 `merge_result` varchar(4000) DEFAULT NULL,
 `gmt_create` datetime NOT NULL,
 `gmt_modified` datetime NOT NULL,
 PRIMARY KEY (`id`),
 KEY `idx_result_sqlmap_file_id` (`sqlmap_file_id`)) ENGINE=InnoDB DEFAULT CHARSET=gbk
配置好后:

第一步:./start_xmltosql.sh 解析sql 并插入到 xmltosql表中。 对于非Java应用来说,我们需要手动将SQL语句插入到表中,注意事项:sqlmap_file_id 这个必须和 sqlmap_file.xml中记录的filed-id 一致。Real_sql 可以是:select id from tasks where task_type= ?,还有status 必须是0 ,其他的可以随便写。

第二步:执行创建索引的脚本,这个会读取sqlreviewdb中待审核的SQL语句,解析SQL,根据规则生成创建索引的脚本,并保存到sqlreviewdb中,整个审核结束。
./start_createindex.sh
第三步是执行索引merge的脚本;
./start_mergeindex.sh

这个会读取sqlreviewdb中的生成的索引,以及这次审核所涉及的表上原有的索引,应用一些规则,进行索引的自动合并,将结果保存在mergeresult 表中的 merge_result 字段,我们在想把办法把这些 结果 应用到线上!

执行结果:
# sh start_createindex.sh
2012-09-26 16:31:14,886 INFO sqlautoreview.CreateIndex - ??????SQL_ID=1,??SQL TEXT?:select id from tasks where task_type= ?2012-09-26 16:31:14,891 INFO sqlautoreview.ParseSQL - SQL at parsing:select id from tasks where task_type= ?2012-09-26 16:31:14,891 DEBUG sqlautoreview.ParseSQL - addToColumnHashMap select_exprs:id2012-09-26 16:31:14,891 DEBUG sqlautoreview.ParseSQL - dealSingleSelectExpr select_expr:id2012-09-26 16:31:14,891 DEBUG sqlautoreview.ParseSQL - column_name:id alias_column_name:id2012-09-26 16:31:14,891 DEBUG sqlautoreview.ParseSQL - column_name: alias_column_name:id
2012-09-26 16:31:14,891 INFO sqlautoreview.ParseSQL - select columns:id
2012-09-26 16:31:14,891 INFO sqlautoreview.ParseSQL - table name:tasks
2012-09-26 16:31:14,892 INFO sqlautoreview.ParseSQL - where condition:task_type= ?2012-09-26 16:31:14,902 DEBUG sqlautoreview.MySQLMetaData - select count(distinct(id)),count(distinct(name)),count(distinct(task_type)),count(distinct(description)),count(distinct(guide_description)),count(distinct(triger_task_ids)),count(distinct(award_gold_money)),count(distinct(award_exp)),count(distinct(start_task_time)),count(distinct(end_task_time)),count(distinct(status)) from (select id,name,task_type,description,guide_description,triger_task_ids,award_gold_money,award_exp,start_task_time,end_task_time,status from tasks limit 10000) aa;2012-09-26 16:31:14,911 INFO sqlautoreview.CreateIndex - create index script: create index idx_tasks_task_type on tasks(task_type);
# sh start_mergeindex.sh
2012-09-26 16:36:45,725 DEBUG sqlautoreview.MySQLMetaData - tasks:PRIMARY(id);id(id,status);id_2(id,task_type);2012-09-26 16:36:45,726 DEBUG sqlautoreview.MergeIndex - 12012-09-26 16:36:45,726 DEBUG sqlautoreview.MergeIndex - tasks2012-09-26 16:36:45,729 DEBUG sqlautoreview.TableMergeIndex - removeExistIndexColumns:12012-09-26 16:36:45,729 DEBUG sqlautoreview.TableMergeIndex - removeExistIndexColumns:12012-09-26 16:36:45,729 DEBUG sqlautoreview.TableMergeIndex - removeExistIndexColumns:12012-09-26 16:36:45,729 DEBUG sqlautoreview.TableMergeIndex - removeExistIndexColumns:12012-09-26 16:36:45,729 DEBUG sqlautoreview.TableMergeIndex - removeExistIndexColumns:12012-09-26 16:36:45,729 DEBUG sqlautoreview.TableMergeIndex - removeExistIndexColumns:12012-09-26 16:36:45,730 DEBUG sqlautoreview.TableMergeIndex - mergeExistIndexes : drop exist index:drop index PRIMARY on tasks2012-09-26 16:36:45,731 DEBUG sqlautoreview.HandleSQLReviewDB - mergeresult sqlmap_file_id -10000000 is deleted.2012-09-26 16:36:45,731 DEBUG sqlautoreview.HandleSQLReviewDB - insert into mergeresult(sqlmap_file_id,tablename,real_tablename,exist_indexes,new_indexes,merge_result,gmt_create,gmt_modified) values(-1000000,'tasks','tasks','PRIMARY(id);id(id,status);id_2(id,task_type);','create index idx_tasks_task_type on tasks(task_type)','create index idx_tasks_task_type on tasks(task_type);drop index PRIMARY on tasks',now(),now())2012-09-26 16:36:45,731 INFO sqlautoreview.MergeIndex - ---------------------------------------------------2012-09-26 16:36:45,731 INFO sqlautoreview.MergeIndex - Table tasks Merge index information as follows:2012-09-26 16:36:45,732 INFO sqlautoreview.MergeIndex - ---------------------------------------------------2012-09-26 16:36:45,732 INFO sqlautoreview.MergeIndex - Exist indexes as follows:
2012-09-26 16:36:45,732 INFO sqlautoreview.MergeIndex - PRIMARY(id)
2012-09-26 16:36:45,732 INFO sqlautoreview.MergeIndex - id(id,status)
2012-09-26 16:36:45,732 INFO sqlautoreview.MergeIndex - id_2(id,task_type)
2012-09-26 16:36:45,732 INFO sqlautoreview.MergeIndex - New indexes as follows:2012-09-26 16:36:45,732 INFO sqlautoreview.MergeIndex - create index idx_tasks_task_type on tasks(task_type)2012-09-26 16:36:45,732 INFO sqlautoreview.MergeIndex - Result indexes as follows:2012-09-26 16:36:45,732 INFO sqlautoreview.MergeIndex - create index idx_tasks_task_type on tasks(task_type)2012-09-26 16:36:45,732 INFO sqlautoreview.MergeIndex - drop index PRIMARY on tasks

你可能感兴趣的:(sql,mysql,Review,审核)