Я обнаружил что-то, что не могу обернуть голову при работе с Postgres и Go GORM.
В следующем фрагменте кода я пытаюсь найти, удалить и создать тот же элемент, но в двух разных транзакциях, однако я начинаю вторую транзакцию и нахожу элемент ДО совершения первой транзакции.
Во второй транзакции происходит нечто странное. Когда я вызываю t2.Delete()
, он не удаляет строки. В базе данных после вызова t1.Commit()
первичный ключ элемента становится 2
, но поскольку он уже получил старый элемент, он удаляет для первичного ключа 1
.
Почему видит ли транзакция 2 новую строку, несмотря на то, что она была создана ДО фиксации первой?
db, err := gorm.Open("postgres", "host=localHost port=5432 user=postgres dbname=test password=postgres sslmode=disable")
defer db.Close()
db.DropTableIfExists(&Product{})
db.AutoMigrate(&Product{})
db.LogMode(true)
db.Create(&Product{Code: "A", Price: 1000})
// SQL: INSERT INTO "products" ("code","price") VALUES ('A',1000) RETURNING "products"."id"
// Start transaction 1, find item, delete it
t1 := db.Begin()
product := &Product{}
err = t1.Find(product, "code = ?", "A").Error
// SQL: SELECT * FROM "products" WHERE (code = 'A')
err = t1.Delete(product).Error
// SQL: DELETE FROM "products" WHERE "products"."id" = 1
// Start transaction 2 and get item, before transaction 1 creates new item and commits
t2 := db.Begin()
product2 := &Product{}
err = t2.Find(product2, "code = ?", "A").Error
// SQL: SELECT * FROM "products" WHERE (code = 'A')
err = t1.Create(&Product{Code: "A", Price: 3000}).Error
// SQL: INSERT INTO "products" ("code","price") VALUES ('A',3000) RETURNING "products"."id"
err = t1.Commit().Error
// Database now contains
err = t2.Delete(product2).Error
// SQL: DELETE FROM "products" WHERE "products"."id" = 1
// [0 rows affected or returned ]
err = t2.Save(&Product{Code: "A", Price: 4000}).Error
// SQL: INSERT INTO "products" ("code","price") VALUES ('A',4000) RETURNING "products"."id"
// ERROR HERE: pq: duplicate key value violates unique constraint "products_code_key"
err = t2.Commit().Error
Примечание: GORM использует по умолчанию Read Committed isolation level
. Я знаю о проблемах целостности, которые это могло бы вызвать, если бы это работало правильно. Вместо этого я буду использовать Serializable
, что приведет к ошибке в Commit или заблокирует Get, если указано "FOR UPDATE;"