MySQL中常见的坑

404 - Not Found · 语雀



将 A 表查询的数据插入到 B 表中

  1. INSERT INTO user_new(id,username,pwd) SELECT id,NAME,pwd FROM user;

不存在则插入,存在则不插入

参考链接

需求: 对于普通的 INSERT 插入,如果想要保证不插入重复记录,我们只有对某个字段创建唯一约束实现(比如:id 不能重复),那么没有不创建唯一约束,仅通过一条 INSERT INTO 语句实现的方案呢?

答案:有的,使用 _**INSERT INTO IF EXISTS**_ 即可实现该需求。
具体语法如下:

  1. INSERT INTO table(field1, field2, fieldn) SELECT 'field1Value', 'field2Value', 'fieldnValue' FROM DUAL WHERE NOT EXISTS(SELECT field FROM table WHERE field = ?)

其中的 DUAL 是一个临时表,不需要物理创建,这么用即可。

趁热打铁

需求:已知有 user 表,判断表中是否有张飞,没有则新增,有则不新增。

user 表

  1. SET NAMES utf8mb4;
  2. SET FOREIGN_KEY_CHECKS = 0;
  3. -- ----------------------------
  4. -- Table structure for user
  5. -- ----------------------------
  6. DROP TABLE IF EXISTS `user`;
  7. CREATE TABLE `user` (
  8. `id` int(11) NOT NULL AUTO_INCREMENT,
  9. `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  10. `alias` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  11. PRIMARY KEY (`id`) USING BTREE
  12. ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
  13. -- ----------------------------
  14. -- Records of user
  15. -- ----------------------------
  16. SET FOREIGN_KEY_CHECKS = 1;
  17. -- 新增两条基础数据
  18. INSERT INTO `user`(`name`, `alias`, `last_login_time`) VALUES ('刘备', '阿蛮', NOW());
  19. INSERT INTO `user`( `name`, `alias`, `last_login_time`) VALUES ('关羽', '云长', NOW());

实现:

  1. INSERT INTO user(name, alias, last_login_time) SELECT "张飞","翼德",NOW() FROM DUAL WHERE NOT EXISTS(SELECT NAME FROM user WHERE NAME = "张飞")

多次执行上面的 SQL 语句可以发现,第一次执行 user 表新增一条 id = 3 的数据,再次执行 user 表无任何变化。

不存在则插入,存在则更新

方法1:使用 replace 关键字
replaceinsert 的增强版,可以实现插入的数据和已存在的数据发生主键或者唯一键重复,则删除已存在的数据,再实现插入,如果不重复,则直接插入数据。
结合 Mybatis 批量处理,用法如下:

  1. <update id="updateSchoolTable" useGeneratedKeys="true" parameterType="java.util.List">
  2. <!-- 存在则更新,不存在则插入 -->
  3. replace into
  4. schoolTable(Id,describeDataForm,childrenID,relevanceID,showType)
  5. values
  6. <foreach collection="list" item="schoolDataForm" separator=",">
  7. (
  8. #{schoolDataForm.Id},
  9. #{schoolDataForm.describeDataForm},
  10. #{schoolDataForm.childrenID},
  11. #{schoolDataForm.relevanceID},
  12. #{schoolDataForm.showType}
  13. )
  14. </foreach>
  15. </update>

方法2:使用 ON DUPLICATE KEY UPDATE
该方法能够在主键或者唯一键重复时,修改原记录中某字段的数据。
结合Mybatis批量处理,用法如下:

  1. <update id="updateSchoolTable" useGeneratedKeys="true" parameterType="java.util.List">
  2. insert into
  3. schoolTable(Id,describeDataForm,childrenID,relevanceID,showType)
  4. values
  5. <foreach collection="list" item="schoolDataForm" separator=",">
  6. (
  7. #{schoolDataForm.Id},
  8. #{schoolDataForm.describeDataForm},
  9. #{schoolDataForm.childrenID},
  10. #{schoolDataForm.relevanceID},
  11. #{schoolDataForm.showType}
  12. )
  13. </foreach>
  14. <!--存在即可修改下述字段的数据,注意values()中的内容是数据表中相应的字段名-->
  15. ON DUPLICATE KEY UPDATE
  16. describeDataForm=values(describeDataForm),
  17. childrenID=values(childrenID),
  18. relevanceID=values(relevanceID),
  19. showType=values(showType)
  20. </update>

性能区别:
正如 replace 底层实现所示,如果主键重复会先删除数据库中原来的记录,插入新纪录。但是数据库删除操作需要维护主键索引,这无疑需要消耗性能。
ON DUPLICATE KET QPDATE 只是在主键重复时修改所需字段的值,所以不影响主键。维护成本自然相对于replace 低。
注意:在数据量较小时,两者效率大致相同,都很快,但是出现大量数据(百万级别)时,差异就显示出来了。