在高并发场景下,为了避免因并发请求导致的重复插入问题,可以采用以下几种策略:
使用 INSERT ... ON DUPLICATE KEY UPDATE
:
利用 MySQL 提供的 ON DUPLICATE KEY UPDATE
语句,可以在尝试插入数据时,如果发现唯一键(如主键或唯一索引)冲突,即数据已存在,自动转为执行更新操作。确保无论多少并发请求同时到达,只要涉及的关键字段值相同,只会执行一次插入或更新操作。
INSERT INTO table (key_column, other_columns...)
VALUES (value1, value2...)
ON DUPLICATE KEY UPDATE
column_to_update = value_for_update,
another_column = another_value;
需要确保表中有适当的唯一约束(如主键或唯一索引)定义在用于判断数据唯一性的列上。
使用 REPLACE INTO
:
REPLACE INTO
语句在插入时,如果发现存在与新数据主键或唯一索引相同的数据行,会先删除旧数据行,再插入新数据。这样可以保证数据的唯一性,但请注意,它会触发一次删除操作,可能导致额外的行级锁定和事务日志增长,且原数据会被替换,可能会丢失某些历史信息。
REPLACE INTO table (key_column, other_columns...)
VALUES (value1, value2...)
使用 INSERT IGNORE
或 INSERT ... IGNORE ...
:
使用 INSERT IGNORE
可以在遇到唯一键冲突时忽略错误,即不执行插入也不更新,而是直接跳过。这样可以避免重复插入,但同时也可能掩盖其他可能的插入错误。
INSERT IGNORE INTO table (key_column, other_columns...)
VALUES (value1, value2...)
使用 INSERT ... SELECT ... WHERE NOT EXISTS
:
在插入之前先进行一次查询,确认数据不存在后再插入。这种方式需要在应用程序中控制并发,确保查询与插入作为一个原子操作完成,通常通过事务和适当的锁定机制实现。
START TRANSACTION;
IF NOT EXISTS (SELECT * FROM table WHERE key_column = value1)
THEN
INSERT INTO table (key_column, other_columns...)
VALUES (value1, value2...);
END IF;
COMMIT;
注意:在高并发场景下,单纯依赖这种查询+插入的方式可能仍无法避免竞态条件,因为查询和插入之间存在时间窗口,其他并发请求可能在此期间插入相同数据。
应用程序层面的同步控制:
SELECT ... FOR UPDATE
),确保在锁定期间其他并发请求无法插入相同数据。但过度使用悲观锁可能会导致锁争用和性能下降。结合以上方法,并结合具体的业务需求和系统架构,选择最合适的策略来避免高并发下的重复插入问题。在许多场景中,INSERT ... ON DUPLICATE KEY UPDATE
由于其简洁性和内建的并发控制能力,通常是首选方案。如果需要更复杂的业务逻辑或更强的隔离性,可能需要结合应用程序层面的同步控制措施。
场景: 假设是订单表, 外部不断地webhook推送订单进来, 本地数据库订单表shopify_order_id与webhook推送过来的订单信息中shopify_order_id对应
在 Laravel 框架中,可以结合上述提到的 SQL 方案,利用 Eloquent ORM 或查询构造器来处理高并发下的订单插入问题。假设你有一个名为 orders
的表,其中包含一个 shopify_order_id
字段作为唯一标识,且外部 webhook 不断推送订单信息,你需要确保每个订单只被插入一次。这里提供两种常用方法:
firstOrCreate()
或 updateOrCreate()
Eloquent 提供了便捷的方法 firstOrCreate()
和 updateOrCreate()
,它们内部已经封装了查找和插入/更新的操作,可以有效避免并发插入的问题。
use App\Models\Order;
// 接收到 webhook 推送的订单信息
$incomingOrderData = [
'shopify_order_id' => $webhookOrderId,
// 其他订单字段...
];
// 使用 firstOrCreate() 或 updateOrCreate()
$order = Order::updateOrCreate(
['shopify_order_id' => $incomingOrderData['shopify_order_id']], // 查找条件
$incomingOrderData // 插入或更新的数据
);
// $order 现在包含了已存在或新创建的订单实例
firstOrCreate()
:如果找到匹配的 shopify_order_id
,则返回该记录;否则,使用提供的数据创建一个新的记录。updateOrCreate()
:如果找到匹配的 shopify_order_id
,则使用提供的数据更新该记录;否则,创建一个新的记录。这两种方法在内部都会通过查询锁定(如 FOR UPDATE
)来确保在操作期间不会发生并发插入。
insertOrIgnore()
或 upsert()
如果你更倾向于使用查询构造器,可以使用 insertOrIgnore()
或 upsert()
方法:
use Illuminate\Support\Facades\DB;
// 接收到 webhook 推送的订单信息
$incomingOrderData = [
'shopify_order_id' => $webhookOrderId,
// 其他订单字段...
];
// 使用 insertOrIgnore()
DB::table('orders')->insertOrIgnore($incomingOrderData);
// 或者使用 upsert()
DB::table('orders')->upsert(
$incomingOrderData, // 插入或更新的数据
['shopify_order_id'], // 唯一/更新键
// 如果需要更新其他字段,可以在此指定更新的字段和值
// ['column1' => 'value1', 'column2' => 'value2']
);
// 这两个方法都不会返回订单实例,仅负责插入或忽略操作
insertOrIgnore()
:尝试插入数据,如果因唯一键冲突导致插入失败,则忽略错误,不会引发异常。upsert()
:类似于 SQL 中的 INSERT ... ON DUPLICATE KEY UPDATE
,如果找到匹配的 shopify_order_id
,则根据提供的字段列表更新记录;否则,插入新的记录。在使用这些方法时,请确保 shopify_order_id
字段在数据库表中已经被正确地定义为唯一键(主键或唯一索引)。这样,无论是 Eloquent 还是查询构造器的方法,都能在高并发场景下有效地防止重复插入订单。
实现
mysql的
INSERT … ON DUPLICATE KEY UPDATE` 怎么写?在 Laravel 的 Eloquent ORM 中,你可以使用 updateOrCreate()
方法来实现类似 INSERT ... ON DUPLICATE KEY UPDATE
的效果。以下是具体的实现方式:
首先,假设你有一个名为 Order
的 Eloquent 模型,对应 orders
表,其中 shopify_order_id
是唯一的标识字段。当接收到 webhook 推送的订单信息时,你可以这样操作:
use App\Models\Order;
// 接收到 webhook 推送的订单信息
$incomingOrderData = [
'shopify_order_id' => $webhookOrderId,
// 其他订单字段...
];
// 使用 updateOrCreate()
$order = Order::updateOrCreate(
['shopify_order_id' => $incomingOrderData['shopify_order_id']], // 查找条件
$incomingOrderData // 插入或更新的数据
);
// $order 现在包含了已存在或新创建的订单实例
在这个例子中:
updateOrCreate()
方法接受两个参数:
shopify_order_id
作为唯一标识。shopify_order_id
,则会使用这些数据更新已有记录;如果没有找到匹配的记录,则会使用这些数据创建一个新的订单记录。注意:
shopify_order_id
字段在 orders
表中被正确地定义为唯一键(主键或唯一索引),这样才能确保在高并发场景下避免重复插入。updateOrCreate()
方法返回的是一个 Eloquent 模型实例,代表已存在或新创建的订单。你可以继续对这个实例进行其他操作,如保存关联关系、触发事件等。通过这种方式,Eloquent ORM 自动帮你处理了 INSERT ... ON DUPLICATE KEY UPDATE
的逻辑,无需手动编写 SQL 语句。在处理 webhook 推送的订单信息时,只需调用 updateOrCreate()
方法即可确保数据的一致性和完整性。
使用 Clauses
函数实现 ON DUPLICATE KEY UPDATE
:
为了在一次操作中实现类似 INSERT ... ON DUPLICATE KEY UPDATE
的效果,可以使用 GORM 的 Clauses
函数配合自定义 SQL 语句。这样可以确保在高并发场景下避免重复插入:
func processWebhookOrderWithOnDuplicateUpdate(db *gorm.DB, webhookOrderData Order) error {
// 构造 ON DUPLICATE KEY UPDATE 的 SQL 片段
onDuplicateUpdate := fmt.Sprintf("ON DUPLICATE KEY UPDATE %s=VALUES(%s), %s=VALUES(%s)",
db.QuoteColumnName("updated_at"), db.QuoteColumnName("updated_at"),
db.QuoteColumnName("other_field"), db.QuoteColumnName("other_field"))
// 执行带有 ON DUPLICATE KEY UPDATE 的插入操作
result := db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "shopify_order_id"}}, // 唯一键冲突列
DoUpdates: clause.Assignments(map[string]interface{}{
"updated_at": gorm.Expr("VALUES(updated_at)"), // 使用 VALUES() 函数保留插入时的值
"other_field": gorm.Expr("VALUES(other_field)"), // 其他需要更新的字段
}),
}).Create(&webhookOrderData)
return result.Error
}
在这个例子中:
使用 Clauses 函数传入 clause.OnConflict 结构体,指定在唯一键(这里是 shopify_order_id)冲突时的处理方式。
DoUpdates 字段指定了冲突时要更新的字段及其值。这里使用 gorm.Expr(“VALUES(field)”) 保留插入时的值,也可以直接指定新的值。
最后,调用 Create 方法执行带有 ON DUPLICATE KEY UPDATE 逻辑的插入操作。
通过上述方法,GORM 在高并发场景下可以有效地避免订单的重复插入,同时确保数据一致性。使用 Clauses 函数实现 ON DUPLICATE KEY UPDATE 语句的方式更为推荐,因为它在单次数据库操作中完成了插入或更新,减少了竞态条件的发生。