如何无需for循环添加条件 筛选复制高于当前DB版本的SQL文件
按版本筛选复制SQL文件的高效实现方案
场景与需求
- 待复制SQL文件以版本号为文件名,格式示例:
001.000.000.000001.sql - 当前数据库版本存储在变量中,版本字符串为13位定长数字,格式示例:
2002000000020 - 核心要求:跳过版本号小于等于当前DB版本的文件,仅复制更高版本的SQL文件,避免全量复制,同时尽量不使用低效的逐文件shell循环判断。
现有实现问题
最早采用嵌套for循环逐文件判断的实现,文件量大时执行效率极低,初始代码如下:
for d in $archive_deltascripts_dir/*; do for f in $d/*; do local filename=$(basename "$f") cp -r "$f" "$path" done done for f in $release_deltascripts_dir/*; do local filename=$(basename "$f") cp -r "$f" "$path" done
后续改为无判断全量复制逻辑,虽然执行速度快,但会复制大量不需要的低版本文件,代码如下:
for d in $archive_deltascripts_dir/*; do cp -r "$d/." "$path" done cp -r "$release_deltascripts_dir/." "$path"
目前已实现文件名版本号到DB版本格式的转换逻辑,可将文件名中的版本段转换为与DB版本一致的13位数字格式,转换命令如下:
echo ${scriptName} | sed 's/\(^[0-9]\{1\}\.[0-9]\{3\}\.[0-9]\{3\}\.[0-9]\{6\}\).*$/\1/g' | sed 's/\.//g'
最优实现方案
不需要写逐文件遍历的shell循环,利用find的文件系统级过滤+定长版本号的字典序特性即可实现高效筛选,性能比shell循环高1~2个数量级。
核心原理:SQL文件版本段是固定长度补零的结构化命名,1位主版本.3位次版本.3位修订版.6位脚本序号的格式下,字符串字典序和版本数值序完全等价,不需要对每个文件做格式转换,仅需提前1次把当前DB版本转为文件名前缀格式作为对比阈值即可。
实现代码:
# 仅执行1次:将当前DB版本转为文件名前缀格式作为筛选阈值,无逐文件开销 current_ver_filefmt=$(echo "$current_db_ver" | sed -E 's/^([0-9]{1})([0-9]{3})([0-9]{3})([0-9]{6})$/\1.\2.\3.\4/') # 批量筛选复制archive目录下(二级子目录内)的高版本SQL find "$archive_deltascripts_dir" -maxdepth 2 -type f -name "*.sql" \ -regextype posix-extended -regex ".*/[0-9]{1}\.[0-9]\{3\}\.[0-9]\{3\}\.[0-9]\{6\}\.sql$" \ -exec bash -c ' for f do [[ $(basename "$f" .sql) > "$1" ]] && cp "$f" "$2/" done ' bash "$current_ver_filefmt" "$path" {} + # 批量筛选复制release目录下的高版本SQL find "$release_deltascripts_dir" -maxdepth 1 -type f -name "*.sql" \ -regextype posix-extended -regex ".*/[0-9]{1}\.[0-9]\{3\}\.[0-9]\{3\}\.[0-9]\{6\}\.sql$" \ -exec bash -c ' for f do [[ $(basename "$f" .sql) > "$1" ]] && cp "$f" "$2/" done ' bash "$current_ver_filefmt" "$path" {} +
方案优势
- 过滤逻辑在文件系统层完成,避免shell逐文件遍历的开销,文件量越大性能优势越明显
- 版本格式转换仅执行1次,不需要为每个文件启动sed子进程做格式转换,资源消耗极低
- 字符串对比无数值运算溢出风险,只要文件名严格遵循固定长度补零规则,版本判断准确率100%
注意:如果存在文件名不符合版本命名规范的SQL文件,会被find的正则规则自动过滤,不会被复制。
兼容现有转换逻辑的次优方案
如果不想修改已有的版本转换逻辑,也可以直接用find批量传参处理,相比原来的shell循环依然有明显性能提升,只是每个文件需要调用sed做转换,性能略低于上面的最优方案:
find "$archive_deltascripts_dir" "$release_deltascripts_dir" -type f -name "*.sql" \ -exec bash -c ' for f do scriptName=$(basename "$f") file_ver=$(echo "$scriptName" | sed "s/\(^[0-9]\{1\}\.[0-9]\{3\}\.[0-9]\{3\}\.[0-9]\{6\}\).*$/\1/g" | sed "s/\.//g") (( file_ver > current_db_ver )) && cp "$f" "$path/" done ' bash {} +
内容的提问来源于stack exchange,提问作者Gian
相关产品推荐
相关产品推荐

