英文:
Prevent GORM giving up INSERT after one duplicate primary key error
问题
我从API中获取数据。获取的结果是结构数组,每个数组包含100个元素。我用以下代码将它们插入到数据库中:
func (ACUpdater) InsertToTable(rawData *models.Contacts, table *gorm.DB) {
for i := 0; i < len(rawData.Results); i++ {
temp := models.ACQ_report{
ID: "", //稍后通过下面的数据字段哈希创建
RecordID: rawData.Results[i].ID,
FirstName: rawData.Results[i].Properties.Firstname,
LastName: rawData.Results[i].Properties.Lastname,
Email: rawData.Results[i].Properties.Email,
PhoneNumber: rawData.Results[i].Properties.Phone,
ContactOwner: rawData.Results[i].Properties.HubspotOwnerID,
CompanyName: rawData.Results[i].Properties.Company,
}
temp.ID = hashContactRecord(&temp)
table.Create(&temp)
fmt.Println(&temp)
fmt.Println(i)
}
}
我使用数据字段的哈希作为表的主键,这样,如果其中任何一个数据字段发生更改,哈希值也会发生变化。这样,我可以将更改后的记录追加到现有表中,而不必担心重复的主键。
问题是,上述函数在遇到1个重复的主键错误后完全放弃了“插入”操作。如果要插入到数据库中的第一条记录是重复的,那么tx.Created(&temp)
仍然会运行,但它没有插入更改后的记录。就好像tx.Create()
在遇到第一个重复的主键错误后就放弃了。
如何修复这个行为?
英文:
I pull data from API. The pull results in struct arrays, each containing 100 elements. The code I use to insert them into DB is:
func (ACUpdater) InsertToTable(rawData *models.Contacts, table *gorm.DB) {
for i := 0; i < len(rawData.Results); i++ {
temp := models.ACQ_report{
ID : "", //to be created later by hashing below data fields
RecordID: rawData.Results[i].ID,
FirstName: rawData.Results[i].Properties.Firstname,
LastName: rawData.Results[i].Properties.Lastname,
Email: rawData.Results[i].Properties.Email,
PhoneNumber: rawData.Results[i].Properties.Phone,
ContactOwner: rawData.Results[i].Properties.HubspotOwnerID,
CompanyName: rawData.Results[i].Properties.Company,
}
temp.ID = hashContactRecord(&temp)
table.Create(&temp)
fmt.Println(&temp)
fmt.Println(i)
}
}
I used the hash of data fields as primary key for the table, so that in case any of those data field changes, the hash also changes. That way, I can append to the existing table the changed record without worrying about duplicate primary keys.
The issue is, the above function totally "gives up" INSERTING
after 1 duplicate primary key error from GORM. If the first record to be inserted into database is a duplicate, then tx.Created(&temp)
will still run, but it did not insert the changed records. As if tx.Create()
gives up after the first duplicate primary key error.
How to fix this behavior?
答案1
得分: 1
这里的第一个问题可能掩盖了实际的根本原因,即您没有检查INSERT
是否返回错误。您可以使用以下代码进行检查:
result := tx.Create(&temp)
if result.Error != nil {
// 处理错误的方式
}
如果您这样做并检查错误,您可能会看到一些情况:
错误:违反唯一约束的重复键值
即使您也看到下面的错误,您可能仍会看到这个错误。在这种情况下,您的INSERT正在执行但失败了。如果您没有看到任何其他错误,并且这个错误只打印一次,那么您可能传递了一个已经链接到DB会话的gorm.DB句柄,并且会在第一个错误处失败。
例如,如评论中所提到的,如果您将db.Table("my_table")
的结果传递给此方法,那么就会出现上述情况。要修复它,改为传递db
或db.NewSession()
,并更新您的方法以指定Table(或Model,以更符合Gorm的风格):
result := db.Table("my_table_name").Create(&temp)
if result.Error != nil {
// ...
}
选项2:错误:当前事务已中止,直到事务块结束后,命令将被忽略
如果您看到这个错误,意味着您的方法在一个事务中运行其插入操作。虽然这对您来说并不是问题,但由于这是一个通用的论坛,我将在此处保留以下解释:在Postgres中,如果事务中的任何语句失败,您将无法执行任何进一步的语句,除非执行ROLLBACK。
要解决这个问题,您有几个选项:
-
在尝试插入之前进行更多的数据验证,以确保每个插入都能成功。您还可以使用Gorm的批量插入功能来优化此方法的插入操作。
-
不使用事务。如果您可以接受跳过某些行,并且不担心重复数据,这是一个合理的选择。
-
使用SAVEPOINTs。在Postgres中,SAVEPOINT类似于事务中的检查点,您可以回滚到该检查点,而不是回滚整个事务,这正是您想要的:
tx.SavePoint("sp1") // SAVEPOINT sp1;
result := tx.Create(&temp)
if result.Error != nil {
tx.RollbackTo("sp1") // ROLLBACK TO sp1;
}
英文:
The first issue here, which may be covering up the actual root cause, is that you're not checking to see if the INSERT
is returning an error. You would do that with:
result := tx.Create(&temp)
if result.Error != nil {
// handle it somehow
}
If you do this and inspect the error, you might see a couple things:
ERROR: duplicate key value violates unique constraint
You will likely see this even if you also see the error below. In this case, your INSERT is executing, but failing. If you do not see any other errors and this only prints once, then you probably passed in a gorm.DB handle that's been chained off a DB session and will fail at the first error.
For example, as mentioned in the comments, if you passed in the result of db.Table("my_table")
to this method, that would be in te situation described above. To fix it, pass instead either just db
or db.NewSession()
, and update your method to specify Table (or Model, to be more Gorm-like):
result := db.Table("my_table_name").Create(&temp)
if result.Error != nil {
// ...
}
Option 2: ERROR: current transaction is aborted, commands ignored until end of transaction block
If you see this, it means your method is running its inserts in a transaction. This proved not to be the case for you, but since this a general forum, I'll leave this and the below explanation here: In Postgres, if any statement fails inside a transaction, you can't execute any further statements except a ROLLBACK.
To resolve this, you have a few options:
-
Do more data validation before attempting the insert, to the point where you can reliably expect every insert to succeed. You could also use Gorm's batch insert functionality to optimize the insert with this approach.
-
Do not use a transaction. If you're okay with skipped rows, and not worried about duplicates, this is a reasonable option.
-
Use SAVEPOINTs. In Postgres, a SAVEPOINT is a like a checkpoint in a transaction that you can roll back to instead of rolling back the entire transaction, which is pretty much what you want:
tx.SavePoint("sp1") // SAVEPOINT sp1;
result := tx.Create(&temp)
if result.Error != nil {
tx.RollbackTo("sp1") // ROLLBACK TO sp1;
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论