knex.js - 尽管检查存在重复 PRIMARY KEY 条目
问题描述
所以我有一个非常简单的要求,并且是 Node/Knex 的新手,我正在苦苦挣扎。我有一个正在运行的 Web 服务器,它接受单个订单的订单数据(电子商务),然后将其写入数据库。我目前正在通过从另一个本地脚本发送请求来在本地对其进行测试,这意味着每秒有多个订单请求到达(我认为这并不重要,但仍然提供详细信息)。
这是订单结构的样子:
{
"id": 123,
"created_at": "date here",
"product" : {
"sku": "sku1",
"name": "Product 1",
"description" : "description here",
// and so on
},
"contact": {
"email" : "me@me.com"
"first_name" : "First",
// and so on
}
}
这是我处理订单数据的方式:
// express app
app.post('/write-db-order', (req, res) => {
const order = req.body.order;
// check if order already exists
knex('orders')
.where('id', order.id)
.select('id')
.then((temp) => {
if (temp.length == 0) {
saveOrder(order);
}
});
res.sendStatus(200);
});
该saveOrder()
函数如下所示:
function saveOrder(order) {
knex('orders').insert({
id: order.id,
title: order.title,
// other fields
}).then(() => {
saveOrderItems(order);
saveOrderShippingInfo(order);
saveOrderContact(order);
}).catch(error => console.log(error));
}
因此,基本订单详细信息首先被插入到数据库中,然后我将保存其余的详细信息。这是saveOrderContact()
函数的样子:
function saveOrderContact(order) {
let contact = order.contact;
if(!contact) {
return;
}
knex('contacts')
.where('email', contact.email)
.select('email')
.then((result) => {
if (result.length == 0) {
knex('contacts').insert({
email: contact.email,
first_name: contact.first_name,
last_name: contact.last_name,
company_name: contact.company_name,
job_title: contact.job_title
}).then(() => {}).catch(error => {
console.log(error);
});;
}
});
}
在典型的试运行中,大约 1000-1200 个订单被发送到此服务器,其中一些联系人和产品预计会包含重复项。这就是为什么saveOrderContact()
我检查主键(电子邮件),测试联系人是否已经存在。
无论如何,重点是,我遇到了许多 SQL 错误,上面写着“主键电子邮件的重复条目”。当我尝试保存产品时也会发生这种情况,其中sku
是主键(不过,我没有在此处包含代码,因为它类似于上面的函数)。
对我来说,Knex 似乎有奇怪的缓存行为,它一次性将所有插入发送到数据库并导致这些重复。但即便如此,我很惊讶select
在插入之前进行检查不起作用。我知道 Node 的异步特性,这就是为什么我在里面添加了插入,then()
但看起来我错过了一些东西。
这是 Node/Knex 中的常见问题吗?如果是,你如何避免它?如果没有,好吧,请帮助我!:-)
解决方案
如果您连续快速地向服务器发送大量请求,那么您很可能会遇到竞争条件。该contacts
行很可能是在检查它和尝试插入它之间插入的。
值得注意的是,这些请求:
}).then(() => {
saveOrderItems(order);
saveOrderShippingInfo(order);
saveOrderContact(order);
}).catch(error => console.log(error));
将在很短的时间内相互触发(执行不会等到 promise 被解决。另一种表达相同事物但严格顺序的方式是这样的:
.then(() => saveOrderItems(order))
.then(() => saveOrderShippingInfo(order))
.then(() => saveOrderContact(order))
如果联系人已经退出,则可以让插入失败。如果您使用约束定义列UNIQUE
(实际上,通过将其设为主键,您实际上已经拥有),它将不允许任何重复条目。然后,如果确实发生冲突,您需要处理引发的错误。这让数据库成为唯一的“事实来源”。你必须做一些自省来区分错误:
knex('contacts')
.insert({
email: 'foo@example.com'
// ...
})
.catch(e => {
// Example for Postgres, where code 23505 is a unique_violation
if (e.code && e.code === '23505') {
return
}
console.error('Something bad happened while inserting a contact.')
})
这默默地吞下了独特的约束违规。允许程序继续执行。不幸的是,它是特定于数据库的。它可能比检查联系人然后插入的查询便宜,并且降低了竞争条件的风险。
或者,您可以使用可以说更优雅的原始 SQL(假设 Postgres):
const insert = knex('contacts').insert({ email: foo@example.com })
knex.raw('? ON CONFLICT DO NOTHING', [insert]).then(console.log)
推荐阅读
- ios - 表视图数据被覆盖
- javascript - 坦克!Unity 目前正在尝试添加一个健康拾取项目
- python - 使用正则表达式从熊猫系列字符串中删除单词
- java - 无法延迟初始化角色集合。简单的 JPA findById
- c# - 拦截和修改请求体
- python - Python3 - 在yaml中写回相同的流
- python - 如何使用 Dask 进行更快的语言检测?
- postgresql - 无法根据 PostgreSQL 中另一个表中的数据过滤一个表的行
- python - Python Django 登录 URL 和视图 - 安全方法
- java - Android包含参数 - 数据绑定不解析变量