
我们继续“重走JAVA路”这个系列。上一节把开发环境的底子打牢了这一节聊聊 Java 后端绕不开的 MySQL。说句实在话现在的后端项目可能不用 Redis可能不用消息队列但绝大多数都会用 MySQL 做持久化存储。不管是搞微服务还是单体应用数据库永远是最底层的那块基石。这一期内容适合刚入门 Java 的新人也适合那些用了很久 MySQL 但一直靠“复制粘贴”写 SQL 的同学。我会把建库建表、索引设计、事务隔离、慢查询排查这些高频场景串起来重点讲讲我在实际项目里踩过的坑和验证过有效的做法。1. 先把定位搞清楚MySQL 在 Java 技术栈里到底承担什么1.1 关系型数据库的核心价值不只是“存数据”很多人一听到 MySQL 就觉得它是“存数据的”这个说法没错但只说对了一半。存数据谁都会文件系统也能存Redis 也能存但 MySQL 之所以能在 Java 技术栈里占据核心位置是因为它提供了两样东西数据之间的关系约束和基于数据的高效查询。举个例子。一个订单系统里用户表、订单表、商品表之间是有明确关系的一个用户可以有多个订单一个订单包含多个商品。这种关系如果靠人工维护成本高得离谱。MySQL 通过外键约束、事务机制和标准化的 SQL 语言把这种“关系”变成了数据库本身的职责。Java 应用层只需要关心业务逻辑至于数据完整性和一致性交给数据库去保证。从 Java 开发者的视角看MySQL 就是一个非常稳定、可靠、可预期的“数据底座”。我们写的业务代码可以随时重启但数据必须有处安放而且怎么存、怎么查、怎么保证不丢这些就是需要认真研究的重点。1.2 Java 应用与 MySQL 之间的那条“连接通道”Java 程序操作 MySQL靠的是 JDBC 这套标准接口。驱动负责把 Java 的调用翻译成 MySQL 能理解的协议连接池则负责管理连接的生命周期。很多刚入行的同学容易忽略一件事每次请求都新建连接是大忌。新建连接要经过 TCP 三次握手、MySQL 权限校验、上下文创建这个过程开销不小。所以生产环境里几乎都用连接池——比较常见的有 HikariCP、Druid。我在一个高并发的订单系统里验证过用默认配置的 HikariCP 和完全裸写 JDBC 相比接口的数据库耗时能差出好几倍。连接池的本质是“复用”把建立好的连接放在池子里循环使用避免了重复建连的损耗。配置连接池时重点盯几个参数maximumPoolSize最大连接数、minimumIdle最小空闲数、connectionTimeout获取连接的超时时间、maxLifetime连接最大存活时间。这些参数不能照抄默认值要根据业务的实际并发量来调整。注意连接池不是越大越好。连接数太多会导致数据库线程上下文切换频繁反而拖慢性能。一般经验是先按CPU核心数 × 2 1估算单实例连接数再结合压测结果调整。我曾经见过一个项目把连接池调到 200结果数据库瞬间被打爆其实那个业务场景 30 个连接就绰绰有余。2. 建库建表看着简单其实最容易被低估2.1 字符集和排序规则一门之差的坑在 Java 项目里建库建表第一个隐藏的关键点就是字符集。MySQL 的默认字符集在不同版本里不一样老版本默认latin1新版本默认utf8mb4。很多人习惯在建库时只写DEFAULT CHARSETutf8这在 5.7 及以下的版本里会给你埋下一个大雷因为这里的utf8其实不是真正的 UTF-8它最多只能存 3 个字节。Java 里常用的 Emoji 表情是 4 字节的如果存到utf8字段里直接报错Incorrect string value。更严重的是一旦数据已经用utf8存进去了再想升级成utf8mb4就得做全表转换线上变更成本极高。所以建库时老老实实用utf8mb4别再让 “utf8” 这个坑延续了。排序规则我推荐用utf8mb4_general_ci或者新版本默认的utf8mb4_0900_ai_ci。ci表示大小写不敏感比较字符串时会忽略大小写。如果业务上必须区分大小写就要用cs后缀的排序规则或者查询时显式加上BINARY关键字。注意排序规则还影响索引的排序方式选错会导致某些范围查询走不了索引。2.2 引擎选型InnoDB 几乎是唯一解MySQL 支持的存储引擎不止一种但在生产环境里我基本只用 InnoDB。原因很简单它支持事务、支持行级锁、支持崩溃恢复还有聚簇索引带来的高性能主键查询。MyISAM 虽然查询快、磁盘占用小但它不支持事务也没有崩溃恢复能力一旦数据库异常宕机表损坏的风险非常高。在建表语句里主键设计是对性能影响最深远的一个选择。我强烈推荐用自增整数主键或者雪花算法生成的分布式 ID不要用 UUID 字符串做主键。原因是 InnoDB 的聚簇索引按照主键顺序物理排列数据自增主键可以保证新记录插入到已有数据的尾部避免页分裂带来的随机 IO。UUID 是随机字符串插入时会造成大量页分裂和碎片时间久了表空间膨胀得厉害查询性能也跟着下滑。CREATE TABLE user_account ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, user_name VARCHAR(64) NOT NULL COMMENT 用户名, email VARCHAR(128) DEFAULT NULL COMMENT 邮箱, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_user_name (user_name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_general_ci COMMENT用户账户表;这块还有个细节值得留意ON UPDATE CURRENT_TIMESTAMP这个写法很实用每次更新记录时自动刷新updated_at字段省去了在 Java 代码里手动维护更新时间的麻烦。3. 索引设计这不只是“给查询加速”那么简单3.1 B树到底强在哪里MySQL 的索引默认是用 B树实现的。新手问得最多的问题就是“为什么不用哈希索引查一条数据不是更快吗”哈希索引确实在做等值查询时速度极快复杂度是 O(1)但它对范围查询无能为力。B树的数据结构让叶子节点形成了有序链表可以高效地做范围扫描比如WHERE age 18 AND age 30这种条件。另外B树的非叶子节点只存索引键值和指针不存完整数据所以单层节点能容纳的键值数量更多整棵树的高度可以控制在很小的范围。一个 500 万行的表走主键索引查询时B树高度也就是 3 层左右意味着最多 3 次磁盘 IO 就能定位到目标记录。这种设计对磁盘存储介质非常友好因为磁盘随机读取的代价很高层数越少读取次数越少性能自然越好。3.2 聚簇索引、二级索引和回表理解之后就能优化InnoDB 的每张表只有一个聚簇索引它决定了表的物理存储顺序。如果没有定义主键InnoDB 会选用第一个唯一索引作为聚簇索引如果还是没有就会生成一个隐藏的主键。聚簇索引的叶子节点直接存整行数据所以通过主键查询时一次性就能拿到所有字段。二级索引也叫非聚簇索引的叶子节点存的是主键值不是整行数据。所以走二级索引查询时先找到主键值再回聚簇索引里查一遍完整数据这个动作叫“回表”。理解“回表”是优化 SQL 的核心因为很多 SQL 慢的根源就是回表次数太多。举一个我在实际项目里遇到的场景SELECT * FROM order_record WHERE user_id 10086 ORDER BY created_at DESC LIMIT 10;表里有联合索引(user_id, created_at)当时查询速度很慢。用EXPLAIN一看发现走了索引但Extra列里出现了Using filesort。原因在于查询条件里有WHERE user_id 10086还要按created_at排序联合索引确实是按(user_id, created_at)排的看起来应该能避免排序。但为什么还是文件排序因为最终要返回SELECT *查询优化器评估后发现回表成本高不如先根据索引索引拿主键再回表排序所以最终选择了文件排序。解决这个问题的思路有两个方向。第一改成覆盖索引把查询字段限制在索引列内第二优化查询逻辑尽量降低回表数据量。实际项目里我用的方案是缩小返回字段的范围只返回业务必需的字段而不是无脑SELECT *同时把分页逻辑从深层分页改成基于游标的分页查询速度提升非常明显。3.3 最左前缀原则联合索引的“使用手册”联合索引的匹配遵循最左前缀原则。索引(a, b, c)实际上会先按a排序a相同时再按b排序b再相同时按c排序。所以查询条件里只用b或者只用b和c时这个索引是用不上的。这个原则在职场面试验里经常被考实际工作中也特别重要。设计联合索引时得把等值条件字段放最左把范围查询字段放右侧。比如WHERE status 1 AND category_id IN (2, 3) AND create_date 2024-01-01我会设计成(status, category_id, create_date)因为IN也算等值匹配create_date的范围判断放在最右边。实操心得联合索引列的顺序越靠左区分度低没关系关键是联合索引本身让等值查询从一开始就在一个较小的范围里扫描。很多 SQL 调优书里强调“区分度高的字段放左边”这在单一字段索引上是正确的但在联合索引中等值字段优先、范围字段置后往往比区分度更值得优先考虑。4. 事务与隔离级别并发环境下数据不乱的根4.1 ACID 并非抽象概念而是落到每个操作里的铁律Java 后端开发天天说事务但很多同学对事务的理解停留在“要么全部成功、要么全部失败”这句话上。MySQL 事务的 ACID 特性要落到实处来看。原子性靠 undo log 实现事务执行过程中如果出错可以通过 undo log 回滚到事务开始前的状态。一致性靠代码和约束共同保证比如转账场景里转出和转入的总金额不能变这不仅是数据库约束更是业务逻辑约束。隔离性靠锁和 MVCC 实现让并发事务之间互不干扰。持久性靠 redo log 实现事务提交后即使数据库宕机重启之后也能通过 redo log 重放保证数据不丢。在 Java 项目里我自己用事务的准则很简单事务越小越好。不要在一个事务里执行耗时的远程调用不要在一个事务里做大量批量更新。因为事务持有数据库连接和锁的时间越长其他请求等待的几率越大系统吞吐量就越低。4.2 四种隔离级别业务里真正常用的其实只有两种MySQL 默认的隔离级别是 REPEATABLE READ而 Oracle 默认是 READ COMMITTED。为什么 MySQL 要默认可重复读一个历史原因是早期 MySQL 的主从复制只支持该级别下的逻辑复制后来虽然支持了 READ COMMITTED但默认值一直没有改。隔离级别从低到高分别是READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE。READ UNCOMMITTED 能读到其他事务未提交的数据也就是“脏读”实际项目里几乎没人用。SERIALIZABLE 通过强制所有事务串行执行来保证绝对隔离并发能力极低我也只在极少数对一致性要求极高的场景里用过。Java 项目里最常用的是 READ COMMITTED 和 REPEATABLE READ。READ COMMITTED 只能读到已提交的数据能避免脏读但无法避免不可重复读——同一个事务里查询两次结果可能不同。REPEATABLE READ 利用 MVCC 的快照机制让同一个事务里的普通SELECT读到一致的数据快照。有一种非常常见的误解可重复读只影响读一致性其实它和锁机制配合时会产生“间隙锁”。间隙锁会锁住一个范围导致某些场景出现死锁或锁等待。所以如果你的业务对“可重复读”没有严格要求我建议在初始化配置里直接改成 READ COMMITTED能减少不少死锁困扰。SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;4.3 行锁、间隙锁和 Next-Key Lock到底是锁了行还是锁了范围InnoDB 在 REPEATABLE READ 级别下为了防止幻读使用了 Next-Key Lock。Next-Key Lock 是行锁和间隙锁的组合既锁住了记录本身也锁住了记录前后的间隙。把一个范围内的所有插入操作都卡住保证查询结果不会被“幻影记录”影响。这个机制在并发写入场景里影响很大。比如一个批量插入任务和另一个事务的范围更新任务同时运行很容易出现死锁。死锁出现后MySQL 的检测机制会回滚代价较小的事务然后抛出一个异常Deadlock found when trying to get lock。遇到死锁时第一反应不是写代码“重试”就好而是要分析死锁的真正原因。最常见的几个原因包括多个事务按不同顺序访问同一组资源、间隙锁之间互相等待、事务隔离级别设置得不合理。排查死锁的手法是执行SHOW ENGINE INNODB STATUS;查看LATEST DETECTED DEADLOCK部分里面会明确显示两个事务各自的持锁和等待信息。给一个我自己的经验所有批量更新操作尽量按同一顺序处理比如按主键升序更新。这样所有事务按同样的顺序获取锁死锁概率会大幅下降。5. 慢查询排查EXPLAIN 是 SQL 调优的起点5.1 慢查询日志怎么开怎么用排查性能问题首先要找到“慢”的 SQL 在哪里。MySQL 提供了慢查询日志机制可以把执行时间超过阈值的 SQL 记录下来。在配置里设置slow_query_log ON slow_query_log_file /data/mysql/slow-query.log long_query_time 1long_query_time 1表示执行时间超过 1 秒的 SQL 会被记录。开发环境可以放开到 0.1 秒方便抓一些被索引问题拖慢的查询。生产环境建议从 1 秒开始逐步调低阈值避免日志量过大影响磁盘。拿到慢查询日志后别急着改 SQL先找出日志里执行次数多、单次耗时长、扫描行数高的语句。工具可以用 MySQL 自带的mysqldumpslow也可以直接手动分析日志文件根据业务场景去筛选。5.2 看懂 EXPLAIN 输出里的几个关键指标EXPLAIN 是 MySQL 提供的查询执行计划解析工具。在慢查询 SQL 前加一个EXPLAIN关键字就能看到这条 SQL 会怎样执行。核心关注这几列type访问类型。从好到差依次是system、const、eq_ref、ref、range、index、ALL。如果看到ALL说明是全表扫描这是最需要警惕的。key实际用到的索引名。如果为NULL说明没走索引。rows预估扫描行数。数值越大说明执行代价越高。Extra额外信息。出现Using filesort和Using temporary是最常见的两个性能警告。我见过最典型的案例是一条列表页查询 SQLSELECT * FROM payment_record WHERE status 1 AND pay_type 2 ORDER BY create_time DESC LIMIT 20;EXPLAIN结果显示type是ALLrows高达 20 万。状态字段区分度低单独建索引可能也走不上此时更合理的方案是设计联合索引(status, pay_type, create_time)让排序字段落在索引中既缩小范围又避免文件排序。改造后type变成了rangerows降到了几百。5.3 几条慢 SQL 的真实排查记录我处理过一个报表统计的场景查询一个月内的订单总额和订单数。原始 SQL 长这样SELECT DATE(create_time) AS day, COUNT(*), SUM(amount) FROM order_master WHERE create_time 2024-06-01 00:00:00 AND create_time 2024-07-01 00:00:00 GROUP BY DATE(create_time);因为对create_time用了DATE()函数导致create_time上的索引失效全表扫描。优化方案是把日期函数调整成对常量字段的操作改成SELECT DATE_FORMAT(create_time, %Y-%m-%d) ... WHERE create_time 2024-06-01 00:00:00 ...不对关键在于DATE(create_time)的写法让索引失效真正的优化思路是保留索引列不被函数修饰写成范围条件。SQL 可以保持WHERE create_time ... AND create_time ...然后在 Java 里按天分组或者用GROUP BY DATE_FORMAT(create_time, %Y-%m-%d)逢查询量不大时也可以接受。但更极端的场景我会直接建汇总表定时任务按天累计查询只读汇总表效果立竿见影。6. 主从复制与高可用多一台机器不只是备份6.1 主从复制的核心原理与配置Java 项目上线之后单一 MySQL 实例很难扛住高并发写入和读取的双重压力所以主从复制几乎是生产环境的标配。MySQL 主从复制的核心机制是主库把所有的数据变更记录到二进制日志binlog中从库通过 IO 线程拉取 binlog 写入自己的中继日志再由 SQL 线程重放中继日志最终在主从之间实现数据同步。配置主从复制的大致步骤是在主库开启log-binmysql-bin设置唯一的server-id在从库设置另一个server-id然后执行CHANGE MASTER TO指定主库地址和日志位点最后START SLAVE;。配置完后用SHOW SLAVE STATUS;检查Slave_IO_Running和Slave_SQL_Running是否都是Yes。这个环节最容易忽略的是初始数据的一致性。如果你直接把一张运行了很久的主库挂载到从库从库只同步从配置时刻开始的新增数据之前的历史数据就缺失了。正确的做法是先通过mysqldump或物理备份把主库的数据全量导入从库记录当时的 binlog 位点再从该位点开始同步这样才能保证数据完整。6.2 读写分离的价值与业务取舍主从配上之后Java 应用层要做读写分离写操作走主库读操作走从库。常见实现方案是在连接层做路由如使用 ShardingSphere 或 MyCat或者直接在 Java 代码里配置多个数据源通过切面或上下文选择不同的数据源。读写分离带来的核心收益是把查询压力从主库上剥离主库专心处理写事务从库横向扩展承担读流量。但代价是存在主从延迟也就是说写入主库的数据需要一点时间才能被从库读到。强一致性的业务场景不适合读写分离比如支付、库存扣减这类操作必须读主库。而那些对延迟不敏感的场景比如文章列表、通知消息、历史记录查询可以放心走从库。我自己做项目时有一条粗规矩没有特殊必要默认都走主库读。只有像日志查询、报表展示、用户画像这种显然能接受秒级延迟的读接口才会配置走从库。这样避免了很多隐藏的一致性问题。6.3 数据备份平时没用关键时刻救命现在有主从复制了是不是就不用再做备份了这是新手很容易产生的错误认知。主从复制解决的是读扩展和单点故障问题但如果有人误操作执行了DELETE FROM table_a而没有加 WHERE 条件这条删除会同时通过 binlog 同步到从库瞬间主从数据都丢了。所以备份必须独立于主从机制之外。常用的备份工具是mysqldump逻辑备份适合中小规模数据备份命令示例mysqldump -h127.0.0.1 -uroot -p --single-transaction --master-data2 -A /backup/full_backup_$(date %F).sql--single-transaction能够在不锁表的情况下取得一致性快照对 InnoDB 表特别重要。--master-data2会在备份文件里记录当时主库的 binlog 位点方便后续做恢复和增量同步。恢复的时候用mysql full_backup.sql导入即可。但这里有个必须提前演练的环节你至少要完整地跑通过一次备份和恢复流程。我见过一个团队备份每周都做但真到宕机恢复那天才发现备份文件因为磁盘问题已经损坏恢复流程不熟练连续折腾了好几个小时。备份这件事就像灭火器必须保证它随时可用。7. 实际项目中常见的问题与排查速查表7.1 连接池相关问题的典型症状Java 项目里连接池相关的问题最常见的是两类连接超时和连接泄漏。连接超时通常表现为Connection is not available, request timed out。这表示连接池里已经拿不到可用连接了。可能是连接池最大连接数设置太小也可能是有些请求获取连接后没有正确释放。排查思路是先看当时的接口 QPS 和数据库活跃连接数再用SHOW PROCESSLIST看看有没有大量Sleep状态的连接。连接泄漏是个更隐蔽的问题。我的排查经验是开启连接池的泄漏检测能力HikariCP 可以设置leak-detection-threshold: 30000当连接被借出超过 30 秒没有归还日志里就会打印 stack trace直接定位到泄漏代码的位置。Druid 也有类似配置具体参数是removeAbandonedtrue。这类问题一旦定位到代码位置修复起来就非常快通常是“最终没有在 finally 块里释放连接”。7.2 SQL 层面的高频报错与原因分析有一个特别高频的报错是Data too long for column。看起来是字符串过长但也可能是字符集不一致导致字段字节数超限。Java 端使用utf8mb4编码数据库字段如果是VARCHAR(64)能存多少个汉字、多少个英文字母两个字符集的字节数计算方式不一样。实际排查时先执行SHOW CREATE TABLE table_name;确认字段的字符集和长度再检查 Java 端传入数据的实际长度。还有一个高频是Duplicate entry xxx for key uk_xxx。这是唯一键冲突原因通常有两个业务代码重复插入或者并发场景下两个请求同时插入了相同的数据。解决思路是先确认业务上这个唯一键是否合理确实需要唯一约束的话Java 代码应该捕获冲突异常并优雅处理而不是让异常直接抛到前端。7.3 常用的排查命令和监控建议日常运维过程中我习惯准备几个随手可用的 MySQL 排查命令-- 查看当前所有正在执行的 SQL SHOW FULL PROCESSLIST; -- 查看 InnoDB 当前锁状态 SHOW ENGINE INNODB STATUS; -- 查看表状态 SHOW TABLE STATUS LIKE order_master; -- 查看慢查询日志状态 SHOW VARIABLES LIKE slow_query_log%;监控方面单靠 MySQL 自带的命令不够最好配合一套可视化的监控系统。我最常用的组合是采集 MySQL 状态指标如连接数、QPS、慢查询数量、复制延迟时间并汇入时序数据库再用图表展示。这样当系统异常时先看图表里的指标走势再决定去看日志还是去查 SQL效率高得多。生产环境如果缺监控数据库出了问题就像打黑枪完全靠猜不建议这么干。8. 几个容易忽略但影响深远的实践细节8.1 字段类型选择要克制别盲目用大类型建表时字段类型选得是否合理直接影响存储空间和查询性能。整数类型按取值范围选别一上来就是BIGINT。比如状态字段用TINYINT就够了年龄用SMALLINT就够了订单金额如果用整数存储就把元转成分为单位用INT甚至BIGINT都行。不要用DECIMAL存金额时还不统一单位浮点运算造成的精度问题迟早会爆。字符串类型优先用VARCHAR但长度不要拍脑袋。VARCHAR(255)是一个常见的选择但如果业务上明确不会超过 20 个字符就定义成VARCHAR(64)或者更短这样索引占用空间更小查询性能也更好。时间字段我推荐用DATETIME不要用TIMESTAMP。TIMESTAMP的取值范围到 2038 年会溢出虽然现在离那个时间还有几年但系统设计往往要面向未来十年甚至更久。DATETIME 没有时区问题存储上占 8 个字节清晰可靠。8.2 SQL 写法的几个坏毛病能改就改很多从培训班出来的同学写 SQL 有个坏习惯SELECT *用得飞起。在表字段少、数据量小的时候感觉不到差别一旦表宽字段多、数据量大SELECT *会带来大量不必要的回表和网络传输开销。还有一类问题是对索引列做函数运算或隐式类型转换。SELECT * FROM order_master WHERE order_no 10086;如果order_no字段是字符串类型这个 SQL 会把每个字符串转换成数字去比较导致索引失效。修正方式是保持类型一致写成WHERE order_no 10086。另一个比较隐蔽的问题是在LIKE查询里前置通配符WHERE name LIKE %张三%。这种写法无法使用索引数据量大时必然全表扫描。如果业务确实需要模糊搜索建议上全文索引或者专门的搜索引擎而不是长期依赖 MySQL 的LIKE。8.3 数据库变更要像代码一样有版本管理Java 代码有 Git 管理数据库结构也一样需要版本管理。项目一多好几个环境的表结构不一致这是很多团队的真实痛点。我推荐引入数据库迁移工具在 Java 项目里最常用的是 Flyway 或 Liquibase它的原理是把数据库结构的每一次变更写成一个带版本号的脚本按顺序执行从而保证不同环境的结构一致性。使用 Flyway 后我在项目启动时最直观的感受就是新同事拉下代码本地一套库执行一次启动脚本表结构就齐了不用再手动执行一条条 SQL。而在生产发布时JDBC 连接配置好后Flyway 会自动执行新增的迁移脚本不再依赖人工到服务器上手敲 SQL。这个习惯是减少维护成本的重要一步值得每个人从早期项目就形成条件反射。9. 关于性能优化的进阶路径与个人体会说句掏心窝的话MySQL 的学习曲线不是直线上升的。刚入门时学会建表、写 CRUD觉得自己已经会了等真正遇到线上性能问题才发现 SQL 执行计划、索引设计、事务隔离这些知识才是分水岭。很多时候一条慢 SQL 背后不是 SQL 写得不对而是对业务数据分布和索引结构缺乏理解。我在实际项目里有一个深刻的体会性能优化一定要基于数据说话。调优前先统计清楚各个表的数据量、索引分布、慢查询比例再用 EXPLAIN 验证假设。不要凭感觉加索引也不要只看执行时间短就以为优化完成了。执行时间短但扫描行数极大的 SQL在数据量翻倍后依然会打回原形。至于这个系列后面的内容我会继续沿着 Java 后端的主线聊聊 Spring 的核心机制、常见中间件的接入、接口设计规范等。MySQL 这块的内容其实还有很多可以深挖的地方比如分区表、分库分表、具体工具源码剖析以后找机会慢慢展开。还是那句话不管工具怎么迭代数据库这条路值得每一个 Java 开发者走扎实。