多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

MySQL Utilities实战:存量环境下的高效运维工具集

MySQL Utilities实战:存量环境下的高效运维工具集 1. 为什么这个过气工具集还在我的工作台里前几天帮一家公司排查主从复制延迟登上去发现两个库的表结构因为开发手工执行了N次漏脚本的发布已经悄悄长出了100多处差异。我第一反应不是打开某个运维平台而是打开终端敲了条命令mysqldiff --server1admin10.0.0.10:3306 --server2admin10.0.0.11:3306 \ --difftypesql --changes-file/tmp/schema_diff.sql不到半分钟128处差异被清清楚楚列出来还生成了可以直接执行的变更脚本。这套被我反复使用的工具就是很多人已经不关注的官方工具集——MySQL Utilities。你可能觉得奇怪这玩意不是停止开发了网上对它的评价也两极分化。但我要说的是对于运维手里还捏着MySQL 5.7、甚至5.6存量环境的人来说它依然是效率极高的瑞士军刀。今天这篇就是我的实战手册把从安装、结构比对、数据导出导入到复制、高可用切换、权限克隆的高频用法过一次最后附上我这些年踩过的坑。1.1 手工运维的重复劳动有多坑先说我为什么对这套工具执念这么深。早些年我管理几十套MySQL实例每天干的活无非就是哪两个库结构不一样肉眼打开两个窗口比对字段新环境要搭从库手写CHANGE MASTER那一套要复制一个用户权限一行行SHOW GRANTS再手工回放。听起来都不难问题是量大以后全是重复劳动而且特别容易出错。比如结构比对你肉眼比对字段顺序、默认值、字符集字符串长一点眼睛直接看花。有一次我把varchar(255)看成varchar(256)上线第一天数据就溢出了。数据导出如果用mysqldump在多表关联、存储过程、触发器混合的场景下dump出来的文件要么顺序不对要么被外键约束卡住。权限复制就更不用提了一个账号十几个库的权限手抄加引号转义错一个字符就授权失败。MySQL Utilities的设计初衷就是把这些高频操作变成一句命令。你不用再去记那一长串SQL拼接逻辑也不用担心中间某一步漏了。虽然它现在的更新已经停滞但核心功能在5.7乃至8.0的兼容模式下依然可以工作而且在很多企业内部存量实例根本轮不到你用新工具那它就是你最顺手的搬砖工具。1.2 工具箱全景你会用到的组件MySQL Utilities不是一个单一命令而是一套Python脚本集合入口是mysqluc。装好以后你可以直接运行mysqluc进入它的命令行也可以像普通命令一样直接调每个工具。我用得最多的几个工具列个清单给你参考工具名作用我的使用频率mysqldiff比对两个数据库/两段SQL的对象差异生成变更脚本每周mysqldbexport导出库/表数据支持SQL和CSV格式每周mysqldbimport导入数据能处理依赖顺序每周mysqlserverclone从现有实例克隆出独立新实例每月mysqlreplicate快速配置主从复制每月mysqlfailover主库故障自动切换每季度演练mysqluserclone克隆MySQL用户权限随时mysqlindexcheck检查表中索引的使用情况每季度mysqlauditgrep过滤解析审计日志偶尔这些工具的共同点是参数风格统一、有--dry-run预览模式、输出结果可解析。你在mysqluc里输入一个工具名再敲--help基本就能把每个参数看明白。别被它的英文文档劝退实际用起来比文档简单得多。1.3 安装与初始化从下载到mysqluc能用很多人卡在第一步就是因为这个工具是基于Python 2的。早先版本需要Python 2.6/2.7系统如果已经用Python 3直接跑多半会报语法错误。我目前的推荐方案是在专用管理机上用源码包安装不跟业务服务器掺和。以1.6.5版本为例安装流程大概是这样的# 下载并解压源码包 tar zxf mysql-utilities-1.6.5.tar.gz cd mysql-utilities-1.6.5 # 用python2执行安装管理机可以保留一个python2环境 python2 setup.py install如果你的系统把python指向了3那就要明确调用python2。装完以后验证一下:mysqluc --version如果显示mysqluc 1.6.5这样的版本信息就说明装好了。还有一个常见的坑它默认依赖mysql-connector-python。很多人在安装时没有先装这个库导致运行任何工具都报ImportError: No module named mysql.connector。解决办法一样用Python 2环境装对应版本的connector然后确认在PYTHONPATH里能找到。另外有些朋友习惯配合图形管理工具一起用比如下载安装dbx数据库管理工具来做连接配置和任务监控。我的建议是dbx这类工具做可视化操作没问题安装包从官方渠道拉连接MySQL时指定--socket或者TCP端口就行。但命令行场景下MySQL Utilities的本事是dbx没法替你完成的尤其是差异比对和复制配置。所以两者目前在我这里是互补状态不冲突。2. 结构比对与数据导出导入最稳的三件套工具装好以后先掌握最常用的三件套。这三个工具能覆盖日常变更上线前的大部分检查工作。2.1 mysqldiff操作步骤与输出解读mysqldiff是我打开率最高的命令。它的核心功能是比较两个连接上的对象定义差异包括表结构、视图、存储过程、函数、触发器等。基本命令长这样mysqldiff --server1admin:pass10.0.0.10:3306/db1 \ --server2admin:pass10.0.0.11:3306/db2 \ --difftypesql --changes-file/tmp/db_diff.sql参数说明里面--server1和--server2除了写库名以外还支持db1.tab1这种形式用来比对单表。--difftype支持sql、unified、differ几种格式。我强烈建议用sql因为它输出的差量可以直接放到生产库执行。--changes-file会把差异SQL写进文件方便你在执行前人工Review。这里我特别提醒一点默认情况下它只比较对象定义不会比较索引的可见性、统计信息这些运行时属性。如果你要连索引一起比加一个--show-options参数。看一次实际输出# Comparing db1 to db2 [PASS] # Definition: Table db1.orders Reversed遇到[FAIL]的文件后面会跟具体的差异片段。有一次它告诉我某个字段的AUTO_INCREMENT属性不一致我顺着它的输出去查果然发现从库的表是用我三个月前的老备份恢复的差了一个自增计数设置。这种细节靠肉眼几乎查不出来。还有个小技巧--changes-file生成的是完整ALTER语句但它不会帮你处理依赖顺序。所以拿到变更脚本后我习惯先用dry-run模式跑一遍确认没语法错误再执行mysqldiff --server1admin:pass10.0.0.10:3306/db1 \ --server2admin:pass10.0.0.11:3306/db2 \ --difftypesql --dry-run2.2 mysqldbexport导出的正确姿势如果说mysqldump是重武器那mysqldbexport就更像给你精细控制的手术刀。它可以按库、按表导出也可以只导出某些对象的数据而不导出表结构。我一般这么用mysqldbexport --serveradmin:pass10.0.0.10:3306/db1 \ --formatsql --exportboth --skip-gtid --output-file/tmp/db1_export.sql--formatsql表示输出SQL文件如果你想拿去做数据分析可以改成--formatcsv。--export有多个值both表示结构加数据structure-only是只要结构>mysqldbimport --serveradmin:pass10.0.0.12:3306 \ --formatsql --import-file/tmp/db1_export.sql--formatsql要和导出时一致。--import-file不指定的话它默认读stdin所以你可以用管道mysqldbexport ... --output-file- | mysqldbimport ... --formatsql这里有个关键参数是--force默认情况下如果目标库已有同名的表它不会覆盖而是报错。想要覆盖导入加--force。但--force使用时要万分小心它会直接把目标表DROP掉重建。我的习惯是先备份再导入到临时库验证一次最后才切正式库。权限陷阱也是老生长谈。目标库的账号如果只有INSERT、SELECT权限导入时建表语句就会被拒绝。所以导入账号最少需要CREATE、DROP、INSERT、ALTER权限。你要是用root导入问题不大但生产环境不建议直接暴露root串在命令行里。可以用--login-path来管理密码或者设置好~/.mylogin.cnf这样命令里不出现明文密码日志里也不容易泄露。3. 复制与高可用mysqlreplicate 和 mysqlfailover 的实战经验搭建主从复制过去要手动处理七八个步骤创建复制账号、拷贝数据、记录binlog位置、配置server-id、启动slave……一步错后面全乱。3.1 用 mysqlreplicate 搭建主从复制如果你已经有了一份全新的从库数据mysqlreplicate能做的就是把那些手工步骤都省了。命令大概是这样的mysqlreplicate --masteradmin:pass10.0.0.10:3306 \ --slaveadmin:pass10.0.0.11:3306 \ --rpl-userrepl:secret --start-slave执行过程中它会在主库创建复制账号在从库设置CHANGE MASTER TO并自动记录当前的binlog坐标。加--start-slave的话它会直接启动复制线程。实测下来只要网络连通、server-id不冲突基本一次成功。这里我要强调一个隐藏条件它假设从库的数据已经跟主库一致。如果不一致复制会在执行过程中报错。所以我的标准流程是三层检查用mysqldbexport或者别的备份工具把主库数据恢复到从库。用mysqldiff核对关键表结构。最后才跑mysqlreplicate。在执行前建议先看一遍--dry-run的输出它会列出将要执行的SQL确认没有意外再正式执行。如果你要快速搭建从库还有一个小兄弟工具叫mysqlserverclone。它可以在一个MySQL实例上克隆出一个新的独立实例端口、数据目录、server-id都可以指定mysqlserverclone --serveradmin:pass10.0.0.10:3306 \ --new-data/data/mysql3307 --new-port3307 \ --new-id3307 --mysqld-options--gtid_modeON这在日常开测试环境时简直是救命工具。以前我建一套隔离测试库要先装了MySQL再手动配my.cnf折腾半小时。现在一条命令两分钟得到一个端口不同、server-id不同的独立实例。3.2 mysqlfailover 的配置与切换演练再往上一个层次就是高可用切换。mysqlfailover可以在主库故障时自动把某个从库提升为新主库。它支持基于GTID或者binlog位置的复制拓扑。基本配置是先在主库启动一个持续监控进程mysqlfailover --masteradmin:pass10.0.0.10:3306 \ --discover-slaves-loginadmin:pass \ --failover-modeauto --log/var/log/mysqlfailover.log--discover-slaves-login告诉它自动发现从库--failover-modeauto表示故障时自动切换。这个进程要一直跑着所以最好用systemd或supervisor守护。切换策略一般用--candidateslave2来指定一个数据最新的候选从库--ping-interval控制心跳频率比如--ping-interval3就是每3秒探活一次。如果主库5秒内没有响应它就开始选举。选举标准不是随机的它会比较从库的日志位置优先选最接近主库的那个。如果配置了--candidate则优先选候选没有候选选日志最新的从库。切换完成后它会自动把其他从库重新指向新主库。如果担心自动切换有风险你可以先把--failover-mode改成manual。这样它只负责监控和告警需要你手工确认后才切换。我第一次在生产环境用它的安全方式就是manual先观察它产生告警是否准确连续演练几次后才敢开auto。3.3 高可用切换的边界与我的避坑提醒我要泼一盆冷水mysqlfailover又不是万能的。首先是脑裂问题。这个工具本身不做防脑裂处理。主库网络闪断但它自身还活着新的主库已经被选举出来。如果业务流量还往老主库写两个库都会写入新数据后续数据合并就非常麻烦。我的规避手段是在网络层面做隔离比如在发生切换时立刻用防火墙规则把老主库的对外流量禁用或者一切用VIP漂移。VIP配合mysqlfailover切换时VIP指向新主库才能相对可靠。其次是日志清理和binlog事件。如果从库落后主库太多日志已经被purge那么这个从库就没有竞选资格。所以高可用环境里binlog的expire_logs_days配置不能太短。我一般设置7天同时搭配定时全备。最后是应用侧连接池。就算MySQL层面切换成功应用的长连接还捏着老主库的IP。如果你不用VIP应用侧必须实现故障重连。这一点在演练前一定要和开发团队确认清楚不要等到真故障了才想起来改连接串。4. 日常巡检、压测与权限治理不止 DBA 能用MySQL Utilities的价值不只在紧急排障日常巡检和上线前评估同样能帮你省下大把时间。4.1 mysqlcheck / mysqlindexcheck 的巡检组合mysqlcheck是MySQL自带的表维护工具可以和mysqlindexcheck搭配来做巡检。但我要重点说mysqlindexcheck因为它能检查冗余索引和未使用索引这属于很多DBA容易忽略的隐性浪费。用法很简单mysqlindexcheck --serveradmin:pass10.0.0.10:3306/db1 \ --show-indexes --show-drops它会列出每个表的索引详情并给出建议删除的重复索引。比如一个字段a上既有单列索引又有(a,b)复合索引它会把单列索引标成冗余。删掉冗余索引之后写入性能能明显提升。这个工具只分析索引定义不会真的去执行DROP INDEX所以你可以放心跑。我一般每季度跑一次把输出交给开发确认后再决定要不要删。实测在我们的订单库中一次巡检发现三个冗余索引删除后写入响应时间平均降了12%。不过这个数据因环境而异但方向是对的。4.2 压测组合mysqlslap 与 mysqladmin 配合很多人不知道mysqlslap就是MySQL自带的一个压力测试工具。它模拟客户端负载帮你快速确认一个库能扛住多大的并发。最简单的用法mysqlslap --concurrency50 --iterations3 \ --number-of-queries1000 --create-schematest \ --querySELECT * FROM orders WHERE user_id12345 \ --host10.0.0.10 --useradmin --password***它会自动造一个测试表压测完后自动清理。压测的时候我同时在另一个终端跑mysqladmin status和mysqladmin extended-status来观测线程数、请求队列长度。比如mysqladmin --host10.0.0.10 --useradmin -p status输出里的Threads、Questions、Connections是重点指标。如果线程数快速上涨但Questions涨不动说明有锁等待。这时候配合SHOW ENGINE INNODB STATUS\G看锁信息基本能定位到瓶颈。需要提醒的是mysqlslap的压测结果只能做相对参考。它产生的负载和真实业务差得很远真实业务有读写混合、有随机分布、有事务提交频率所以要拿它来定容量上限是不严谨的。我更推荐用它来做上线前冒烟比如确认新索引有没有带来明显副作用或者新从库能否扛住基础流量。4.3 mysqluserclone权限克隆的正确用法最后这个工具我做强推mysqluserclone。它可以一键把一个用户的所有权限克隆到另一个新用户身上。比如要创建一个和app_rw权限完全一样的账号app_rw_backupmysqluserclone --serveradmin:pass10.0.0.10:3306 \ app_rw app_rw_backup它会把原用户的全局权限、库级权限、表级权限都复制过去。相比手工SHOW GRANTS再重放至少节省了90%的时间而且不容易漏掉授权范围。要注意的一点这个工具复制不了密码本身的功能。它能克隆权限结构但不会把原用户的密码hash复制过去。你需要为新用户单独设置密码ALTER USER app_rw_backup% IDENTIFIED BY 新密码;这个特性反而让我觉得安全因为新账号密码必然要经过一个人工设置的过程不会出现两个账号用同一套密码的隐患。还有一个小场景应用从一台库迁到另一台库应用账号也要跟着迁。你可以先用mysqldbexport把整个权限相关的库导出再在目标端导入。但账号密码的密文在mysql.user表里导出时有时候会变动所以我更倾向于直接用mysqluserclone在主库克隆再在目标端创建相同的账号。效率实测下来比导入导出干净得多。5. 我踩过的坑和现在的工作流工具再好只讲命令不讲坑等于没讲。这部分我把个人踩坑经历整理出来希望能给你省下几晚加班。5.1 常见报错对照表下面的表是按我实际的经历整理出来的附带解决思路。报错/现象原因解决办法ImportError: No module named mysql.connector缺少mysql-connector-python用Python2环境安装connector确认PYTHONPATHUnable to retrieve server information连接账号权限不足给账号加SELECT、RELOAD等权限或使用rootERROR 2013 (HY000): Lost connection连接超时或keepalive断开执行前加--connect-timeout120网络不稳时重试mysqldiff提示对象不存在server参数写错库名或者没加--pattern检查--server1db的库名表级别比对要写db.table导入时报外键约束错误表导入顺序不对导入前临时SET FOREIGN_KEY_CHECKS0或者按依赖排序切换后应用连不上应用长连接没有重连机制使用VIP或者在应用侧启用连接探活复制的从库掉线无法自动续传网络闪断导致IO线程停止START SLAVE;查看Last_IO_Errno修复网络后重新START5.2 我的生产工作流现在我的常规变更都是这样走的第一步变更前做结构比对。只要涉及表结构变更先跑mysqldiff生成变更脚本作为评审依据。第二步变更后做数据校验。涉及数据迁移就用mysqldbexport mysqldbimport并在迁移前后分别做行数统计。行数统计我直接用SQL几条简单的SELECT COUNT(*)就够了。第三步重要实例的复制变化用mysqlreplicate重新配置。配置后观察SHOW SLAVE STATUS确认没有报错。第四步每季度固定做一次mysqlindexcheck巡检和mysqlslap压测。这两个命令我会写进cronjob输出保存到文件方便历史对比。第五步高可用每周做一次切换演练。不是真的把主库停机而是用--failover-modemanual触发一次计划内切换观察日志和VIP漂移情况。这比出问题后再临时演练可靠得多。5.3 维护现状与替代方案我不得不承认MySQL Utilities的官方维护确实停止了最后的1.6.5版本也是多年前的产物。它和新版MySQL 8.0的部分特性存在兼容性风险尤其是GTID、权限模型、密码插件等深度集成的地方。所以如果你管理的全是MySQL 8.0环境我会建议用Oracle官方推广的MySQL Shell和它的AdminAPI来替代一部分功能特别是复制和高可用场景。但如果你和我一样手头还有相当比例的5.7实例MySQL Utilities依然是成本最低、最省事的方案。它不需要额外装agent不需要升级业务侧代码命令行一敲就能用。另外一个趋势是使用MySQL Enterprise Monitor或开源的Prometheusmysqld_exporter做监控告警工具集则逐渐退居二线变成变更辅助工具。这其实是个好分工监控交给新体系变更操作继续用这些老伙计。毕竟它们足够小、足够快没有花里胡哨的依赖。说实话这么多年过去了MySQL Utilities已经不是我每天必开的工具但每当我需要快速做一个结构比对或者临时搭一个从库测试环境第一个想到的还是它。这也解释了为什么它还在我的工作台里占着一席之地。工具的价值不在于新不新而在于能不能稳定解决你手头的问题。与其追逐每个新工具不如把手上的这组命令吃透在关键时刻能顶上去就够了。
返回列表