公司內部推廣DevOps,全部項目配置CI/CD流水線(Jenkins+SVN+ANT),在代碼實現持續集成后,SQL語句自動執行的實現提上日程
一、環境
- Linux環境
- 安裝ANT工具,包括ant擴展包---ant-contrib-1.0b3.jar,maven鏈接
- 下載oracle連接ojdbc5.jar包。
二、思路
步驟1: 從SVN的QL腳本提交路徑上,下載所有SQL腳本
步驟2:【shell腳本】:獲取日期文件夾名為今天的文件夾下的sql腳本,拷貝到待執行文件夾中。將其中已執行成功的腳本(即存在於bak文件夾中)刪掉;
步驟3:【ant腳本】:對待執行腳本排序后,循環執行每條sql語句,執行成功的sql腳本備份到bak文件夾,方便后續執行時排除重復。
三、具體實現
3.1 源碼管理
3.2 執行shell
if [ $# -ne 1 ] then echo "Usage: $0 foldername" echo " $0 Exec SQL" exit fi Workspace=$1 year=`date +%Y` today=`date +%Y%m%d` sqlfolder=${Workspace}/svn/${year}/${today} bakfolder=${Workspace}/sqlbak/${year}/${today} logfolder=${Workspace}/sqllog #如果待執行sql文件夾已存在就清空該文件夾,否則創建 if [ -d "runSqlInFolder" ];then echo "-----Clean the executed sql folder:/runSqlInFolder-----" rm -rf runSqlInFolder/* else mkdir -p ${Workspace}/runSqlInFolder fi if [ ! -d "${logfolder}" ];then mkdir ${logfolder} else echo "-----Dir ${logfolder} is exist" fi #將今天提交到SVN的sql腳本中還未執行過的拷貝到runSqlInFolder文件夾 if [ -d "${sqlfolder}" ];then echo '-----Copy the sql file into folder' cp -r ${sqlfolder}/* runSqlInFolder find runSqlInFolder/ -name '*.jar' |xargs rm -rf find runSqlInFolder/ -type d | grep .svn$ | xargs rm -rf if [ -d "${bakfolder}" ];then cd ${bakfolder} find * -type f -name *.sql|xargs -i rm -f ../../../runSqlInFolder/{} else echo "-----create back folder: ${bakfolder}" mkdir -p ${bakfolder} fi else echo dir ${sqlfolder} not exist fi
3.3 Invoke ANT
3.3.1 定義一個target:runSqlInFolder
使用try catch包裹for 循環,for循環中調用execSQL標簽(自定義公共方法塊,即宏),順序逐條執行SQL腳本。
<taskdef resource="net/sf/antcontrib/antlib.xml" classpath="${lib}/ant-contrib-1.0b3.jar"/> <target name="runSqlInFolder"> <echo>Run the SQL at Folder: ${sqlfolder}</echo> <echo>DB Host: ${v7uatdb.host}</echo> <echo>DB Name: ${v7uatdb.name}</echo> <echo>DB User: ${v7uatdb.user}</echo> <trycatch property="errMsg"> <try> <for param="folder"> <path> <sort xmlns:rcmp="antlib:org.apache.tools.ant.types.resources.comparators"> <dirset dir="${sqlfolder}" includes="*" /> </sort> </path> <sequential> <echo>SQL Folder: @{folder}</echo> <for param="file"> <path> <sort xmlns:rcmp="antlib:org.apache.tools.ant.types.resources.comparators"> <fileset dir="@{folder}" includes="*/*.sql" casesensitive="false"/> </sort> </path> <sequential> <echo>SQL: @{file}</echo> <execsql dbhost="${v7uatdb.host}" dbport="${v7uatdb.port}" dbname="${v7uatdb.name}" dbuser="${v7uatdb.user}" dbpwd="${v7uatdb.pwd}" sqlfile="@{file}" logfile="${Sqllogfile}"/> </sequential> <!--<move file="@{file}" todir="${sqlbakdir}/@{folder}"/> folder 包含路徑和文件名,所以直接復制file還有點問題,需要截取文件名--目前待研究 --> </for> <move file="@{folder}" todir="${sqlbakdir}"/> </sequential> </for> <echo>Finished running all SQL</echo> <echo>File moved to backup folder:</echo> <echo>${sqlbakdir}</echo> </try> <catch> <echo>Error found when running SQL</echo> <echo>Log file can be found in:</echo> <echo>${sqlbakdir}/err</echo> <move file="${Sqllogfile}" todir="${sqlbakdir}/err"/> <fail>Error Occur</fail> </catch> <finally> </finally> </trycatch> </target>
3.3.2 定義execsql標簽
通過sql標簽執行sql文件,通過record標簽記錄對應的執行日志並輸出。
注意:如果執行procedure就需要設置delimiter,本例中通過SQL文件的命名來區分是不同的SQL類型(procedure,declare等)。
<macrodef name="execsql" description="Run single SQL file."> <attribute name="dbhost" description="Host Name/ IP of the DB"/> <attribute name="dbport" description="DB Port"/> <attribute name="dbname" description="DB name"/> <attribute name="dbuser" description="DB User name"/> <attribute name="dbpwd" description="DB Password"/> <attribute name="sqlfile" description="SQL file to be run"/> <attribute name="logfile" default="sql.log" description="Log file"/> <sequential> <echo>Log file @{logfile}</echo> <record name="@{logfile}" action="start"/> <if> <contains string="@{sqlfile}" substring="PROCEDURE"/> <then> <sql driver="${oracleDriver}" url="jdbc:oracle:thin:@@@{dbhost}:@{dbport}:@{dbname}" userid="@{dbuser}" password="@{dbpwd}" classpathref="classpath" encoding="${encoding}" print="true" autocommit="true" delimiter="/" delimitertype="row"> <transaction src="@{sqlfile}"/> </sql> </then> <else> <sql driver="${oracleDriver}" url="jdbc:oracle:thin:@@@{dbhost}:@{dbport}:@{dbname}" userid="@{dbuser}" password="@{dbpwd}" encoding="${encoding}" classpathref="classpath" autocommit="true" print="true"> <transaction src="@{sqlfile}"/> </sql> </else> </if> <record name="@{logfile}" action="stop"/> </sequential> </macrodef>
更新后
<macrodef name="execsql" description="Run single SQL file."> <attribute name="dbhost" description="Host Name/ IP of the DB"/> <attribute name="dbport" description="DB Port"/> <attribute name="dbname" description="DB name"/> <attribute name="dbuser" description="DB User name"/> <attribute name="dbpwd" description="DB Password"/> <attribute name="sqlfile" description="SQL file to be run"/> <attribute name="logfile" default="sql.log" description="Log file"/> <sequential> <echo>Log file @{logfile}</echo> <record name="@{logfile}" action="start"/> <if> <contains string="@{sqlfile}" casesensitive="no" substring="PROCEDURE"/> <then> <sql driver="${oracleDriver}" url="jdbc:oracle:thin:@@@{dbhost}:@{dbport}:@{dbname}" userid="@{dbuser}" password="@{dbpwd}" classpathref="classpath" encoding="${encoding}" print="true" autocommit="true" delimiter="/" delimitertype="row"> <transaction src="@{sqlfile}"/> </sql> </then> <elseif> <contains string="@{sqlfile}" casesensitive="no" substring="DECLARE"/> <then> <sql driver="${oracleDriver}" url="jdbc:oracle:thin:@@@{dbhost}:@{dbport}:@{dbname}" userid="@{dbuser}" password="@{dbpwd}" classpathref="classpath" encoding="${encoding}" print="true" autocommit="true" delimiter=";;"> <transaction src="@{sqlfile}"/> </sql> </then> </elseif> <else> <sql driver="${oracleDriver}" url="jdbc:oracle:thin:@@@{dbhost}:@{dbport}:@{dbname}" userid="@{dbuser}" password="@{dbpwd}" encoding="${encoding}" classpathref="classpath" autocommit="true" print="true"> <transaction src="@{sqlfile}"/> </sql> </else> </if> <record name="@{logfile}" action="stop"/> </sequential> </macrodef>
3.4 SVN中存放SQL的文件夾設置

3.5 成功執行效果

3.5 注意
在這個過程中,需要DBA 介入人工審核:
第一類:是在提交腳本之后。審核的內容主要是變更內容是否合法、方式是否得當、是否影響業務等等。
第二類:是在提交生產變更后。審核的主要的內容是,判斷變更是否會對當時的生產系統產生影響。比如,訂單表的更新、大表的變化等,就不允許在業務高峰期進行。
四、遇到的問題處理
4.1【問題】如果執行的SQL語句中存在中文,執行完在數據庫中保存的數據是亂碼
目前常用的兩種數據庫編碼格式為:GBK、UTF-8,也是兩種編碼風格(ANSI和UNICODE)中的代表
經確認公司數據庫采用GBK編碼,所以將encoding改為GBK,亂碼問題解決,ant文件頭設置
<property name="encoding" value="GBK" /><!--UTF-8-->
4.2【問題】如果執行的SQL語句中,有declare,聲明變量的,執行declare過程中會報錯
delete zssys.WEB_APP_TEMPLATE where C_INTERFACE_ID = '0000-GDCARVA';; declare response clob; begin response := 'aaa'; insert into zssys.WEB_APP_TEMPLATE (C_PK_ID, C_INTERFACE_ID, C_SYSCODE, C_APP_NAME, C_TEMPLATE_REQUEST, C_TEMPLATE_RESPONSE) values (SYS_GUID(), '0000-GDCARVA', '*', 'test', null, response); end;
4.3【問題】目前備份腳本是按照模塊整體備份的,沒有做到單個sql腳本備份,后期優化
五、 參考資料
使用ANT腳本批量執行SQL,並且結合Jenkins自動化構建
