ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL插入数据避坑指南:从基础语法到批量性能优化

MySQL插入数据避坑指南:从基础语法到批量性能优化 一条 INSERT 语句能有什么技术含量如果你写了好几年 SQL 还这么想那可能只是还没在生产环境里踩过坑。字符集不对导致中文乱码、字段长度不够直接报错、批量插入把数据库锁住、误用 REPLACE 把外键数据搞没了——这些问题我都在真实项目里见过。MySQL 插入数据这件事看起来简单实际细节非常多。这篇指南会从最基础的 INSERT 语法讲起覆盖批量插入、重复键处理、事务锁、字符集、性能调优最后给出一份排错清单。无论你是刚入行的新手还是想系统查漏补缺的开发者都可以从里面找到自己需要的那部分。1. 插入前先搞懂这四件事1.1 目标表结构与字段类型很多新手拿到表就直接 INSERT结果报错了才回头去看表结构。这个习惯非常不好。插入数据之前第一步永远是确认目标表的完整结构。执行SHOW CREATE TABLE tb_user;或者DESC tb_user;把字段名、类型、默认值、是否非空、是否有唯一键全部过一遍。我曾经见过合作方把一个 DECIMAL(10,2) 字段用浮点字符串去插精度高位数直接跑偏也见过把 VARCHAR(64) 塞进 100 个字符的串插入时报 “Data too long for column”。为什么强调这一步因为 MySQL 的插入是否成功本质上就是“值是否符合列定义”的校验。字段类型决定存储格式也决定比较和索引行为。比如日期字段用字符串插入时要符合YYYY-MM-DD HH:MM:SS格式如果格式不对在严格模式下会报错在非严格模式下则可能插入0000-00-00 00:00:00这种脏数据后面查询、统计都会出乱子。根据我的经验插入前最好有一个“类型映射检查”Java 代码里的 String 对应数据库 varchar/text、Integer/Long 对应 int/bigint、BigDecimal 对应 decimal、LocalDateTime 对应 datetime/timestamp。类型对不上靠数据库隐式转换早晚出事。1.2 默认值、自增与 NULL 的处理表里的 DEFAULT 关键字不是摆设。插入数据时如果没有给某个字段赋值MySQL 会使用 DEFAULT 定义的值如果字段没有默认值且允许 NULL则插入 NULL如果字段是 NOT NULL 且没有默认值那么不传该字段就会直接报错。这个规则很基本但在实际业务里经常被人忽略。自增主键更是个经典陷阱。插入时你通常不需要给自增列赋值让 MySQL 自动生成即可但如果你显式传了 0 或者 NULLMySQL 在不同版本和 sql_mode 下的表现并不完全一致。简单做法不要在主键列上显式传值除非你有明确的需求比如数据迁移、指定 ID 的导入场景。显式指定自增值还有一个副作用——之后的自增计数会基于最大值继续走如果你插入了一个很大的 ID后续新数据的主键就会继续往上跳这种跳跃并不代表数据异常但容易吓到不熟悉的人。NULL 字段处理也要谨慎。比如可空字段没有传值默认就是 NULL但如果业务上逻辑需要空字符串而不是 NULL就得在应用层显式传入。NULL 和空字符串在唯一索引上的表现也不同唯一索引允许多个 NULL但不允许重复的空字符串。这个区别在插入时就要想清楚否则后面想去重会被坑。1.3 字符集与排序规则不能将就中文乱码是我见过的插入问题里最多的根子十有八九在字符集。MySQL 5.7 时代默认字符集是 latin1MySQL 8.0 默认是 utf8mb4。如果你的库表还停留在老配置插入中文字符就可能变成乱码或问号。正确做法是统一使用 utf8mb4。为什么不用 utf8因为 MySQL 的 utf8 实际上是 utf8mb3只能存基本多语言平面的字符像 emoji、生僻字、更多特殊符号都存不了。utf8mb4 才是真正的四字节 UTF-8 编码。字符集涉及三层服务器层character_set_server、库表层character_set_database、表的 charset、连接层character_set_client/character_set_results。即使库表是 utf8mb4客户端连接的字符集不对照样乱码。命令行连接后在插入前执行SET NAMES utf8mb4;这条命令同时改变 client、connection、results 三处的字符集。在 JDBC 连接串里则要加上characterEncodingutf8mb4或者更推荐useUnicodetruecharacterEncodingUTF-8注意老驱动版本对 utf8mb4 的支持差异新版本驱动直接用 utf8mb4 即可。连接后也可以执行SHOW VARIABLES LIKE character_set%;检查。排序规则 collation 很多人不关心但它影响字符串比较和索引排序。utf8mb4 下常见的有 utf8mb4_general_ci 和 utf8mb4_unicode_ciMySQL 8.0 默认是 utf8mb4_0900_ai_ci。选一个全链路统一不要在插入后再去嘀咕为什么 ORDER BY 出来的顺序“不对劲”。1.4 客户端连接与参数设置插入数据之前还要确认客户端和 MySQL 服务器之间的参数匹配。这里最容易出问题的是时区。JDBC 连接串里经常要指定serverTimezoneAsia/Shanghai否则 TIMESTAMP 字段插入后看到的时间可能有 8 小时偏差。MySQL 8.0 的连接器对时区更敏感建议显式配置不要依赖默认值。另一个常见报错是 SSL 连接错误。MySQL 8.0 默认开启 SSL 相关行为客户端连接如果没有正确支持会出现类似 “Cannot load trust material” 或 SSL 握手失败的提示。如果你在内网开发环境通常会选择关闭 SSL在 JDBC 连接串加useSSLfalseallowPublicKeyRetrievaltrue。后者是因为 MySQL 8.0 在 caching_sha2_password 认证插件下允许从服务器获取公钥必须显式开启否则也会连不上。这些虽然是连接阶段的问题但会直接影响你后续能不能顺利插入数据排查的时候别忽略。我见过不少同事在 Navicat 或 DBeaver 里图形化连接没问题换成命令行或者程序里就报错基本都是连接参数不一致造成的。建议在本地准备一个统一的连接配置模板命令行、JDBC、GUI 工具三处保持一致。2. 插入语句的“正确打开方式”2.1 单条插入基础语法最基本的插入语句大家都会写INSERT INTO tb_user (user_name, age, email) VALUES (张三, 28, zhangsanexample.com);建议每次插入都显式列出字段名而不是省掉列名直接 VALUES。为什么因为列出字段名之后表结构变更比如增加列时这条 INSERT 仍然安全不列出字段名必须严格按表结构顺序给值少一个字段、顺序错一个立刻报错或者插入到错误的列里这种错误在生产上非常致命。另外 VALUES 里可以用 DEFAULT 关键字显式使用默认值也可以用 NULL 来插入 NULL如果列允许。对于时间字段如果你希望插入当前时间可以写NOW()、CURRENT_TIMESTAMP()或者让字段直接依赖DEFAULT CURRENT_TIMESTAMP自动生成插入时不传即可。INSERT 成功后 MySQL 会返回 “Query OK, 1 row affected”注意 row affected 表示受影响的行数不是查询结果集。在编程语言里这个返回值可以用来判断插入是否成功。2.2 批量多值插入的正确姿势面向生产环境的批量插入尽量不要一条一条 INSERT。一条一条插意味着每条语句都有一次 SQL 解析、权限检查、网络往返、事务提交默认 autocommit 模式。数据量一上来性能差异是数量级的。正确姿势是使用多值列表INSERT INTO tb_user (user_name, age, email) VALUES (张三, 28, aexample.com), (李四, 30, bexample.com), (王五, 25, cexample.com);一句话里插入几百甚至一千行网络开销和语句解析次数被大幅压缩插入速度能提升好几倍。但这里有几个关键参数要留意max_allowed_packet单条 SQL 语句的最大包大小。默认值在不同版本里不一样4MB/64MB。如果你一次插入的数据过大会报 “Packet for query is too large”。需要根据实际数据量调大。分批次而不是一次全塞我的经验是每批 500~1000 行比较稳定既减少网络往返又不会让单条语句过大到难以排查。如果单行字段很多比如十几个字段一次 200~500 行作为上限更稳。批量插入时如果某一行数据违反约束整条语句会报错前面插入的行也会回滚在事务中。所以批量插入前最好先做一轮数据校验。从 MySQL 8.0 开始多值 INSERT 在 binlog 中的记录方式也有优化对主从复制更友好这也是推荐批量插入的一个隐藏理由。2.3 插入后如何拿到自增 ID业务里常见的痛点插入一条记录后需要立刻拿到新的主键 ID 去做后续操作。很多人喜欢先SELECT MAX(id)再 1这种做法在高并发下就是灾难会产生重复 ID。正确方式是用LAST_INSERT_ID()INSERT INTO tb_user (user_name) VALUES (张三); SELECT LAST_INSERT_ID();LAST_INSERT_ID()是连接级别的函数每个连接独立维护不会被其他连接干扰。也就是说你在同一个连接里先插入再执行SELECT LAST_INSERT_ID()拿到的就是当前这条连接插入的自增 ID。注意不能换一个连接去查那拿到的是别的值。在 JDBC 中更规范的做法是使用RETURN_GENERATED_KEYSPreparedStatement ps conn.prepareStatement( INSERT INTO tb_user (user_name) VALUES (?), Statement.RETURN_GENERATED_KEYS ); ps.setString(1, 张三); ps.executeUpdate(); ResultSet rs ps.getGeneratedKeys(); if (rs.next()) { long id rs.getLong(1); }这里需要特别提醒批量多值插入时JDBC 的getGeneratedKeys()在不同驱动版本下的表现不完全一样。很多 MySQL 驱动对批量插入只返回最后一行或第一行的 ID并不保证返回全部 ID。如果你真的需要批量插入后拿到每个 ID老实逐条插入可以放在一个事务里或者插入后根据业务唯一键反查别在一个坑里死磕。2.4 用 INSERT ... SELECT 复制数据除了 VALUES插入的另一种来源是查询结果INSERT INTO tb_user_backup (id, user_name, email) SELECT id, user_name, email FROM tb_user WHERE create_time 2024-01-01;这在做数据归档、临时表复制、报表初始化时非常常用。它本质上是一条语句完成“查出来再写进去”不需要在应用层先查一遍再拼 INSERT。使用 INSERT ... SELECT 有几个容易踩的坑目标表的主键和唯一键约束要提前确认否则插入中途遇到重复键直接报错中断。如果两个表字段顺序不一致一定要在 INSERT 和 SELECT 中分别显式列出字段名不要用SELECT *。大批量 INSERT ... SELECT 时如果表很大会占用较多资源并可能影响线上查询建议在低峰期执行或加上 LIMIT 分批处理。SELECT 出的 NULL 值会原样插入目标表除非目标表字段有 NOT NULL 约束或默认值。另外INSERT ... SELECT 在数据量大的情况下同样可以使用 WHERE 条件做切片分批比如以 id 范围为界每批 10 万条既稳定又可监控。3. 进阶场景与决策冲突、事务与预处理3.1 INSERT IGNORE、ON DUPLICATE KEY UPDATE、REPLACE INTO 怎么选如果说普通 INSERT 是“闭眼插入”那么这三种语法就是“看看再插”。它们都是针对唯一键冲突的处理但结果完全不同。INSERT IGNORE 遇到唯一键冲突时直接忽略不报错也不插入。适合“有数据就不动它”的场景比如初始化基础数据跑批导入时想跳过已存在的记录。ON DUPLICATE KEY UPDATE 遇到冲突时执行更新而不是插入。这个最常用适合“存在则更新、不存在则插入”的 upsert 场景。典型写法INSERT INTO tb_user (id, user_name, email) VALUES (1, 张三, newexample.com) ON DUPLICATE KEY UPDATE user_name VALUES(user_name), email VALUES(email);需要注意的是 MySQL 8.0.20 开始VALUES()在 ON DUPLICATE KEY UPDATE 中被标记为已废弃推荐使用“新值引用”别名写法INSERT INTO tb_user (id, user_name, email) VALUES (1, 张三, newexample.com) AS new ON DUPLICATE KEY UPDATE user_name new.user_name, email new.email;REPLACE INTO 则是“先删后插”遇到冲突先删除原来的行再插入新行。这个副作用很多人忽略一是行记录会被删除重建自增 ID 可能变化二是如果该行被其他表外键引用删除时可能触发外键约束错误或级联删除三是删除和插入虽然整体在内部事务里完成但从语义上它确实是 DELETE INSERTbinlog 记录也和普通 INSERT 不同。我的建议是绝大多数场景优先考虑 ON DUPLICATE KEY UPDATE只有确实需要“重建数据”时才用 REPLACE INTOINSERT IGNORE 用在幂等导入里比较合适。三者的对比可以看这个表语法冲突时行为影响行数自增ID适用场景INSERT IGNORE忽略0不变幂等初始化、跳过已存在ON DUPLICATE KEY UPDATE更新1插入/2更新不变插入时变化upsert、同步场景REPLACE INTO删除后插入2可能变化明确需要重建记录3.2 事务、锁与插入的三角关系MySQL 的插入默认每条语句都在 autocommit 模式下也就是说每条 INSERT 自己构成一个事务。问题在于如果批量插入几百条每条一个事务那么每条都要刷盘、记录 binlog整体性能很差。把多条插入包进一个显式事务是提高性能最直接的办法START TRANSACTION; INSERT INTO tb_order (order_no, amount) VALUES (O001, 100); INSERT INTO tb_order (order_no, amount) VALUES (O002, 200); COMMIT;把事务包起来至少三个好处一是减少提交次数binlog 刷盘和 redo log 刷盘频率降低二是出错时整体回滚不会产生一半成功一半失败三是锁的粒度在事务结束时释放更可控。但事务大了也有问题。事务越大锁持有的时间越长其他插入/更新操作会被阻塞。InnoDB 的默认隔离级别是 REPEATABLE READ插入操作会使用插入意向锁和记录锁。在高并发下两个事务同时往同一个间隙插入数据可能出现间隙锁互相等待最终报 1205 锁等待超时。这里给个经验值在线业务相关的大批量插入一个事务控制在 1000 行以内比较安全如果是离线跑批可以放宽到几万行但要注意观察锁等待和磁盘 IO。如果真的要在大量并发下做插入合并可以考虑对数据做分片按业务键让不同线程处理不同分片避免锁的相互影响。还要提醒一点如果插入过程中步骤多一定要在 catch 异常里执行 ROLLBACK别只 COMMIT 不管理异常。Spring 里可以用 Transactional但要注意事务边界、传播属性和回滚条件别以为方法一加注解就万事大吉。3.3 预处理语句和防注入插入在编程语言里插入数据永远优先使用 PreparedStatement或对应语言的预处理机制而不是拼接 SQL 字符串。为什么首要原因就是防 SQL 注入。举个反例String sql INSERT INTO tb_user (user_name) VALUES ( name );如果 name 传入的是); DROP TABLE tb_user; --数据库直接原地爆炸。而预处理语句把 SQL 结构和参数分开参数值只是作为数据传给数据库不存在被解释为 SQL 的风险。参数化查询是底线任何代码规范都不应该突破它。预处理还有一个性能优势相同结构的 INSERT 语句多次执行时MySQL 可以缓存执行计划减少重复解析开销。在 Java 中直接执行String sql INSERT INTO tb_user (user_name, age) VALUES (?, ?); try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, name); ps.setInt(2, age); ps.executeUpdate(); }批量插入时可以用 addBatch executeBatch配合手动提交事务速度会好很多。不过在 MySQL 驱动下executeBatch 的性能提升高度依赖 JDBC URL 中的rewriteBatchedStatementstrue参数。这个参数很关键没有它驱动会把 batch 里的每一条都单独发给服务器性能跟逐条插入差不多开了它驱动才会把多条语句重写成多值 INSERT 一次性发送。这是我调试过很多次才确认的细节建议写入数据库连接配置模板。3.4 大文本与 JSON 类型数据的插入细节开发中越来越多的表开始用 JSON 类型或者用 TEXT/ MEDIUMTEXT 存长文本。插入这些类型时有几个细节值得注意。JSON 类型在插入时不能直接放一个乱写的字符串。MySQL 8.0 会在插入时校验 JSON 合法性非法 JSON 直接报错。如果你在应用层拼了一个不完整的 JSON数据库会毫不留情地拒绝。所以要么在应用层先做 JSON 解析校验要么在插入前用JSON_VALID()检查一下。更推荐的做法是把数据在应用层构造好后再传MySQL 端尽量少做格式转换。TEXT 类型插入时要注意max_allowed_packet的影响。如果单个字段就几 MB可能触发 “Packet for query is too large”。还要注意 TEXT 类型不能设置默认值MySQL 5.7/8.0 中 TEXT 类型不能有 DEFAULT 值除非是表达式默认值但限制较多插入时要么给值要么让列允许 NULL。这个限制经常让新人在建表阶段困惑以为是自己 SQL 写错了。大段的字符串插入还会影响内存。MySQL 处理长文本时会在内存里构造行记录太长的值会使用外部存储比如 TEXT 字段超出页面大小时存储在溢出页。插入超大字段本身性能不高如果有需求建议拆分表设计或者评估是否真的需要落到 MySQL 里。4. 插入数据的典型故障排查清单4.1 “Data too long”和类型不匹配报错信息“Data too long for column xxx at row 1”这几乎是插入数据最常见的报错。原因基本就两种字符串长度超出 VARCHAR 定义的长度数值精度/范围超出 INT/DECIMAL 限制。排查思路先看表结构里列的长度定义确认业务数据是否真的需要这么长。如果是业务确实要加大用 ALTER TABLE 修改列长度但要注意大表 ALTER 的性能和锁。如果不想改表应用层做截断或校验别把垃圾数据塞进库。类型不匹配则更隐蔽。比如插入abc到 INT 字段在严格模式下报错 “Incorrect integer value”在非严格模式下可能插入 0。MySQL 5.7 默认开启严格模式STRICT_TRANS_TABLES所以现在基本都是报错。你可以执行SELECT sql_mode;查看当前模式。生产环境中建议保留严格模式别关闭否则脏数据会在不知不觉中进库后面查数时才是真正的噩梦。4.2 中文乱码问题定位与解决乱码问题的定位思路其实是“全链路字符集排查”。执行SHOW VARIABLES LIKE character_set%;检查数据库、客户端、结果集等字符集。如果character_set_client不是 utf8mb4客户端发给服务器的 SQL 就可能被误解。命令行连接时建议在 my.cnf 的 [mysql] 段配置default-character-setutf8mb4或者在连接后立即执行SET NAMES utf8mb4。JDBC 连接串里配置characterEncoding时注意连接器版本Connector/J 8.x 推荐characterEncodingUTF-8实际映射到 utf8mb4或者直接用 utf8mb4旧版本驱动只认 UTF-8。另外一个容易忽略的点是操作系统和终端编码。如果你在 Windows 的 cmd 里直接用 mysql 命令行插入中文终端编码是 GBK而客户端字符集是 utf8mb4几乎必然乱码。解决方案是用图形工具或者切换终端编码或者让客户端字符集同终端编码保持一致。这类问题查了半天最后发现是“终端端”的问题并不罕见。一旦数据已经以错误字符集写入再想转换就非常折腾。正确做法是插入前保证链路一致。如果已经乱了可以在导出和导入时做一次字符集转换但说实话能救回来的概率和复杂度完全不成正比。这也是为什么我一直强调“事前统一”。4.3 批量插入慢、卡顿与锁等待批量插入性能差先别急着怪机器通常是可以优化的。几个优先检查的方向是否真的在用多值插入还是逐条插入。是否每条提交一次事务。是否表上有过多的索引需要同步更新。是否有其他事务在占用行锁/表锁产生了锁等待。锁等待超时的报错是ERROR 1205: Lock wait timeout exceeded。出现这个错误用SHOW ENGINE INNODB STATUS;查看最近的锁信息重点看 TRANSACTION 段落里的等待关系。或者在performance_schema.data_lock_waits表中查当前等待队列。常见的一种情况两个事务分别插入不同数据但恰好它们都试图在同一个“间隙”上拿插入意向锁导致互相等待。这种问题很难彻底消除只能通过调整业务逻辑例如错峰插入、按唯一键范围分片来规避。如果是“慢”而不是“卡死”检查innodb_flush_log_at_trx_commit参数。默认值为 1意味着每次事务提交都要把 redo log 刷到磁盘最安全但最慢。批量导入等可以接受丢最后一部分日志的场景可以临时设置为 2性能提升明显。但线上业务不建议动这个参数数据安全优先。还有sync_binlog。它的默认值在 MySQL 8.0 是 1每次提交同步 binlog。批量导入场景可以设置sync_binlog0提升性能但要注意主从切换的数据风险。测试环境无所谓生产环境要权衡。4.4 主键冲突、自增跳跃与数据重复主键冲突报错是ERROR 1062: Duplicate entry xxx for key PRIMARY。出现这个错误通常是业务代码没有做唯一性控制或者重复插入数据。如果期望“有则更新”就用 ON DUPLICATE KEY UPDATE如果只是不想让任务中断就配合 INSERT IGNORE 或先查后插但先查后插存在并发窗口不推荐做唯一性保障。自增 ID 跳跃问题多数人都遇到过删除了一批数据后再插入的记录 ID 并不会复用被删除的 ID而是继续往上走。这个是 InnoDB 的行为自增计数器只增不减。RESTART 表的自增计数需要ALTER TABLE tb_user AUTO_INCREMENT1但只对“空表”或者“指定值大于当前最大值”时才有效否则会被 MySQL 忽略。还有双主/多主架构下auto_increment_increment和auto_increment_offset会影响分片节点各自生成的自增 ID。插入时如果程序里没有正确处理可能产生重复 ID。这种情况一般通过雪花 ID 或使用集中式发号器解决而不是依赖 MySQL 自增。最后提一个容易犯的错程序里明明做了重复判断还是出现了重复数据。多半是并发了——两个请求同时查数据库发现都不存在然后同时插入。幂等的正确实现方式是靠数据库唯一索引兜底应用层判断只是降低错误概率的辅助手段。插入前先在表上加好唯一索引这是最稳定的一道防线。5. 性能优化与个人实战心得5.1 影响插入性能的关键参数聊完了具体操作再说说真正决定插入性能的几个核心参数。innodb_buffer_pool_size内存中缓存数据和索引的缓冲池。插入时会写入 buffer pool再异步落到磁盘。内存大插入缓存命中率高速度就快。一般建议设置为物理内存的 60%~75%但要留给操作系统和其他进程空间。innodb_flush_log_at_trx_commit上面提过取值 0/1/2。1 最安全也最慢2 在很多场景下是性能和安全的折中。innodb_log_file_sizeredo log 文件大小。太小会导致频繁切换和刷盘影响插入性能。MySQL 8.0 默认值是 48MB批量写入较大时建议调大到 512MB~1GB。注意调整该参数需要实例重启并且要评估启动时的恢复时间。max_allowed_packet控制单条 SQL 包大小。批量插入语句比较大不调大就会报错或性能异常。bulk_insert_buffer_size对 MyISAM 表有效InnoDB 表会自动忽略这个参数。如果你还在用 MyISAM建议迁移到 InnoDB这里就不展开讲了。这些参数可以在会话级别或全局级别临时调整但要明白哪些是安全变更、哪些需要重启。比如SET GLOBAL max_allowed_packet...是动态参数立即生效且不需要重启而innodb_log_file_size必须修改配置文件并重启不能动态变更。搞混这两个类别会在线上生产掉进大坑。5.2 LOAD DATA INFILE大批量导入的正确选择当要插入的数据量达到几十万、几百万行时无论你怎么优化 VALUES 多值插入速度都很难和 LOAD DATA INFILE 相比。LOAD DATA INFILE 的基本用法LOAD DATA INFILE /tmp/user_data.csv INTO TABLE tb_user FIELDS TERMINATED BY , OPTIONALLY ENCLOSED BY LINES TERMINATED BY \n IGNORE 1 LINES (user_name, age, email);这是一种把文本文件数据高速导入数据库的方式比逐条 INSERT 快得多。核心原理是 MySQL 内部直接解析文件并批量写入绕开了大量的 SQL 解析、网络传输和日志记录开销。但 LOAD DATA 有几个前提文件需要放在 MySQL 服务器本地LOCAL 选项表示从客户端上传文件但会受参数local_infile控制。权限要求需要 FILE 权限服务器本地文件或客户端开启了 local_infile客户端文件。引号、转义、换行符的处理容易出错导入前先导一个小样本验证格式。文件里的垃圾行、错误类型同样会报错。可以用 IGNORE 和 SET 子句处理部分数据比如LOAD DATA INFILE /tmp/user_data.csv INTO TABLE tb_user FIELDS TERMINATED BY , IGNORE 1 LINES (user_name, tmp_age, email) SET age IF(tmp_age , NULL, tmp_age);当然LOAD DATA 也是一条大事务如果文件特别大仍然需要考虑事务大小和锁的影响。建议按业务逻辑把文件切分成多个中小文件分批导入这样既能控制锁持有时间也方便失败后定位到具体文件。5.3 生产环境插入数据的最佳实践清单最后把我这些年实践下来觉得比较靠谱的几条经验整理一下显式列出插入字段不依赖字段顺序。使用参数化查询PreparedStatement禁止拼接 SQL。JDBC 批量插入开启rewriteBatchedStatementstrue。大批量写入用多值 INSERT每批控制 500~1000 行放进显式事务。唯一性判断交给数据库唯一索引不要相信应用层的 check-then-insert。主从架构下关注自增 ID 冲突必要时引入分布式 ID。插入前设置SET NAMES utf8mb4或连接串配置确保全链路字符集统一。严格模式保持开启别为省事放弃数据质量。监控锁等待超时轻则调大innodb_lock_wait_timeout重则需要改业务分片逻辑。写完代码自己看一眼执行计划或者 EXPLAIN插入语句没有执行计划但 INSERT ... SELECT 的 SELECT 部分是可以用 EXPLAIN 看的。测试环境插性能数据前先同步生产环境的表结构别用测试环境的空表/少索引去推断性能。这些建议每一条背后我都有过真实翻车经历。比如rewriteBatchedStatements这个参数我是在一个日增百万数据的同步任务里发现的没加之前一次 batch 提交要十几秒加上之后降到两秒多又比如严格模式一个同事为了“插入方便”把 sql_mode 改松了结果库里产生了一大堆隐式转换后的脏数清洗成本远超省下的那点时间。插入数据看起来是 CRUD 里最不起眼的一步但你要是把它每一步想透了能避开的问题比很多“高级技巧”都值钱。
返回列表