{"id":16962210,"url":"https://github.com/trydofor/godbart","last_synced_at":"2025-04-11T22:12:48.630Z","repository":{"id":64302916,"uuid":"156685396","full_name":"trydofor/godbart","owner":"trydofor","description":"go-db-art, a SQL-based CLI for RDBMS schema versioning \u0026 data migration","archived":false,"fork":false,"pushed_at":"2021-07-21T10:21:56.000Z","size":196,"stargazers_count":14,"open_issues_count":0,"forks_count":4,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-04-11T22:12:43.386Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/trydofor.png","metadata":{"files":{"readme":"README.md","changelog":"changelog.txt","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-11-08T09:52:50.000Z","updated_at":"2021-07-22T02:02:42.000Z","dependencies_parsed_at":"2023-01-15T09:45:19.535Z","dependency_job_id":null,"html_url":"https://github.com/trydofor/godbart","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trydofor%2Fgodbart","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trydofor%2Fgodbart/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trydofor%2Fgodbart/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trydofor%2Fgodbart/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/trydofor","download_url":"https://codeload.github.com/trydofor/godbart/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248487684,"owners_count":21112190,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-13T23:05:42.801Z","updated_at":"2025-04-11T22:12:48.605Z","avatar_url":"https://github.com/trydofor.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# godbart - go-db-art\n\n\n```\n  |^^^^^^|    /god-bart/是一个go写的\n  |      |    基于SQL的RDBMS运维CLI\n  | (o)(o)    □ 多库执行SQL，DB版本管理\n  @      _)   □ 比较结构差异，生成原始DDL\n   | ,___|    □ 提取业务逻辑关联的`数据树`\n   |   /      □ 纯SQL做配置，注释做关联\n```\n\n使用场景和前置要求，\n\n * DBA维护多库，一个SQL在多库上执行。\n * 支持分表，多表的更新和版本管理。\n * 生成某库某表的创建SQL（表\u0026索引，触发器）。\n * 对比多库多表的结构差异（表，列，索引，触发器）。\n * 多库的版本管理，按指定版本更新。\n * 提取`数据树`，保存为CSV/JSON文件。\n * 数据归档，从A库迁移`数据树`到B库。\n * 主键有分布式特征，无自增型。\n * SQL语句，必须有结束符，如`;`，否则认为是一组。\n * 当前只适配了MySql，可自行实现PG版。\n * 正则Regexp为go标准库`re2`，有些高级功能不支持。\n\n`数据树(DataTree)` 指一堆有业务逻辑关联的树状或图状的数据。\n比如`demo/init/2.data.sql`中的关系，存在以下多个`1:N`关系。\n```\n|-(TOP)-收件人(tx_receiver)\n|      |-(1:N)-包裹(tx_parcel)\n|      |      |-(1:N)-物流信息(tx_track)\n|      |      |-(1:N)-包裹事件(tx_parcel_event)\n|      |      |-(1:N)-历史变更(tx_parcel$log)\n```\n就可以形成以`收件人`为根的树，或从`包裹`为根的树。\n对于非单继承（多个父节点）的数据结构，有多重循环时会存在问题。\n\n## 1. 场景举例\n\n以下是开发和测试环境，得益于GoLang的优势，理论上应该跨平台。\n\n * ubuntu 16.04/ mac 10.15.7 \n * Go 1.11.2 / 1.16\n * MySQL (5.7.23)\n \n下列各命令的参数，大部分时通用的，所以举例中不重复介绍各参数。\n\n### 1.1. 执行脚本 Exec\n\n在不同的db上，纯粹的批量执行SQL。\n\n```bash\n# 执行 demo/sql/init/的`*.sql`和`*.xsql`\n./godbart exec \\\n -c godbart.toml \\\n -d prd_main \\\n -d prd_2018 \\\n -x .sql -x .xsql \\\n -l trace \\\n demo/sql/init/\n```\n\n其中，`exec` 命令，会把输入的文件或路径，分成SQL组执行。\n\n * `-c` 必填，配置文件位置。\n * `-d` 必填，目标数据库，可以指定多个。\n * `-x` 选填，SQL文件后缀，不区分大小写。\n * `-l` 选填，通过修改输出级别，调整信息量。\n * `--agree` 选填，风险自负，真正执行。\n \n 在分表上执行，参考`revi`说明。\n\n### 1.2. 版本管理 Revi\n\n健康的数据库需要有版本管理。通常，有一个版本信息表，用来识别和对比版本号。\n`Revi`只考虑Up不考虑Down。如果需要Down时，以`逆向补丁`形式进行Up。\n\n```bash\n# 执行 demo/sql/revi/*.sql，具体SQL写法参考此目录的文件\n./godbart revi \\\n -c godbart.toml \\\n -d prd_main \\\n -d prd_2018 \\\n -r 2018111701 \\\n -m '[0-9a-z]{10,}'\n -x .sql -x .xsql \\\n demo/sql/revi/\n```\n\n其中，`revi` 命令，会把输入的文件或路径的SQL进行按版本号分组。\n\n * `-c` 必填，配置文件位置。\n * `-d` 必填，目标数据库，可以指定多个。\n * `-r` 必填，执行到的版本号。\n * `-m` 选填，版本更新语句中版本号的正则，默认10位以上数字。\n * `-q` 选填，查询版本语句的前缀，`SELECT` 不区分大小写。\n * `-x` 选填，SQL文件后缀，不区分大小写。\n * `--agree` 选填，风险自负，真正执行。\n\n`版本号`要求，\n * 必须全局唯一且递增，但不要求连续。\n * 能以字符串方式比较大小，如日期+序号：`yyyymmdd###`。\n * 具有可以用正则匹配提取的固定格式。\n\n\n具有版本管理的SQL要求，必须被`版本查询`和`版本更新`的SQL包围。\n因此，SQL文件中，首个SELECT和最尾的Execute，视为版本查询和更新的SQL。\n\n作为参数传入的版本文件，内含版本号需要递增，否则报错（程序只检查，不排序）。\n\n```mysql\n-- 创建version表 # 此时没有版本查询，但在之前，因此会被执行\nCREATE TABLE `sys_schema_version` (\n  `version` BIGINT NOT NULL COMMENT '版本号',\n  `created` DATETIME NOT NULL COMMENT '创建时间',\n  PRIMARY KEY (`version`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;\n\n-- 版本查询\nSELECT max(version) FROM sys_schema_version;\n\nALTER TABLE `tx_outer_trknum`\n  ADD COLUMN `label_file` VARCHAR(200) DEFAULT NULL COMMENT '面单文件位置' AFTER `trknum`;\nALTER TABLE `tx_outer_trknum$log`\n  ADD COLUMN `label_file` VARCHAR(200) DEFAULT NULL COMMENT '面单文件位置' AFTER `trknum`;\n\n-- 版本更新\nREPLACE INTO sys_schema_version (version, created) VALUES( 2018022801, NOW());\n```\n\n### 1.3. 分表版本管理 Revi\n\n当存在分表的情况下，可以按序号建表，或者根据规则更新已存在的表。\n更多关于`指令`可以参考`指令变量`说明，及`tree`应用实例。\n\n```mysql\n-- SEQ tx_test_%02d[1,10] tx_test_##\nCREATE TABLE `tx_test_##` (\n  `id` BIGINT NOT NULL COMMENT 'id',\n  PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;\n\n\n-- TBL tx_outer_trknum.* `tx_outer_trknum`\nALTER TABLE `tx_outer_trknum`\n  ADD COLUMN `label_file` VARCHAR(200) DEFAULT NULL COMMENT '面单文件位置' AFTER `trknum`;\n```\n\n上述SQL会完成以下两种操作。\n\n * 创建 tx_test_01,...,tx_test_20，一共20张表\n * 更新 tx_outer_trknum 和 tx_outer_trknum$log表\n\n### 1.4. 结构对比 Diff\n\n用来对比结构差异，支持table\u0026index，trigger。\n\n对比结果中，用`\u003e`表示只有左侧存在，`\u003c`表示只有右侧存在。\n过程信息以log输出。结果信息fmt输出，可通过`SHELL`特性分离信息。\n\n```bash\n# 对表名，字段，索引，触发器都进行比较，并保存结果到 main-2018-diff-out.log\n./godbart diff \\\n -c godbart.toml \\\n -s prd_main \\\n -d prd_2018 \\\n -d dev_main \\\n -t tbl,trg \\\n 'tx_.*' \\\n| tee main-2018-diff-out.log\n```\n\n * `-s` 左侧比较相，必须指定。\n * `-d` 右侧比较相，可以零或多。\n * `-t` 比较类型，支持以下三种，默认`tbl`，多值时用逗号分割。\n    - `tbl` 表明细(column, index)\n    - `trg` trigger\n    - `sum` 仅显示表名差异\n\n参数为需要对比的表的名字的正则表达式。如果参数为空，表示所有表。\n正则会默认进行全匹配，等同于`^$`的效果。\n\n当只有一个库时，不做比较，而是打印该库，多个时才进行比较。\n\n### 1.5. 生成脚本 Show\n\n生成一些常用的DDL，如创建table, trigger，更复杂的history历史表。\n\n```bash\n./godbart show \\\n -c godbart.toml \\\n -s prd_main \\\n -t tbl,trg \\\n 'tx_[^$]+' \\\n| tee prd_main-show-out.log\n```\n模板在`godbart.toml`中的`sqltemplet`里配置，`key`就是`-t` 参数，多个时用`,`分割。\n模板使用的`变量`全都存在时，输出模板，全都不存在时不输出，其他则报错。\n\n因为不支持否定环视，所有已不包含`$`去除`$log`表。\n\n系统内置了以下`变量`，不想使用`${}`不可以省略，包含数组的模板会循环输出。\n\n * ${TABLE_NAME}   string, 当前table名\n * ${TABLE_DDL}    string, 当前table的DDL\n * ${TRIGGER_NAME} []string, 当前table的trigger名\n * ${TRIGGER_DDL}  []string, 当前table的trigger的DDL\n * ${COLUMNS_BASE} string, 当前table的所有列的基本信息(名字和类型)。\n * ${COLUMNS_FULL} string, 当前table的所有列的全部信息(同创建时，创建DDL必须一行一列，否则解析可能错误)。\n\n### 1.6. 结构同步 Sync\n\n同步多库间的表结构，目前只支持空表创建。此场景一般出现在初始化一个新数据库的时候。\n因为数据库版本管理不会造成很大差异，如果存在差异，且有数据的情况下，人工介入更好。\n\n对于小表，提供数据同步。而多实例，大表，建议使用DBA的方式同步，性能更好。\n\n注意，对于DBA，可以使用`mysqldump -d`来导出表结构。\n\n```bash\n./godbart sync \\\n -c godbart.toml \\\n -s prd_main \\\n -d prd_2018 \\\n -t tbl,trg \\\n 'tx_.*'\n```\n\n * `-s` 左侧比较相，可以零或一。\n * `-d` 右侧比较相，可以一或多。\n * `-t` 创建类型，支持以下三种，默认`tbl`。\n    - `tbl` 只创建表和索引\n    - `trg` 只创建trigger\n    - `row` 标准insert语法，并忽略重复，不如DBA脚本猛烈，适合小数据。\n * `--agree` 选填，风险自负，真正执行。\n\n参数为需要对比的表的名字的正则表达式。如果参数为空，表示所有表。\n\n### 1.7. 数据迁移 Tree\n\n不建议一次转移大量数据，有概率碰到网络超时或内存紧张。\n\n```bash\n# 把数据从main迁移到2018库，结果保存到main-tree-out.log\n./godbart tree \\\n -c godbart.toml \\\n -s prd_main \\\n -d prd_2018 \\\n -x .sql -x .xsql \\\n -e \"DATE_FROM=2018-11-23 12:34:56\" \\\n demo/sql/tree/tree.sql\n \u003e main-tree-out.log\n\n# 静态分析上面的datatree语法结构。\n./godbart sqlx \\\n -c godbart.toml \\\n -e \"DATE_FROM=2018-01-01 00:00:00\" \\\n demo/sql/tree/tree.sql \\\n | tee /tmp/sqlx-tree.log\n```\n\n不同业务场景对`数据活性`有不同的定义，比如日期，按ID范围等。\n`Tree`命令只支持静态分离数据，即在执行前已预知数据范围和目标数据库。\n因为动态分库，通常有业务代码负责，而不会沦落到\"SQL+数据维护\"的层面。\n此外，要求表的主键具有分布式主键特质（自增型主机很糟糕，破坏数据关系）\n\n数据树(DataTree)的核心是`占位`，其具有以下特性。\n\n * 定义（Def）的唯一性。\n * 可以准确描述数据关系。\n * 可以满足基本的SQL语法。\n * 占位必须先声明再使用，以区别普通字面量。\n\n```mysql\n-- 建立分库有关的表\nCREATE TABLE `sys_hot_separation` (\n  `table_name` VARCHAR(100) NOT NULL COMMENT '表名',\n  `checked_id` BIGINT(20) NOT NULL COMMENT '检查过的最大ID',\n  `checked_tm` DATETIME NOT NULL COMMENT '上次检查的时间',\n  PRIMARY KEY (`table_name`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;\n```\n\n分离数据的规则必须预先可知，如下脚本根据历史信息，迁移10棵以tx_parcel为根的`数据树`。\n并且每迁移一棵树，就会在源数据库上执行一次`FOR`，用来完成此树的标记和清理工作。\n\n注意：`FOR`时强关系，`REF`是弱关系，两者的关联和区别，见后面章节。\n\n```mysql\n-- 不存在则增加默认值\nINSERT IGNORE sys_hot_separation VALUES ('tx_parcel',0,now());\n\n-- VAR checked_id 'tx_parcel.checked_id'  #数据树根节点\nSELECT checked_id FROM sys_hot_separation WHERE table_name = 'tx_parcel';\n\n-- REF id 'tx_parcel.id'  #一级树节点'tx_parcel.id'，父节点是 'tx_parcel.checked_id'\n-- REF track_num 'tx_parcel.track_num'  #提取结果中的id和track_num作为变量，形成数据树\nSELECT * FROM tx_parcel WHERE id \u003e 'tx_parcel.checked_id' LIMIT 10;\n\n-- REF id 990003  #二级树节点990003，父节点是 TRK0001\nSELECT * FROM tx_track WHERE track_num = 'tx_parcel.track_num';\n\n-- REF id 990004 #二级树节点990004，父节点是 'tx_parcel.id'\nSELECT * FROM tx_parcel_event WHERE parcel_id = 'tx_parcel.id';\n\n-- RUN FOR 'tx_parcel.id' #每棵'tx_parcel.id'树节点完成时，执行此语句\nREPLACE INTO sys_hot_separation VALUES ('tx_parcel', 'tx_parcel.id', now());\n```\n\n### 1.8. 控制端口\n\n对于长时间执行的命令，支持单例和运行时控制（如优雅停止），因此增加了`控制端口`功能。\n其监听TCP端口（建议1024以上），当端口号≤0时，表示忽略此功能。\n开启`控制端口`时，会在stderr输入`控制密码`，通过`127.0.0.*`登录不需要密码。\n\n * 单例，检测`控制端口`是否被监听，保证当前主机唯一单例。\n * 控制，通过tcp链接，输入`控制密码`，验证后，执行支持的命令。\n\n全局命令：\n * help - 查看帮助。\n * exit - 关闭当前连接。\n * pass - 生成一个新密码，作废旧密码，新登录有效。\n * info - 查看当前用户和待执行的命令。\n * kill N - 杀掉队列中id=N的任务，N=-1时，清掉全部。\n * `/` 公聊，跟所有登录用户发消息。\n * `/ip:port ` 私聊，指定登录用户发消息。\n\n非全局命令，称作一个`room`，一个room内，改变行为的命令的信息时全员可见的。\n只对`Tree`提供了以下命令，可使用不存在的id查看当前运行情况。\n\n\n * tree - 显示当前在执行的sqlx的树状结构及ID。\n * stat - 显示当前在执行的信息。\n * stop - 优雅的停止程序(exit 99)，全员可见。\n   - stop 直接在当前树结束时停止。\n   - stop N 在id=N的树时停止，N\u003c0时等效于stop。\n * wait - 执行等待，kill可继续。长时间停止可能导致数据库连接超时。全员可见。\n   - wait 在当前树完成时等待。\n   - wait N 在id=N的树时停止，N\u003c0时等效于stop。\n   \n```bash\n# 连接控制端口，非127.0.0.* 登录，需要先输入密码\ntelnet 127.0.0.1 59062\n# 以下为连接成功输入的命令。\n\ninfo # 查看运行信息\ntree # 查看当前执行`数据树`结构\nstat # 查看统计情况\nwait 0 # 空等待，显示每个执行节点信息。\nkill # 清理掉所有任务\nstop # 优雅停止当前一棵树的结束\n```\n\n## 2. 指令变量\n\n`指令`在SQL的注释中定义，由`指令名`，`变量para`和`占位hold`三部分构成。\n`指令`保留SQL的可读性和执行能力，对DBA友好，在运行时进行静态或动态替换。\n\n`数据树`按SQL的自然顺序构建和执行，`占位`必须先声明再使用，否则无法正确识别。\n明确语意和增加可读性，`RUN|OUT`存在顺序调整，下文有讲。\n\n`挂树`是指数据数分叉时，寻找父树的动作，当前的规则如下：\n * `RUN|OUT` 属于显示`挂树`，优先级是`10`，支持多父结构\n * `REF|STR`，是隐式挂树，只支持单父，取按行号大者，优先级是`20`。\n * `SEQ|TBL`，同`REF`，优先级时`30`。\n * 按优先级`挂树`，数值越小优先级越高，当高优先级完成后，忽略低优先级。\n\n * `指令名`是固定值，当前只支持，`ENV|REF|STR|RUN|OUT`\n    - `ENV|REF|STR|SEQ|TBL` 等会产生值，为定义（Def）指令。\n    - `RUN|OUT` 为行为（Act）指令。\n    - `ENV|REF` 对`变量`自动脱去最外层成对的引号。\n    - `STR` 有自己的脱引号规则，以进行`模式展开`。\n * `引号`包括，单引号`'`，双引号`\"`，反单引号`` ` ``。\n * `空白`指英文空格`0x20`和制表符`\\t`\n * `变量`和`占位`要求相同，都区分大小写。\n    - ```[^ \\t'\"`]+``` 连续的不包括引号和空白的字符串。\n    - ```(['\"`])[^\\1]+\\1```成对引号括起来的字符串(非贪婪)。\n * `占位`，在SQL语句符合语法的字面量（数字，字符串，语句等）。\n    - 必须当前SQL中全局唯一，不与其他字面量混淆，以准确替换，确定数据关系。\n    - 尽量使用SQL的合规语法，没必要自找麻烦，比如没必要的引号或特殊字符。\n    - 使用时，保留所有引号。\n    - 选择`占位`，尽量构造出where条件为false的无公害SQL。\n\n注意：所有包含`空白`的`变量`和`占位`，都需要有`引号`配合\n\n### 2.1. 环境变量 ENV\n\n`ENV`通过 `-e MY_ENV=\"my val\"`从命令行传入，全局有效。\n当只有Key时，表示使用系统变量，如 `-e PATH`。\n\n系统内置了以下变量，\n \n - `USER`，当前用户\n - `HOST`，主机名\n - `DATE`，当前日时(yyyy-mm-dd HH:MM:ss)\n - `ENV-CHECK-RULE`，ENV检查规则，默认报错，可用`EMPTY`置空\n - `SRC-DB`，当前执行的源DB（只有Tree，且唯一）；\n - `OUT-DB`，当前执行的目标DB（只有Tree，只有OUT时能确定）；\n\n当`变量`被`1个以上`的`反单引号`包围时，表示此`ENV`通过运行`SQL`获得，\n是第一条记录的第一个字段。优点是不会被纳入数据树，缺点是不享受SQL高亮，\n不能替换其他占位。注意 STR不支持这么骚气的操作，因为有模式展开。\n\n如下SQL，定义环境变量`DATE_FROM`，其占位符`'2018-11-23 12:34:56'` ，\n需要通过系统环境变量获得，如果不存在（默认ERROR）则会报错。\n\n假设运行时 `DATE_FROM`的值为`'2018-01-01 00:00:00'`，那么上述SQL执行时为，\n是采用PreparedStatement的动态形式，可避免SQL转义或注入，提高运行时性能。\n\n```mysql\n-- ENV ``SELECT NOW();`` sql_now  运行时赋值\n\n-- ENV DATE_FROM '2018-11-23 12:34:56'\nSELECT * FROM tx_parcel WHERE create_time = '2018-11-23 12:34:56';\n\n-- 运行时替换，比如实际参数为'2018-01-01 00:00:00'\n-- SELECT * FROM tx_parcel WHERE create_time = ?\n```\n\n### 2.2. 结果引用 REF\n\n`REF` 也采用PreparedStatement替换，并对所在结果集的每条记录循环。\n多个`REF`会产生多个分叉点，进而形成不同的子数据树。\n\n当子语句，只依赖一个`REF`的`占位`(如9900397)时，相当于`RUN FOR 9900397`，\n两者在关系上等价的，但执行时机不同，前者在树中，后者在树末。\n\n当子语句，会依赖多个`REF`的`占位`(如9900398,9900399)时，为了避免歧义，\n必须使用 `RUN/OUT`精确描述，否则系统会任性选择。\n\n如下SQL，定义了结果集的引用 `id`和`track_num`变量，和他们对应的SQL占位符。\n其中，`id`和`track_num`，都是`tx_parcel`的结果集中，用来描述数据树。\n\n```mysql\n-- ENV DATE_FROM '2018-11-23 12:34:56'\n-- REF `id` 1234567890  #假设id需要反单引号处理\n-- REF track_num 'TRK1234567890'\nSELECT * FROM tx_parcel WHERE create_time = '2018-11-23 12:34:56';\n\nSELECT * FROM tx_track WHERE track_num = 'TRK1234567890';\n\nSELECT * FROM tx_parcel_event WHERE parcel_id = 1234567890;\n```\n\n系统为结果集（SELECT）内定了引用，以便可以多值insert和update语句。\n\n * `COL[]` 表示所有列名，会展开为 `id`,`name`,等（可以转义）\n * `VAL[]` 表示结果的值，会展开为 `?`占位符和对应值。\n * `COL[1]` 表示获得第1个列名\n * `VAL[2]` 表示获得第2个值\n \n 其中，角标从1开始。引用为数组时，在`[]`内指定分隔符，约定如下，\n \n * `COL[]`和`COL[,]`相同，分隔符默认是`,`。\n * 存在多个分隔符时，只取第一个非空的。\n * 不能用数字，因为做角标\n * 不能用`[`或`]`，因为你懂的。\n * 仅支持`\\\\`，`\\t`，`\\n`的字符转义。\n\n### 2.3. 变量声明 VAR\n\n同`REF`一样作用于结果集，但不形成树状结构。和`ENV`相比，可以时string之外的SQL类型。\n\n### 2.4. 静态替换 STR\n\n`STR`与`ENV`和`REF`不同，采用的是静态替换字符串。\n它可以直接定义（同`REF`和`ENV`），也以重新定义其他动态`占位`使其静态化。\n\n`脱引号`处理，当`变量`和`占位`具有相同的引号规则，会都脱去最外的一层。\n此规则只对`STR`有效，因为其变量部分，可以重定义带有引号的`占位`。\n\n`模式展开`，`变量`中有`COL[*]`或`VAL[*]`时，会进行展开，规则如下，\n\n - 首先脱引号处理。\n - 只支持直接定义，不支持重新定义。\n - 除了`COL[*]`和`VAL[*]`外，都作为字面量处理，不会深度展开。\n - `COL[*]`部分，使用静态替换。\n - `VAL[*]`部分，使用PreparedStatement形式执行。\n\n```mysql\n-- REF Y4 '2018-00-00 00:00:00'\nSELECT year(now()) as Y4;\n\n-- STR '2018-00-00 00:00:00' $y4_table   #重新定义，以使SQL语法正确。\nCREATE TABLE tx_parcel_$y4_table LIKE tx_parcel;\n-- 替换后\n-- CREATE TABLE tx_parcel_2018 LIKE tx_parcel;\n\n-- STR COL[1] $COL1  #直接定义。\n-- STR \"`COL[]` = VAL[]\" \"logno = -99009\"  #直接定义，脱引号，模式展开。\n-- REF VAL[1] '占位值'\n-- REF id 'tx_parcel.checked_id'\nSELECT * FROM tx_parcel WHERE create_time = '2018-11-23 12:34:56';\n\nINSERT INTO tx_parcel (`$COL1`) VALUES ('占位值');\n-- 替换后\n-- INSERT INTO tx_parcel (`id`) VALUES (?);\n\nUPDATE tx_parcel SET logno = -99009 WHERE id='tx_parcel.checked_id';\n-- 替换后\n-- UPDATE tx_parcel SET `id` = ? ,`create_time` = ? /*循环加下去，逗号分割*/ WHERE id='tx_parcel.checked_id';\n```\n\n### 2.5. 整数序列 SEQ\n\n`SEQ`会生成整数序列，只支持静态替换。\n\n * `参数` 格式为`格式[开始,结束,步长]`，如`tx_test_%02d[1,20]`\n * `格式`为`fmt`的`printf`标准格式\n * `开始`和`结束`都时闭区间，是包含的\n * `步长`可以省略，默认是`1`\n * 注意`空白`\n \n`SEQ`会其定义处产生循环，但不产生树。对自身及子树有影响。\n\n详见 `demo/sql/tree/stbl.sql`\n \n ```mysql\n -- SEQ `tx_test_%02d[1,10]` tx_test_## #生成tx_test_01到tx_test_10，共10张表\n CREATE TABLE `tx_test_##` (\n   `id` BIGINT NOT NULL COMMENT 'id',\n   PRIMARY KEY (`id`)\n ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;\n```\n\n \n### 2.6. 表名匹配 TBL\n\n`TBL`根据当前库现有表进行匹配，只支持静态替换\n\n * `参数` 为正则，行为上等同于使用了`^$`，会对表名进行全匹配，不是部分。\n * 也可以使用`\\b`或 `^|$`进行界定\n\n规则同`SEQ`，详见 `demo/sql/tree/stbl.sql`。\n当在Out执行时，表名为当前数据库内所有表。\n\n```mysql\n -- TBL tx_outer_trknum.* `tx_outer_trknum` # 正则匹配表名。\n ALTER TABLE `tx_outer_trknum`\n   ADD COLUMN `label_file` VARCHAR(200) DEFAULT NULL COMMENT '面单文件位置' AFTER `trknum`;\n```\n\n### 2.7. 条件执行 RUN\n\n执行条件由`REF`或`ENV`定义，只对所在的语句有效，执行顺序与SQL行顺序有关。\n\n * `ONE` 以定义`占位`的节点为根，第一棵树时执行。\n * `FOR` 以定义`占位`的节点为根，每棵树时执行，等效于`REF`。\n * `END` 以定义`占位`的节点为根，最后一棵树执行。\n * `HAS` 表示`占位`变量有值时执行该树。`有值`指，\n    - 数值大于`0`\n    - 布尔`true`\n    - 非`NULL`\n    - 字符串非空（`“”`）\n    - 其他类型强转为字符串后非空。\n * `NOT` 与`HAS`相反。\n\n条件执行，有以下约定关系，\n\n * 多个`ONE|FOR|END`是`OR`关系。\n * `HAS|NOT`自身或与其他是`AND`关系。\n * `RUN` 可以确定多个父关系，且强于`REF`。\n * `RUN` 在树结束时执行，而`REF`在树中执行。\n * 数据点增序排列，权重为`REF`\u003c`ONE`\u003c`FOR`\u003c`END`，同级时算SQL位置。\n * 增加`ITSELF`占位，表示单独执行，没有任何依赖。\n\n条件执行的例子，参考 `demo/sql/tree/*.sql`\n\n### 2.8. 输出执行 OUT\n\n与条件执行 `RUN` 一样的定义，但不在源DB上执行，而是在目标DB上执行。\n\n注意，在有`定义Def`语句，如`REF`或`SEQ`等，不能使用`OUT`。\n因为一个`占位`在运行时存在多值，从而导致语义混乱或执行时麻烦。\n\n```mysql\n-- ENV DATE_FROM '2018-11-23 12:34:56'\n-- REF id 1234567890\nSELECT * FROM tx_parcel WHERE create_time = '2018-11-23 12:34:56';\n\n-- OUT FOR 1234567890\nREPLACE INTO tx_parcel VALUES(1234567890);\n```\n\n## 3. 测试手册\n\n使用工程中/demo/sql下的SQL进行所有功能的演示和测试。以下是准备工作，你必须都懂。\n注意，所有对数据库有写操作的命令，都需要增加`--agree`才会执行，否则仅输出预计结果。\n\n可以分步人工确认，也可以在工程目录中执行`demo/chk/manual.sh`自动确认。\n执行之前，需要增加执行权限，`chmod +x `，并设置好mysql连接信息。\n\n### 3.1. 获得执行文件\n\n```bash\n### 方法一：下载 ###\n# 直接下载release文件，直接到unzip步骤\n# https://github.com/trydofor/godbart/releases\n\n### 方法二：编译 ###\n\ngit clone https://github.com/trydofor/godbart.git\ncd godbart\n\n# 单平台编译\nGOOS=linux GOARCH=amd64 go build\n\n# 或全平台发布\nchmod +x build.sh\n./build.sh\n\nls -l release \n# 解压对应系统的执行文件，默认linux\nunzip release/godbart-linux-amd64.zip\n\n# 得到 godbart 程序\n```\n\n### 3.2. 修改数据源配置\n\n修改`godbart.toml`中的数据库用户名，密码，主机，端口等\n\n```bash\n# 你的用户是 yourname\nsed -i 's/trydofor:/yourname:/g' godbart.toml\n# 你的密码是 yourpass\nsed -i 's/:moilioncircle@/:yourpass@/g' godbart.toml\n# 你的ip是 127.0.0.9\nsed -i 's/(127.0.0.1:/(127.0.0.9:/g' godbart.toml\n# 你的端口是 13306\nsed -i 's/:3306)/:13306)/g' godbart.toml\n```\n\n### 3.3. 创建数据库\n\n```bash\n# 存在一个可使用的数据库，如一般都有的test\n./godbart exec \\\n -c godbart.toml \\\n -d lcl_test \\\n --agree \\\n demo/sql/diff/reset.sql\n \n # 或用 mysql命令，创新数据库\n cat demo/sql/diff/reset.sql \\\n | mysql -h127.0.0.1 \\\n -utrydofor \\\n -P3306 \\\n -p\"moilioncircle\"\n```\n\n### 3.4. Exec 执行脚本\n\n使用 exec 执行init中的脚本初始化 prd_main 数据库。\n\n```bash\n./godbart exec \\\n -c godbart.toml \\\n -d prd_main \\\n --agree \\\n demo/sql/init/\n```\n\n### 3.5. Revi 版本控制\n\n执行revi中的脚本使 prd_2018 更新到 2018111103 版本（只有结构没有数据）。\n因为prd_main 版本号比 2018111103 所以会跳过小版本的更新。\n\n```bash\n./godbart revi \\\n -c godbart.toml \\\n -d prd_main \\\n -d prd_2018 \\\n -r 2018111103 \\\n --agree \\\n demo/sql/revi/\n```\n\n### 3.7. Sync 结构同步\n\n复制prd_main表结构到dev_main\n\n```bash\n./godbart sync \\\n -c godbart.toml \\\n -s prd_main \\\n -d dev_main \\\n -t tbl,trg \\\n --agree\n \n# 同步小表（表结构版本）\n./godbart sync \\\n -c godbart.toml \\\n -s prd_main \\\n -d dev_main \\\n -t row \\\n --agree \\\n sys_schema_version\n```\n\n### 3.7. Diff 结构差异\n\n使用 diff 执行比较 prd_main 与 prd_2018, dev_main 差异。\n\n```bash\n# 查看 prd_main 与 dev_main的表名差异，sync后完全一致\n./godbart diff \\\n -c godbart.toml \\\n -s prd_main \\\n -d dev_main \\\n -t tbl,trg\n \n# 显示 tx_parcel表在prd_main上的创建语句\n./godbart show \\\n -c godbart.toml \\\n -s prd_main \\\n -t tbl,trg \\\n  tx_parcel \\\n| tee /tmp/ddl-tx_parcel-main.sql\n\n# 比较 tx_parcel 在prd_main和prd_2018详细差异\n./godbart diff \\\n -c godbart.toml \\\n -s prd_main \\\n -d prd_2018 \\\n -t tbl,trg \\\n  tx_parcel \\\n| tee /tmp/diff-tx_parcel-main-2018.sql\n```\n\n### 3.8. SqlX 静态分析\n\n静态分析 DataTree结构。\n\n```bash\n./godbart sqlx \\\n -c godbart.toml \\\n -e \"DATE_FROM=2018-01-01 00:00:00\" \\\n demo/sql/tree/tree.sql \\\n | tee /tmp/sqlx-tree.log\n```\n\n### 3.9. Tree 保存JSON\n\n把数据，保持成TSV（TAB分割），CSV（逗号分割）和JSON。\n此例中，有`脱引号`，`模式展开` 的组合。\n\n```bash\n# 危险动作，先保持日志查看。\n# 注意SQL以DEBUG输出，用TRACE会没有输出。\n./godbart tree \\\n -c godbart.toml \\\n -s prd_main \\\n -e \"DATE_FROM=2018-01-01 00:00:00\" \\\n demo/sql/tree/json.sql \\\n | tee /tmp/tree-main-json.log\n \n#分离和处理，去掉注释和结束符\ncat /tmp/tree-main-json.log \\\n| grep -E '^--' | grep -vE  \"^(-- )+(SRC|OUT)\" \\\n| sed -E 's/^-- |;$//g' \\\n| tee /tmp/tree-main-json.txt\n```\n### 3.10. Tree 迁移数据\n\n此例中，因为危险操作比较多，务必先分离脚本，人工确认。\n脚本99%可以执行，在二进制或转义字符转换字面量可能有遗漏。\n\n字面量不好描述的类型，可`--agree`，在程序中以动态数据来执行。\n\n```bash\n# 危险动作，先保持日志查看\n./godbart tree \\\n -c godbart.toml \\\n -s prd_main \\\n -d prd_2018 \\\n -e \"DATE_FROM=2018-01-01 00:00:00\" \\\n demo/sql/tree/tree.sql \\\n2\u003e\u00261| tee /tmp/tree-main-2018-all.log\n \n# 获得全部SQL\ncat /tmp/tree-main-2018-all.log \\\n| grep -vE '^[0-9]{4}/[0-9]{2}|^$' \\\n| tee /tmp/tree-main-2018-all.sql\n\n# 获得源库SQL\ncat /tmp/tree-main-2018-all.sql \\\n| grep -E '^[^-]|-- SRC' \\\n| tee /tmp/tree-main-2018-src.sql\n\n# 获得目标库SQL\ncat /tmp/tree-main-2018-all.sql \\\n| grep -E '^--' | cut -c 4- | grep -v  \"-- SRC\" \\\n| tee /tmp/tree-main-2018-out.sql\n\n# 直接执行\n./godbart tree \\\n -c godbart.toml \\\n -s prd_main \\\n -d prd_2018 \\\n -e \"DATE_FROM=2018-01-01 00:00:00\" \\\n --agree \\\n demo/sql/tree/tree.sql \\\n2\u003e\u00261| tee /tmp/tree-main-2018-all.log\n```\n\n## 4. 实用小技巧\n\n数据的日常处理，会有很多技巧，能提高数据意识，培养直觉。\n\n### 4.1. SHELL分离信息\n\n过程信息以log在stderr(`2`)输出。结果信息以stdout(`1`)输出，\n`1`和`2`是描述符，`\u003e`表重定向，`\u0026`表合并，组合起来可分离信息。\n\n * `\u003e main-2018-diff-out.log` 结果直接保存文件，控制台不输出。\n * `2\u003e main-2018-diff-err.log` 过程保存文件，控制台不输出。\n * `\u0026\u003e main-2018-diff-all.log` 全部保存文件，控制台不输出。\n * `| tee main-2018-diff-out.log` 结果保存文件，且控制台输出。\n * `2\u003e\u00261| tee \u003e(grep -vE '^[0-9]{4}' \u003e main-2018-diff-out.log)` 同上。\n * `2\u003e\u00261| tee main-2018-diff-all.log` 全部保存文件，且控制台输出。\n\n### 4.2. 按数据量排序\n\n查询所有表的记录数，对于单表300万的数据，进行按树分离或清理。\n\n```mysql\n-- 按记录数排序，同时查看磁盘空间\nSELECT \n    TABLE_NAME,\n    TABLE_ROWS,\n    FLOOR(DATA_LENGTH  / 1048576) AS DATA_M,\n    FLOOR(INDEX_LENGTH / 1048576) AS INDEX_M\nFROM\n    INFORMATION_SCHEMA.TABLES\nWHERE\n    TABLE_SCHEMA = 'godbart_prd_main'\nORDER BY \n    TABLE_ROWS DESC, \n    DATA_M DESC;\n```\n\n### 4.3. 调整分叉位置\n\n多分支的`REF`会生成多个分叉的节点，可以通过`FOR`和`END`调整。\n\n而依赖与多个条件的`WHERE`，可`JOIN`到同一个分叉SQL中。\n\n以 `./demo/sql/tree/fork.sql` 为例。\n\n### 4.4. REF的默认值\n\n当REF的SQL返回的0条记录时，以此为根的树就不会存在。\n我们可以通过以下的SQL来指定默认值，保证能返回1条记录。\n\n```mysql\n-- 通过 INSERT IGNORE 插入默认值\nINSERT IGNORE SYS_HOT_SEPARATION VALUES ('tx_parcel',0, NOW());\n\n-- 批量初始化\nINSERT IGNORE SYS_HOT_SEPARATION SELECT \n    TABLE_NAME,0,NOW()\nFROM\n    INFORMATION_SCHEMA.TABLES\nWHERE\n    TABLE_SCHEMA = 'godbart_prd_main';\n\n-- 通过 聚集函数与CASE WEN\nSELECT \n    CASE\n        WHEN MAX(CHECKED_ID) IS NULL THEN 0\n        ELSE MAX(CHECKED_ID)\n    END AS CHECKED_ID\nFROM\n    SYS_HOT_SEPARATION\nWHERE\n    TABLE_NAME = 'tx_parcel';\n```\n\n### 4.５. 全部同步\n\n可以使用 `sync -t row` 进行小表的数据同步，也可以使用 `tree`的以下脚本。\n这些脚本可以使用正则进行批量生成，参考`攻城狮朋友圈`正则分享。\n\n```mysql\n-- STR SRC-DB SRCDB\n-- VAR checked_id 'tx_sender.checked_id'\nselect checked_id from sys_hot_separation where table_name = 'tx_sender';\n\n-- REF max_id 'tx_sender.max_id'\nselect max(id) as max_id from tx_sender where id \u003e 'tx_sender.checked_id';\n\n-- OUT FOR 'tx_sender.max_id'\nreplace into tx_sender\n  select * from SRCDB.tx_sender\n  where id \u003e 'tx_sender.checked_id' and id \u003c= 'tx_sender.max_id';\n\n-- RUN FOR 'tx_sender.max_id'\nreplace into sys_hot_separation values ('tx_sender', 'tx_sender.max_id', now());\n```\n\n### 4.6. 如何对比迁移数据\n统计各表的数据变化，查看迁移效果\n```mysql\n-- 统计库数据\nSELECT \n    TABLE_SCHEMA,\n    SUM(TABLE_ROWS)\nFROM\n    INFORMATION_SCHEMA.TABLES\nWHERE\n    TABLE_SCHEMA like 'godbart_%'\nGROUP BY \n    TABLE_SCHEMA;\n\n-- 统计表数据\nSELECT \n    TABLE_NAME,\n    TABLE_ROWS\nFROM\n    INFORMATION_SCHEMA.TABLES\nWHERE\n    TABLE_SCHEMA = 'godbart_prd_main'\n    AND TABLE_ROWS \u003e 0\nORDER BY \n    TABLE_ROWS DESC;\n```\n\n### 4.7. 如何静态分析和运行时监控\n\n静态分析，第一步，要执行sqlx命令，分析树结构。第二部，不带agree参数在db上执行以下，看debug日志。\n运行时监控，使用控制端口，telnet连接过去，使用`stat|wait|tree`命令，还有本机日志。\n\n### 4.8. `tree`做版本管理（分表）\n\n除了`revi`，使用`tree`的`VAR和RUN FOR`也可以完成版本更新的。\n\n```mysql\n-- 单表 =========================\n-- VAR VER v2019010302\nSELECT MAX(version) as VER FROM sys_schema_version WHERE version = 2019010302;\n-- RUN NOT v2019010302\nALTER TABLE tx_parcel ADD CONSTRAINT uk_track_num UNIQUE (is_deleted, track_num);\n-- RUN NOT v2019010302\nREPLACE INTO sys_schema_version (version, created) VALUES(2019010302, NOW());\n\n-- 分表 =========================\n-- VAR VER v2019010302\nSELECT MAX(version) as VER FROM sys_schema_version WHERE version = 2019010302;\n-- RUN NOT v2019010302\n-- STR tbl `tx_parcel_#` 为分表更新\nSELECT tbl FROM (\n  SELECT 'tx_parcel_0' AS tbl  UNION ALL\n  SELECT 'tx_parcel_1' UNION ALL\n  SELECT 'tx_parcel_2' UNION ALL\n  SELECT 'tx_parcel_3') TMP;\n-- RUN NOT v2019010302\nALTER TABLE `tx_parcel_#` ADD CONSTRAINT uk_track_num UNIQUE (is_deleted, track_num);\n-- RUN NOT v2019010302\nREPLACE INTO sys_schema_version (version, created) VALUES(2019010302, NOW());\n\n-- 分表 =========================== 0.9.7+\n\n-- SEQ tx_parcel_%02d[1,10] tx_parcel_##create\nCREATE TABLE IF NOT EXISTS `tx_parcel_##create` like `tx_parcel`;\n-- RUN FOR tx_parcel_##create\nINSERT IGNORE `tx_parcel_##create` SELECT * FROM `tx_parcel` limit 1;\n-- OUT FOR tx_parcel_##create\nCREATE TABLE IF NOT EXISTS `tx_parcel_##create` like `tx_parcel`;\n\n\n-- TBL tx_parcel_\\d+ tx_parcel_##select\n-- REF id 'tx_parcel.id'  #提取 id，作为'tx_parcel.id'节点\n-- STR VAL[] 'tx_parcel.VALS'\nSELECT * FROM `tx_parcel_##select` limit 1;\n```\n\n## 5. 不想理你的问题\n\n* Q01：使用中发现了问题，出现了BUG怎么办？\n  - 有能力hack code的，就提交PR。\n  - 没能力的，提交 issue。\n  - 再不行的，就认命吧。\n\n* Q02：我SQL写错了，习惯性输入了`--agree`，结果数据丢了 :(\n  - 事后没有后悔药，不要轻易 agree。\n  - 执行前要确认，要两人确认，想好fallback计划。\n  - 一定写where false的条件安全SQL。\n  - 甚至写替换前语法错误的SQL。\n\n* Q03：`FOR`中只有`HAS`和`NOT`，会增加`\u003e`,`\u003c`或其他运算符么？\n  - 复杂的条件判断，可以由SQL语句产生，然后`REF|VAR`。\n  - 写那么复杂的SQL，不如去编程好了。\n  \n* Q04：数据树迁移的吞吐量/性能如何？\n  - 坏消息是吞吐量不太好，好消息是不占资源。\n  - 实测一棵4层100条SQL的数据树，同机同实例千万数据，每秒迁移10.87棵树。\n  - 速度依赖于sql索引，golang层面提升不大。\n  \n* Q05：输出信息太多了/太快了，看不清/来不及处理\n  - 使用`-l trace`调整信息输出级别。\n  - 用 shell的重定向分离信息流。\n  - 看文档，像吃药一样，看说明书，听医嘱。\n\n* Q06：SQL没有正常解析，报错了。\n  - 确认单个完整的SQL中间没有空行分开，结尾有分隔符。\n  - 确认一组SQL间，每个独立SQL有分隔符或空行分开。\n  - 发个issue，贴上SQL，应该时没见过的SQL。\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrydofor%2Fgodbart","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftrydofor%2Fgodbart","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrydofor%2Fgodbart/lists"}