最近在公司项目中,随着产品的迭代,需要维护数据库表结构、数据升级等问题。最初采用了平台数据库版本维护框架,需要写shell脚本,然后配置rundeck节点实现数据库一键升级。
简单的介绍.sh维护思路:
首先,数据库中,维护一张表history记录数据库版本,类似于flyway的flyway_schema_history表。在history中记录几个关键数据:exe_time、git_commit_id、db_name、sql_type等。
其次,为了使得DDL数据可重复执行,需要在数据库中编写存储过程。调用存储过程执行DDL。
第三,shell脚本逻辑:
在rundeck执行部署计划时,首先去history中查找最近一次执行部署记录;如果没有,则拉取仓库中所有*.sql文件,然后顺序执行文件中的SQL脚本。脚本文件按照文件名排序。
如果有记录,则对比文件改动日期,拉取仓库改动日期晚于最后一次执行部署记录的所有*.sql文件,然后顺序执行文件中的SQL脚本。脚本文件按照名称排序。
核心shell脚本如下:
#执行脚本函数 function sql_sync_frame(){ execmysqlbin="/usr/bin/mysql -h${host} -P${port} -u${user} -p${password} $2" sql_file=$(cat $1) while read line do exec_info="$(date +"%Y-%m-%d %H:%M:%S") 正在执行DDL sql变更:$sql_base_dir/${line}" if [ -z ${line} ]; then echo "空行跳过" fi echo ${exec_info} ret_info=`$execmysqlbin --default-character-set=utf8 < $sql_base_dir/${line} 2>&1` if [ $? == 1 ]; then echo -e "\e[31m执行$sql_base_dir/${line}出现异常,退出更新脚本!\e[0m" while read line; do echo -e "\e[31m${line}\e[0m" done< <(echo $ret_info) exit 1 fi done< <(echo -e "$sql_file") } #获取需要执行的脚本 function diff_git_sql(){ res=`${mysqlbin} "SELECT comit_id FROM sql_history WHERE sql_type='data' AND db = '${1}';"` hist_commit_id=`echo ${res}| awk '{print $2}'` echo -e "hist_commit_id:$hist_commit_id" if [[ "$hist_commit_id" = "" ]] then echo -e "没有查询到执行记录" first_start="first" find . -type f -name "*.sql"| sort >change_all.txt cur_commit_id=`git rev-parse HEAD` elif [[ "$hist_commit_id" = "`git rev-parse HEAD`" ]] then echo -e "\e[1;32mgit没有最新提交的sql文件\e[0m" exit 0; else cur_commit_id=`git rev-parse HEAD` echo -e "cur_commit_id:$cur_commit_id" git diff --name-only $hist_commit_id $cur_commit_id |grep ".*sql$"| sort > change_all.txt fi cat change_all.txt | awk -F"\/" '{print $1"/"$2}'|sort|uniq > change_category.txt change_category_file=$(cat change_category.txt) while read line do grep "$line" change_all.txt |grep -v ".*mix.sql$" >run_sql.txt grep "$line" change_all.txt |grep ".*mix.sql$" >>run_sql.txt sql_sync_frame run_sql.txt ${1} done< <(echo -e "${change_category_file}") if [[ "$first_start" = "first" ]] then `${mysqlbin} "insert into sql_history (comit_id,sql_type,db) values('${cur_commit_id}','data','${1}');"` else `${mysqlbin} "update sql_history set comit_id='${cur_commit_id}' where sql_type='data' AND db = '${1}';"` fi }
通过这个shell脚本可以完成数据库的维护,但是出现错误后无法回滚,直接跳出。所以我们在写DML语句时,均要求所以语句是幂等的。否则修改错误脚本后,再次执行,会导致脏数据的产生。但是目前而言,很多中初级Java程序员对shell脚本,导致运维的时候比较码放。所以寻求一种Java程序员可以快速掌握和维护的方法。就找到了flyway。当然还可以使用liquibase 。
FLYWAY介绍
可以直接访问我的github项目,该项目通过SpringBoot+Flyway实现数据库版本维护,其中数据库连接使用jdbc。
DEMO核心配置文件:
spring:
application:
name: dbmanage #服务名
profiles:
active: default # 配置文件的版本
datasource:
url: jdbc:mysql://127.0.0.1:3306/world?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
username: root
password: 1234
driver-class-name: com.mysql.cj.jdbc.Driver
# jpa:
# hibernate:
# naming:
# physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
# show-sql: true
flyway:
enabled: true
baseline-on-migrate: true
validateMigrationNaming: false
createSchemas: false
#initSql: CREATE IF NOT EXISTS flyway_history_schema
url: jdbc:mysql://127.0.0.1:3306/world?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
schemas: world
user: root
password: 1234
使用springboot+flyway所需的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
I、官网:https://flywaydb.org/
II、关于SQL脚本
SQL脚本默认放在./src/resources/db/migration,可以通过spring.flyway.locations修改
SQL脚本命名规范如下:
1、Prefix 前缀:V 代表版本迁移,U 代表撤销迁移(暂不支持),R 代表可重复迁移(内容发生变化后,脚本重复执行)
2、Version 版本号:版本号通常 . 和整数组成,前缀为R的脚本文件不能存在版本号--flyway_schema_history中的version属性值
3、Separator 分隔符:固定由两个下划线 __ 组成
4、Description 描述:由下划线分隔的单词组成,用于描述本次迁移的目的--flyway_schema_history中的description 属性值
5、Suffix 后缀:如果是 SQL 文件那么固定由 .sql 组成,如果是基于 Java 类则默认不需要后缀
III、版本管理表
系统会自动创建flyway_schema_history用于记录所有版本演化和状态,其表结构如下(以 MySQL 为例):
Field Type Null Key Default
version_rank int(11) NO MUL NULL
installed_rank int(11) NO MUL NULL
version varchar(50) NO PRI NULL
description varchar(200) NO NULL
type varchar(20) NO NULL
script varchar(1000) NO NULL
checksum int(11) YES NULL
installed_by varchar(100) NO NULL
installed_on timestamp NO CURRENT_TIMESTAMP
execution_time int(11) NO NULL
success tinyint(1) NO MUL NULL
其中:
checksum:Flyway 会给脚本计算一个 checksum 保存在数据库中,用于在之后运行过程中对比 sql 文件是否有变化,如果发生了变化,则会报错,也就防止了误修改脚本导致发生问题。
IV、flyway事务
默认情况下,Flyway总是将整个迁移的执行封装在单个事务中。也可以设置group属性等于true,将未执行的迁移封装到一个事务中。
对于SQL迁移,可以指定脚本配置属性executeInTransaction。
V、Schema creation
如果createSchemas设置为false,可能出现如下情况:
flyway.createSchemas=false
flyway.schemas=my_schema
1、运行迁移
2、my_schema 不是由Flyway创建的
3、由于my_schema是默认Schema,因此Flyway尝试在my_schema创建架构历史记录表
4、my_schema不存在,因此操作失败
因此,当切换createSchemas到时false,建议进行以下设置:
将默认Schema设置为 flyway_history_schema,通过设置defaultSchema或将其放在schemas配置选项中的第一位,设置initSql为创建flyway_history_schema(如果不存在)
将其他架构放在schemas属性中
flyway.createSchemas=false
flyway.initSql=CREATE IF NOT EXISTS flyway_history_schema
flyway.schemas=flyway_history_schema,my_schema
此时,将发生以下情况:
1、运行迁移
2、initSql被执行,所以flyway_history_schema被创建
3、由于flyway_history_schema是默认schema,因此Flyway尝试在flyway_history_schema创建架构历史记录表
4、my_schema 不是由Flyway创建的
5、迁移正常进行
6、迁移可自由控制my_schema的创建
VI、Migration States(迁移状态)
1、迁移已解决或已应用。Flyway的文件系统和类路径扫描程序已检测到已解决的迁移。最初,它们是未决的。一旦对数据库执行了它们,它们就会被应用。
2、迁移成功后,在Flyway的架构历史记录表中将其标记为成功(success)。
3、当迁移失败并且数据库支持DDL事务时,将回滚迁移,并且架构历史记录表中不会记录任何内容。
4、如果迁移失败并且数据库不支持DDL事务,则在模式历史记录表中将迁移标记为失败(failed),表明可能需要手动清理数据库。
5、已被撤消迁移撤消其影响的版本化迁移被标记为“撤消”(undone)。
6、自从上次应用以来,其校验和已更改的可重复迁移被标记为过时(outdated),直到再次执行它们为止。另请注意,更改占位符的值将导致可重复的迁移被视为过时(outdated)的迁移。
7、当Flyway发现所应用的版本迁移的版本高于已知的最高版本时(通常是在较新版本的软件已迁移该架构时发生),该迁移被标记为未来(future)。