主键自增错误

huangapple go评论91阅读模式
英文:

Primary key increment wrong

问题

我在使用GORM和Gin创建数据库模型时遇到了问题。

这是我的控制器中的代码:

func CreateSymbol(c *gin.Context) {
	var payload models.Symbol
	if err := c.ShouldBind(&payload); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
		return
	}
	fmt.Println(payload)

	symbol, err := repositories.CreateSymbol(payload)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{"data": symbol})
}

这是我的存储库中的函数:

func CreateSymbol(model models.Symbol) (models.Symbol, error) {
	result := boot.DB.Create(&model)

	if result.Error != nil {
		return models.Symbol{}, result.Error
	}

	return model, nil
}

这是我的模型和迁移:

type Symbol struct {
	gorm.Model
	Code        string
	Icon        string
	Status      string
	MaxLeverage uint32
	Precision   uint32
	MinQty      float64
}
type Symbol struct {
	gorm.Model
	Code        string `gorm:"index;unique"`
	Precision   uint32
	MaxLeverage uint32
	MinQty      float64
	Icon        string
	Status      string `gorm:"index"`
}

这是数据库中的最后一条记录:

{
    "ID": 11,
    "CreatedAt": "2023-02-22T14:51:27.52Z",
    "UpdatedAt": "2023-02-22T14:51:27.52Z",
    "DeletedAt": null,
    "Code": "KLC2",
    "Icon": "/images/klc1.png",
    "Status": "inactive",
    "MaxLeverage": 20,
    "Precision": 4,
    "MinQty": 0
}

当我完成代码并从Postman运行时,一切都正常,直到我意外地创建了另一个具有重复Code字段的符号。错误返回如下:

{
    "message": "Error 1062 (23000): Duplicate entry 'KLC2' for key 'symbols.code'"
}

然后,我更改了请求数据。错误消失了,但是符号的ID变为了13,而我期望的是12。

{
    "data": {
        "ID": 13,
        "CreatedAt": "2023-02-22T16:08:08.827Z",
        "UpdatedAt": "2023-02-22T16:08:08.827Z",
        "DeletedAt": null,
        "Code": "KLC3",
        "Icon": "/images/klc3.png",
        "Status": "inactive",
        "MaxLeverage": 20,
        "Precision": 4,
        "MinQty": 0
    }
}

我不确定我做错了什么。

英文:

I have an issue when I try to create a model in the database using GORM and Gin.

This is my code in the controller:

func CreateSymbol(c *gin.Context) {
	var payload models.Symbol
	if err := c.ShouldBind(&payload); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
		return
	}
	fmt.Println(payload)

	symbol, err := repositories.CreateSymbol(payload)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{"data": symbol})
}

And this is my function in the repository:

func CreateSymbol(model models.Symbol) (models.Symbol, error) {
	result := boot.DB.Create(&model)

	if result.Error != nil {
		return models.Symbol{}, result.Error
	}

	return model, nil
}

And this is my model and migration:

type Symbol struct {
	gorm.Model
	Code        string
	Icon        string
	Status      string
	MaxLeverage uint32
	Precision   uint32
	MinQty      float64
}
type Symbol struct {
	gorm.Model
	Code        string `gorm:"index;unique"`
	Precision   uint32
	MaxLeverage uint32
	MinQty      float64
	Icon        string
	Status      string `gorm:"index"`
}

This is my last record in the database:

        {
            "ID": 11,
            "CreatedAt": "2023-02-22T14:51:27.52Z",
            "UpdatedAt": "2023-02-22T14:51:27.52Z",
            "DeletedAt": null,
            "Code": "KLC2",
            "Icon": "/images/klc1.png",
            "Status": "inactive",
            "MaxLeverage": 20,
            "Precision": 4,
            "MinQty": 0
        }

When I completed my code and ran it from Postman, everything seemed OK until I accidentally create another symbol that duplicate Code field. The error was returned:

{
    "message": "Error 1062 (23000): Duplicate entry 'KLC2' for key 'symbols.code'"
}

Then, I changed the request data. The error is gone, but the symbol was created with ID is 13 that I expected is 12.

{
    "data": {
        "ID": 13,
        "CreatedAt": "2023-02-22T16:08:08.827Z",
        "UpdatedAt": "2023-02-22T16:08:08.827Z",
        "DeletedAt": null,
        "Code": "KLC3",
        "Icon": "/images/klc3.png",
        "Status": "inactive",
        "MaxLeverage": 20,
        "Precision": 4,
        "MinQty": 0
    }
}

I am not sure what I am doing wrong.

答案1

得分: 1

我假设你正在使用 PostgreSQL。

这并不是一个问题。你描述的行为正如它所宣传的那样,也就是完全按照规范进行的。换句话说,在 PostgreSQL 中,这就是 sequences 的工作方式,而 serial 类型 的列以及新的 GENERATED ... AS IDENTITY 列都使用了序列。

请阅读 https://www.postgresql.org/docs/15/functions-sequence.html 中的 CAUTION 部分:

> 为了避免阻塞从同一序列获取数字的并发事务,如果调用事务稍后中止,则 nextval 获取的值不会被重新使用。这意味着事务中止或数据库崩溃可能导致分配值序列中的间隙。这也可能发生在没有事务中止的情况下。例如,带有 ON CONFLICT 子句的 INSERT 在检测到导致其遵循 ON CONFLICT 规则的冲突之前,将计算要插入的元组,包括执行任何必需的 nextval 调用。因此,PostgreSQL 序列对象不能用于获取“无间隙”的序列。
>
> . . .


如果你正在使用 MySQL,我对它的了解较少,但描述的行为虽然可能实现方式不同,但也可能是正常的。

英文:

I'm going to assume that you're using PostgreSQL.

There is no issue. The behaviour you described is as advertised, i.e. exactly according to the specification. In other words, that is how sequences work in PostgreSQL, and a sequence is what is used by columns with serial types and also by the new GENERATED ... AS IDENTITY columns.

Read the CAUTION block in https://www.postgresql.org/docs/15/functions-sequence.html

> To avoid blocking concurrent transactions that obtain numbers from the same sequence, the value obtained by nextval is not reclaimed for re-use if the calling transaction later aborts. This means that transaction aborts or database crashes can result in gaps in the sequence of assigned values. That can happen without a transaction abort, too. For example an INSERT with an ON CONFLICT clause will compute the to-be-inserted tuple, including doing any required nextval calls, before detecting any conflict that would cause it to follow the ON CONFLICT rule instead. Thus, PostgreSQL sequence objects cannot be used to obtain “gapless” sequences.
>
> . . .


If you're using MySQL, of which I have less knowledge, the described behaviour, although probably implemented differently, is also likely normal.

答案2

得分: -1

Gorm在故障之前会提前增加序列号。这是Gorm的正常行为,它将序列号保存在一个单独的表中。

即使将操作放在事务中,也无法阻止gorm/数据库积极地分配键。

如果你真的需要没有间隙的连续键,你需要自己管理ID分配,并在创建对象的同一事务中分配键。

以下代码片段可以实现这一点。


    type TestObj struct {
    	gorm.Model
    	Key string `gorm:"uniqueIndex"`
    }
    
    type IdTable struct {
    	ID    string `gorm:"uniqueIndex primaryKey"`
    	Value uint
    }
    func InsertTestObj(db *gorm.DB, obj TestObj) (TestObj, error) {
    	err := db.Transaction(func(tx *gorm.DB) error {
    		idRow := IdTable{ID: "test_objs"}
    		tx.Find(&idRow)
    		idRow.Value++
    		if err := tx.Save(&idRow).Error; err != nil {
    			fmt.Printf("Error on %v %v\n", idRow, err)
    			return err
    		}
    		obj.ID = idRow.Value
    		if err := tx.Save(&obj).Error; err != nil {
    			fmt.Printf("Error on %s %v\n", obj.Key, err)
    			return err
    		}
    		return nil
    	})
    	if err != nil {
    		return TestObj{}, err
    	}
    	return obj, nil
    }

如果我在测试用例中运行它:

    func TestInsertTestObj(t *testing.T) {
    	db := DbConnect()
    	assert.NotNil(t, db)
    
    	o1, err := InsertTestObj(db, TestObj{Key: "val1"})
    	assert.Nil(t, err)
    	fmt.Println(o1.ID)
    	o2, err := InsertTestObj(db, TestObj{Key: "val2"})
    	assert.Nil(t, err)
    	fmt.Println(o2.ID)
    	o3, err := InsertTestObj(db, TestObj{Key: "val3"})
    	assert.Nil(t, err)
    	fmt.Println(o3.ID)
    	o4, err := InsertTestObj(db, TestObj{Key: "val3"})
    	assert.NotNil(t, err)
    	fmt.Println(o4.ID)
    	o5, err := InsertTestObj(db, TestObj{Key: "val4"})
    	assert.Nil(t, err)
    	fmt.Println(o5.ID)
    }

我得到以下输出:

1
2
3
Error on val3 ERROR: duplicate key value violates unique constraint "idx_test_objs_key" (SQLSTATE 23505)
0
4
英文:

Gorm will have increase the sequence number in advance of the failure. This is normal behavior for GORM and it keeps the sequence in a separate table.

Even putting the operation in a transaction will not prevent gorm/your database from agressively allocating keys.

If you really need sequntial keys with no gaps you would have to manage the ID allocation yourself and allocate the key in the same transaction as you create the object.

The following snippet will do that.


    type TestObj struct {
    	gorm.Model
    	Key string `gorm:"uniqueIndex"`
    }
    
    type IdTable struct {
    	ID    string `gorm:"uniqueIndex primaryKey"`
    	Value uint
    }
    func InsertTestObj(db *gorm.DB, obj TestObj) (TestObj, error) {
    	err := db.Transaction(func(tx *gorm.DB) error {
    		idRow := IdTable{ID: "test_objs"}
    		tx.Find(&idRow)
    		idRow.Value++
    		if err := tx.Save(&idRow).Error; err != nil {
    			fmt.Printf("Error on %v %v\n", idRow, err)
    			return err
    		}
    		obj.ID = idRow.Value
    		if err := tx.Save(&obj).Error; err != nil {
    			fmt.Printf("Error on %s %v\n", obj.Key, err)
    			return err
    		}
    		return nil
    	})
    	if err != nil {
    		return TestObj{}, err
    	}
    	return obj, nil
    }

If I run that in a test case:

    func TestInsertTestObj(t *testing.T) {
    	db := DbConnect()
    	assert.NotNil(t, db)
    
    	o1, err := InsertTestObj(db, TestObj{Key: "val1"})
    	assert.Nil(t, err)
    	fmt.Println(o1.ID)
    	o2, err := InsertTestObj(db, TestObj{Key: "val2"})
    	assert.Nil(t, err)
    	fmt.Println(o2.ID)
    	o3, err := InsertTestObj(db, TestObj{Key: "val3"})
    	assert.Nil(t, err)
    	fmt.Println(o3.ID)
    	o4, err := InsertTestObj(db, TestObj{Key: "val3"})
    	assert.NotNil(t, err)
    	fmt.Println(o4.ID)
    	o5, err := InsertTestObj(db, TestObj{Key: "val4"})
    	assert.Nil(t, err)
    	fmt.Println(o5.ID)
    }

I get the following output

1
2
3
Error on val3 ERROR: duplicate key value violates unique constraint "idx_test_objs_key" (SQLSTATE 23505)
0
4

huangapple
  • 本文由 发表于 2023年2月23日 00:11:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/75535364.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定