Gorm事務有error卻不返回會發生什么
Gorm包是大家比較高頻使用。正常的用法是,如果有失敗返回error,整體rollback,如果不返回error則commit。下面是Transaction的源碼:
// Transaction start a transaction as a block, return error will rollback, otherwise to commit. Transaction executes an
// arbitrary number of commands in fc within a transaction. On success the changes are committed; if an error occurs
// they are rolled back.
func (db *DB) Transaction(fc func(tx *DB) error, opts ...*sql.TxOptions) (err error) {panicked := trueif committer, ok := db.Statement.ConnPool.(TxCommitter); ok && committer != nil {// nested transactionif !db.DisableNestedTransaction {err = db.SavePoint(fmt.Sprintf("sp%p", fc)).Errorif err != nil {return}defer func() {// Make sure to rollback when panic, Block error or Commit errorif panicked || err != nil {db.RollbackTo(fmt.Sprintf("sp%p", fc))}}()}err = fc(db.Session(&Session{NewDB: db.clone == 1}))} else {tx := db.Begin(opts...)if tx.Error != nil {return tx.Error}defer func() {// Make sure to rollback when panic, Block error or Commit errorif panicked || err != nil {tx.Rollback()}}()if err = fc(tx); err == nil {panicked = falsereturn tx.Commit().Error}}panicked = falsereturn
}
神奇用法
但如果我瞎搞呢?如果有error我仍然返回nil,會發生什么?
err := db.Transaction(func(tx *gorm.DB) error {var err errorisInserted, err = d.TxCreate(tx, record)if err != nil {return nil}err = d.TxUpdate(tx, opt)if err != nil {return errors.Wrap(err, "failed to update")}return nil})
解釋
這種情況下,本質看MySQL自身的設計。事務具有原子性,理論上應該要么全部成功,要么全部失敗。但如果在一條語句失敗后執行COMMIT
,MySQL 會盡力提交已成功執行的語句。例如:
START TRANSACTION;
INSERT INTO test (id, value) VALUES (2, 'data3');
INSERT INTO test (id, value) VALUES (3, 'data4');
-- 故意讓這條語句失敗(假設id沖突)
INSERT INTO test (id, value) VALUES (2, 'data5');
COMMIT;
在上述例子中,前兩條INSERT
語句可能已經成功執行并修改了數據庫,而第三條失敗。執行COMMIT
后,前兩條語句的修改會被永久保存到數據庫中,而失敗的語句不會回滾整個事務。
其實十分不建議這么用。數據庫狀態可能會出現部分操作生效,部分未生效的情況,破壞了事務的原子性。這可能導致數據不一致問題,例如業務邏輯上要求某些數據必須同時存在或不存在,但由于這種部分提交的情況,無法保證這種一致性。
MySQL表數據時間的記錄時刻
假設數據庫是這樣的
CREATE TABLE `conversation_new_message` (`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',`conversation_new_id` bigint unsigned NOT NULL COMMENT '自增id',`message` json DEFAULT NULL COMMENT 'conversation history, a json map',`turn` bigint NOT NULL COMMENT '輪數',`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '上一次更新時間',PRIMARY KEY (`id`),
) ENGINE=InnoDB
起一個事務,按照下面的時間點執行,最終的創建時間是多少?是按照insert時刻的時間來的(不同記錄不一樣),還是按照真正落庫的時間來的(不同記錄一樣)?
發現是按照真正寫入的時間來的。其實也比較好理解,雖然我們沒有直接賦值,但CURRENT_TIMESTAMP本身在記錄創建的時候就已經設置好值了。