Изменения просмотра транзакций в строках базы данных, сделанные после ее начала - PullRequest
0 голосов
/ 21 февраля 2020

Я обнаружил что-то, что не могу обернуть голову при работе с 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;"

1 Ответ

1 голос
/ 21 февраля 2020

Из PostgreSQL документов

Read Committed - уровень изоляции по умолчанию в PostgreSQL. Когда транзакция использует этот уровень изоляции, запрос SELECT (без предложения FOR UPDATE / SHARE) видит только данные, зафиксированные до начала запроса; он никогда не видит ни незафиксированные данные, ни изменения, зафиксированные во время выполнения запроса параллельными транзакциями. По сути, запрос SELECT видит моментальный снимок базы данных на момент начала выполнения запроса.

...

Также обратите внимание, что две последовательные команды SELECT могут видеть разные данные, даже если они находятся в одной транзакции, если другие транзакции фиксируют изменения после первой SELECT запускается и до начала второго SELECT.

Второй абзац, кажется, подводит итог вашей ситуации. Любой «SELECT» в t2, который запускается после фиксации t1, может видеть обновления, примененные t1.

...