MySQL 当记录不存在时insert,当记录存在时update

本文介绍在MySQL中实现当记录不存在时插入,存在时更新的方法。包括使用INSERT与ON DUPLICATE KEY UPDATE结合、REPLACE语句及利用NOT EXISTS进行插入的不同场景。

MySQL 当记录不存在时insert,当记录存在时更新

网上基本有三种解决方法。

第一种:

示例一:insert多条记录

假设有一个主键为 client_id 的 clients 表,可以使用下面的语句:

INSERT INTO clients

(client_id, client_name, client_type)

SELECT supplier_id, supplier_name, 'advertising'

FROM suppliers

WHERE not exists (select * from clients

where clients.client_id = suppliers.supplier_id);

示例一:insert单条记录

INSERT INTO clients

(client_id, client_name, client_type)

SELECT 10345, 'IBM', 'advertising'

FROM dual

WHERE not exists (select * from clients

where clients.client_id = 10345);

使用 dual 做表名可以让你在 select 语句后面直接跟上要insert字段的值,即使这些值还不存在当前表中。

第二种:

INSERT 中 ON DUPLICATE KEY UPDATE的使用

如果您指定了ON DUPLICATE KEY UPDATE,并且insert行后会导致在一个UNIQUE索引或PRIMARY KEY中出现重复值,则执行旧行UPDATE。例如,如果列a被定义为UNIQUE,并且包含值1,则以下两个语句具有相同的效果:mysql> INSERT INTO table (a,b,c) VALUES (1,2,3)

         -> ON DUPLICATE KEY UPDATE c=c+1;

mysql> UPDATE table SET c=c+1 WHERE a=1;

如果行作为新记录被insert,则受影响行的值为1;如果原有的记录被更新,则受影响行的值为2。

注释:如果列b也是唯一列,则INSERT与此UPDATE语句相当:

mysql> UPDATE table SET c=c+1 WHERE a=1 OR b=2 LIMIT 1;

如果a=1 OR b=2与多个行向匹配,则只有一个行被更新。通常,您应该尽量避免对带有多个唯一关键字的表使用ON DUPLICATE KEY子句。

您可以在UPDATE子句中使用VALUES(col_name)函数从INSERT...UPDATE语句的INSERT部分引用列值。换句话说,如果没有发生重复关键字冲突,则UPDATE子句中的VALUES(col_name)可以引用被insert的col_name的值。本函数特别适用于多行insert。 VALUES()函数只在INSERT...UPDATE语句中有意义,其它时候会返回NULL。

示例:

mysql> INSERT INTO table (a,b,c) VALUES (1,2,3),(4,5,6)

         -> ON DUPLICATE KEY UPDATE c=VALUES(a)+VALUES(b);

本语句与以下两个语句作用相同:

mysql> INSERT INTO table (a,b,c) VALUES (1,2,3)

         -> ON DUPLICATE KEY UPDATE c=3;

mysql> INSERT INTO table (a,b,c) VALUES (4,5,6)

         -> ON DUPLICATE KEY UPDATE c=9;

当您使用ON DUPLICATE KEY UPDATE时,DELAYED选项被忽略。

第三种:

REPLACE语句

我们在使用数据库时可能会经常遇到这种情况。如果一个表在一个字段上建立了唯一索引,当我们再向这个表中使用已经存在的键值insert一条记录,那将会抛出一个主键冲突的错误。当然,我们可能想用新记录的值来覆盖原来的记录值。如果使用传统的做法,必须先使用DELETE语句删除原先的记录,然后再使用 INSERTinsert新的记录。而在MySQL中为我们提供了一种新的解决方案,这就是REPLACE语句。使用REPLACEinsert一条记录时,如果不重复,REPLACE就和INSERT的功能一样,如果有重复记录,REPLACE就使用新记录的值来替换原来的记录值。

使用REPLACE的最大好处就是可以将DELETE和INSERT合二为一,形成一个原子操作。这样就可以不必考虑在同时使用DELETE和INSERT时添加事务等复杂操作了。

在使用REPLACE时,表中必须有唯一索引,而且这个索引所在的字段不能允许空值,否则REPLACE就和INSERT完全一样的。

在执行REPLACE后,系统返回了所影响的行数,如果返回1,说明在表中并没有重复的记录,如果返回2,说明有一条重复记录,系统自动先调用了 DELETE删除这条记录,然后再记录用INSERT来insert这条记录。如果返回的值大于2,那说明有多个唯一索引,有多条记录被删除和insert。

REPLACE的语法和INSERT非常的相似,如下面的REPLACE语句是insert或更新一条记录。

REPLACE INTO users (id,name,age) VALUES(123, '赵本山', 50);

insert多条记录:

REPLACE INTO users(id, name, age)

VALUES(123, '赵本山', 50), (134,'Mary',15);

REPLACE也可以使用SET语句

REPLACE INTO users SET id = 123, name = '赵本山', age = 50;

上面曾提到REPLACE可能影响3条以上的记录,这是因为在表中有超过一个的唯一索引。在这种情况下,REPLACE将考虑每一个唯一索引,并对每一个索引对应的重复记录都删除,然后insert这条新记录。假设有一个table1表,有3个字段a, b, c。它们都有一个唯一索引。

CREATE TABLE table1(a INT NOT NULL UNIQUE,b INT NOT NULL UNIQUE,c INT NOT NULL UNIQUE);

假设table1中已经有了3条记录

a b c

1 1 1

2 2 2

3 3 3

下面我们使用REPLACE语句向table1中insert一条记录。

REPLACE INTO table1(a, b, c) VALUES(1,2,3);

返回的结果如下

Query OK, 4 rows affected (0.00 sec)

在table1中的记录如下

a b c

1 2 3

我们可以看到,REPLACE将原先的3条记录都删除了,然后将(1, 2, 3)insert。

总结:虽然没有具体测试,感觉第一种最费资源了(只是感觉),不过你要是没有主键的话也只能用他了。第二种和第三种的区别是:1)insert是先尝试insert,若主键存在则更新。REPLACE是先尝试insert,若主键存在则删除原纪录再insert。2)如果有多个唯一关键字发生冲突(不同关键字的冲突发生在不同记录),比如现在有2个字段2条记录冲突了(没条记录冲突一个字段),则insert是选择排序后在前面的一条进行更新,REPLACE是删除那两条记录,然后insert新记录。本人的一点小见解,如有错误,欢迎指正。

<think>我们面对的问题是:在MySQL中,对于没有主键的表,如何实现“如果数据存在则更新,存在则插入”的操作。 根据之前的引用和讨论,我们知道在有唯一索引或主键的情况下,可以使用`INSERT ... ON DUPLICATE KEY UPDATE`或者`REPLACE`语句。但是,现在表没有主键,也没有唯一索引,那么这些方法就适用了。 我们需要考虑在没有唯一标识(主键或唯一索引)的情况下,如何判断数据是否存在。这里的关键是:我们根据什么条件来判断数据的存在?通常,我们需要根据多个字段的组合来判断,即使这些字段没有建立唯一约束。 然而,在MySQL中,如果没有唯一索引(或主键),就无法使用`ON DUPLICATE KEY UPDATE`,因为它依赖于唯一索引或主键来检测重复。 因此,我们可以考虑以下方法: 方法1:使用`UPDATE`然后检查影响行数,如果影响行数为0则执行`INSERT` 步骤: 1. 执行UPDATE语句,更新满足条件的数据(条件就是用来判断数据存在的多个字段)。 2. 检查UPDATE影响的行数(通过ROW_COUNT()函数或JDBC中的getUpdateCount()等)。 3. 如果影响行数为0,说明没有记录存在,则执行INSERT。 示例: 我们先假设我们有一个表`t`,有三个字段:`col1`, `col2`, `col3`,其中没有主键,也没有唯一索引。我们想根据`col1`和`col2`的组合来判断是否存在,如果存在则更新`col3`,存在则插入新行。 SQL语句可以这样写: ```sql -- 第一步:尝试更新 UPDATE t SET col3 = 'new_value' WHERE col1 = 'value1' AND col2 = 'value2'; -- 第二步:检查影响行数 -- 如果影响行数为0,则插入 INSERT INTO t (col1, col2, col3) SELECT 'value1', 'value2', 'new_value' FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM t WHERE col1 = 'value1' AND col2 = 'value2' ); ``` 但是,这种方法存在一个问题:在UPDATEINSERT之间可能有其他事务插入了一行满足条件的数据,这样INSERT语句可能会因为重复而失败(如果我们在业务逻辑上认为col1和col2的组合是唯一的,但没有在数据库层面加唯一约束,则可能发生)。或者,如果我们认为它们是唯一的,那么上述UPDATE可能更新了多行,而我们实际上只想要更新一行?所以这种方法在并发情况下可能安全。 方法2:使用事务来保证原子性 我们可以将上述操作放在一个事务中,并给查询条件加上锁(例如SELECT ... FOR UPDATE)。这样,在事务执行期间,其他事务能修改这些行(具体取决于隔离级别和锁的类型)。 步骤: 1. 开启事务。 2. 使用SELECT ... FOR UPDATE查询满足条件的行(根据col1和col2)。 3. 如果查询到数据,则执行UPDATE。 4. 如果没有查询到数据,则执行INSERT。 5. 提交事务。 示例: ```sql START TRANSACTION; SELECT * FROM t WHERE col1 = 'value1' AND col2 = 'value2' FOR UPDATE; -- 锁定匹配的行(如果没有匹配行,则锁定间隙以防止插入) -- 然后根据查询结果决定 -- 如果在应用程序中检测到有结果,则: UPDATE t SET col3 = 'new_value' WHERE col1 = 'value1' AND col2 = 'value2'; -- 如果没有结果,则: INSERT INTO t (col1, col2, col3) VALUES ('value1', 'value2', 'new_value'); COMMIT; ``` 这种方法可以避免并发问题,但是需要显式地使用事务,并且需要确保所有操作都使用相同的条件,并且加锁可能会影响性能。 方法3:创建唯一索引 虽然问题要求没有主键,但我们可以考虑添加一个唯一索引。如果业务上确实需要根据某些字段组合来唯一确定一行,那么添加唯一索引是最佳做法。 但是,如果问题中确实能添加唯一索引(例如没有权限修改表结构,或者字段组合并唯一),那么方法2是相对安全的选择。 方法4:使用临表(批量操作) 如果是批量操作,可以考虑使用临表,然后通过JOIN进行更新,再通过NOT EXISTS进行插入。但单条操作适用。 方法5:使用REPLACE语句(但要求有唯一索引) 根据引用[3],REPLACE语句的工作原理是先尝试插入,如果插入失败(因为唯一索引冲突),则删除原有记录再插入新记录。但如果没有唯一索引,则无法触发替换。 方法6:使用INSERT IGNORE(适用) INSERT IGNORE会忽略插入的错误(包括重复键错误),但如果没有唯一索引,就会有重复键错误,所以它能用于更新,而只是插入(如果插入失败则忽略,但这里没有唯一索引,所以会失败,也就会更新)。 总结: 在无主键且无唯一索引的情况下,推荐使用方法2(事务+锁)来保证操作的原子性,避免并发问题。 但是,请注意:如果表中没有索引,那么WHERE条件的查询(尤其是在FOR UPDATE)可能会全表扫描并锁住整个表,这在性能上可能是可接受的。因此,如果条件字段(如col1和col2)经常用于查询,建议创建索引(即使是唯一索引)以提高性能。 因此,最终的解决方案是: 1. 如果条件字段可以创建唯一索引,则创建唯一索引,然后使用`INSERT ... ON DUPLICATE KEY UPDATE`。 2. 如果能创建唯一索引,则使用事务和锁(方法2),并考虑在条件字段上创建普通索引以提高查询性能。 示例代码(方法2)在应用程序中的伪代码: ``` 开启事务 执行:SELECT ... WHERE col1=? AND col2=? FOR UPDATE 如果查询到记录: 执行UPDATE 否则: 执行INSERT 提交事务 ``` 注意:在MySQL中,使用SELECT ... FOR UPDATE需要在事务中,并且隔离级别至少为REPEATABLE READ(InnoDB默认的隔离级别)。 相关问题: 1. 在MySQL中,如何避免无主键表的重复数据? 2. 如何为没有主键的表添加主键? 3. MySQL中SELECT ... FOR UPDATE的锁定范围是什么? 4. 在并发环境下,如何安全地执行“存在则更新,存在则插入”的操作? </think>### MySQL 无主键表实现存在则更新/存在则插入的方法 #### 📌 核心思路:通过组合字段唯一性判断 + 事务控制 当表中无主键,需自行定义业务层面的唯一性条件(通常是多个字段组合),并通过显式事务保证操作的原子性。以下是具体解决方案: --- ### 🔧 方法一:事务+显式锁(推荐) 通过`SELECT ... FOR UPDATE`锁定符合条件的行,再根据结果决定执行`UPDATE`或`INSERT`: ```sql START TRANSACTION; -- 1. 锁定符合条件的行(假设col1+col2组合唯一) SELECT * FROM your_table WHERE col1 = 'value1' AND col2 = 'value2' FOR UPDATE; -- 2. 根据查询结果分支处理 -- 存在则更新 UPDATE your_table SET col3 = 'new_value' WHERE col1 = 'value1' AND col2 = 'value2'; -- 存在则插入(通过ROW_COUNT()判断) IF ROW_COUNT() = 0 THEN INSERT INTO your_table (col1, col2, col3) VALUES ('value1', 'value2', 'new_value'); END IF; COMMIT; ``` **优势**: - 100% 保证操作的原子性[^1] - 显式锁防止并发冲突 **注意**:需在`col1`,`col2`上创建普通索引提升性能 --- ### 🔄 方法二:两阶段操作(无事务) ```sql -- 第一阶段:尝试更新 UPDATE your_table SET col3 = 'new_value' WHERE col1 = 'value1' AND col2 = 'value2'; -- 第二阶段:若未更新到数据则插入 INSERT INTO your_table (col1, col2, col3) SELECT 'value1', 'value2', 'new_value' FROM DUAL WHERE NOT EXISTS ( SELECT 1 FROM your_table WHERE col1 = 'value1' AND col2 = 'value2' ); ``` **适用场景**: - 低并发环境 - 可容忍短暂数据一致 **风险**:两次操作间可能有新数据插入导致重复 --- ### ️ 关键问题分析与建议 1. **性能瓶颈** - WHERE条件中的字段**必须创建索引**(无索引会导致全表扫描锁表) - 复合索引示例: ```sql CREATE INDEX idx_unique_combo ON your_table (col1, col2); ``` 2. **并发控制** | 方案 | 并发安全性 | 性能影响 | |--------------------|------------|----------| | 事务+显式锁 | ⭐⭐⭐⭐⭐ | ⭐⭐ | | 两阶段无事务 | ⭐⭐ | ⭐⭐⭐⭐ | | 应用层重试机制 | ⭐⭐⭐ | ⭐⭐⭐ | 3. **终极解决方案** **添加代理主键**是最佳实践: ```sql ALTER TABLE your_table ADD id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY; ``` 添加后即可直接使用原子操作: ```sql INSERT INTO your_table (col1, col2, col3) VALUES ('v1','v2','v3') ON DUPLICATE KEY UPDATE col3 = VALUES(col3); -- 依赖代理主键 ``` --- ### 🌐 应用场景对比 | 场景 | 推荐方案 | 示例 | |--------------------------|-------------------|---------------------------| | 高频更新(如计数器) | 事务+显式锁 | 库存扣减 | | 批量数据处理 | 两阶段操作 | 夜间数据同步 | | 强一致性要求(金融场景) | 添加代理主键 | 账户余额变更 | | 只插入更新 | `INSERT IGNORE` | 日志记录[^3] | > **关键提示**:MySQL的`REPLACE`和`ON DUPLICATE KEY`都**依赖主键或唯一索引**[^2][^3],无主键表必须通过事务或程序逻辑实现类似功能。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值