最近在项目中遇到了一个非常隐蔽的 Bug,最终定位到是 Mybatis 主键配置不当导致的数据丢失。这个问题让我意识到,即使是使用了多年的框架,在细节上稍有疏忽,也可能造成严重的后果。希望通过这篇文章,分享我踩过的坑以及填坑的经验,避免大家重蹈覆辙。
问题场景重现:数据凭空消失
我们有一个用户订单表 user_order,其中 order_id 是主键,并且是自增的。在业务逻辑中,我们需要先插入订单信息,然后根据 order_id 进行后续的操作。当时使用的 Mybatis 配置如下(简化版):
<insert id="insertOrder" parameterType="UserOrder" useGeneratedKeys="true" keyProperty="orderId">
INSERT INTO user_order (user_id, order_time) VALUES (#{userId}, #{orderTime})
</insert>
在测试环境一切正常,上线后却偶发性的出现数据丢失的情况:插入订单后,返回的 orderId 竟然是 null!导致后续的业务逻辑无法正常执行,相当于订单数据凭空消失了。
底层原理深度剖析:MySQL 自增主键的特性
要理解这个问题,需要深入了解 MySQL 自增主键的特性以及 Mybatis 如何获取自增主键。
- MySQL 的
AUTO_INCREMENT:MySQL 的AUTO_INCREMENT机制保证了主键的唯一性和自增性。当插入数据时,如果没有指定主键的值,MySQL 会自动生成一个唯一的递增值。 useGeneratedKeys和keyProperty:Mybatis 的useGeneratedKeys属性设置为true时,会指示 Mybatis 使用 JDBC 的getGeneratedKeys()方法来获取数据库自动生成的主键值。keyProperty属性指定了将获取到的主键值设置到哪个 Java 对象的属性上。- JDBC 的
getGeneratedKeys():JDBC 的getGeneratedKeys()方法返回一个ResultSet对象,其中包含了数据库自动生成的主键值。但是,这个方法的实现依赖于具体的数据库驱动。
问题的原因在于:
在高并发场景下,如果 MySQL 的连接池配置不当,或者网络出现短暂的抖动,Mybatis 获取 ResultSet 的时候可能会失败,导致无法正确获取到 orderId,从而返回 null。
LSI 实体词共现:
在上述场景中,我们使用了 MySQL 作为数据库,并且面临着高并发的挑战。为了应对高并发,我们使用了连接池技术,例如 Druid 或者 HikariCP。同时,为了保证系统的稳定性,我们还使用了 Nginx 进行反向代理和负载均衡。在高并发情况下,需要密切关注 MySQL 的并发连接数,合理设置连接池的大小,并且监控 Nginx 的状态。
解决方案:配置调整与代码优化
针对上述问题,我们采取了以下解决方案:
- 调整 Mybatis 配置:为了避免获取主键失败的情况,我们可以使用
<selectKey>标签,在插入数据之前或者之后,通过执行一个查询语句来获取主键值。
<insert id="insertOrder" parameterType="UserOrder">
<selectKey keyProperty="orderId" order="AFTER" resultType="java.lang.Long">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT INTO user_order (user_id, order_time) VALUES (#{userId}, #{orderTime})
</insert>
keyProperty:指定了将获取到的主键值设置到哪个 Java 对象的属性上。order:指定了<selectKey>标签中的查询语句在INSERT语句之前还是之后执行。AFTER表示在INSERT语句之后执行。resultType:指定了查询结果的类型。
- 优化 MySQL 连接池配置:合理设置连接池的大小,避免连接耗尽。可以根据实际的并发量和数据库服务器的性能进行调整。
- 增加重试机制:在获取主键失败的情况下,可以增加重试机制,例如重试 3 次。但是需要注意,重试机制可能会增加系统的延迟,需要根据实际情况进行权衡。
实战避坑经验总结
- 仔细阅读 Mybatis 官方文档:Mybatis 官方文档包含了大量的细节信息,仔细阅读官方文档可以避免很多常见的错误。
- 充分的测试:在上线之前,进行充分的测试,包括单元测试、集成测试和压力测试。特别是要模拟高并发场景,检查是否存在数据丢失的情况。
- 监控和告警:建立完善的监控和告警机制,及时发现和解决问题。可以监控 MySQL 的连接数、QPS、慢查询等指标。
- 代码 Code Review:代码 Code Review 可以帮助发现潜在的问题,提高代码质量。
- 善用日志:打印详细的日志,方便问题排查。可以记录 SQL 语句、参数和执行结果。
通过这次踩坑经历,我深刻体会到,即使是使用了多年的框架,也需要保持学习和探索的精神,不断深入了解其原理和细节。只有这样,才能避免重蹈覆辙,写出更加健壮和可靠的代码。
冠军资讯
键盘上的咸鱼