尽管检查是否存在,但重复的 PRIMARY KEY 条目
Duplicate PRIMARY KEY entry despite checking for existence
所以我有一个非常简单的要求并且是 Node/Knex 的新手,我正在努力。我有一个 Web 服务器 运行ning,它接受单个订单的订单数据(电子商务),然后将其写入数据库。我目前正在通过从另一个本地脚本发送请求来在本地对其进行测试,这意味着每秒有多个订单请求到达(我认为这并不重要,但仍提供详细信息)。
订单结构如下:
{
"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 错误 'Duplicate entry for PRIMARY KEY email'。当我尝试保存产品时也会发生这种情况,其中 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
约束定义列(实际上,通过将其设为主键,您实际上已经拥有)它将不允许任何重复条目。然后,如果确实发生冲突,您需要处理由此引发的错误。这让数据库成为单个 'source of truth'。你确实需要做一些反省来区分错误:
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)
所以我有一个非常简单的要求并且是 Node/Knex 的新手,我正在努力。我有一个 Web 服务器 运行ning,它接受单个订单的订单数据(电子商务),然后将其写入数据库。我目前正在通过从另一个本地脚本发送请求来在本地对其进行测试,这意味着每秒有多个订单请求到达(我认为这并不重要,但仍提供详细信息)。
订单结构如下:
{
"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 错误 'Duplicate entry for PRIMARY KEY email'。当我尝试保存产品时也会发生这种情况,其中 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
约束定义列(实际上,通过将其设为主键,您实际上已经拥有)它将不允许任何重复条目。然后,如果确实发生冲突,您需要处理由此引发的错误。这让数据库成为单个 'source of truth'。你确实需要做一些反省来区分错误:
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)