目录
解决 GORM 操作 MySQL 时的 Error 1170 和 Error 1062 错误
一、问题背景
在使用 GORM 操作 MySQL 数据库时,遇到了两个常见错误:
- Error 1170:
BLOB/TEXT column 'xxx' used in key specification without a key length
- Error 1062:
Duplicate entry 'xxx' for key 'xxx'
本文结合具体代码和场景,详细记录问题成因及解决方案。
二、Error 1170:TEXT 字段索引问题
2.1 错误原因
- 现象:尝试对
TEXT
或LONGTEXT
类型字段创建索引,但 MySQL 不允许直接对这类大文本类型加索引(需指定索引长度)。 - 代码场景:
- 数据库表中字段类型为
VARCHAR
,但 GORM 模型未正确声明类型,导致自动推断为TEXT
。 - 例如:模型中
Username
字段未加gorm:"type:varchar(255)"
标签,GORM 默认推断为TEXT
。
- 数据库表中字段类型为
2.2 解决方案
2.2.1 修改模型(Model)添加 GORM 标签
type User struct {
Username string `gorm:"type:varchar(255);uniqueIndex;not null" json:"username"` // 关键:指定varchar类型和唯一索引
// 其他字段...
}
type:varchar(255)
:强制 GORM 使用VARCHAR
类型,避免推断为TEXT
。uniqueIndex
:添加唯一索引约束(等价于数据库UNIQUE INDEX
)。
2.2.2 同步数据库表结构
-- 删除旧表(如果存在)
DROP TABLE IF EXISTS users;
-- 创建新表(确保字段类型为VARCHAR)
CREATE TABLE IF NOT EXISTS users (
username VARCHAR(255) NOT NULL UNIQUE, -- 与模型一致
-- 其他字段...
INDEX idx_username (username) -- 索引自动适配VARCHAR
) ENGINE=InnoDB;
三、Error 1062:唯一键冲突问题
3.1 错误原因
- 现象:插入数据时违反唯一索引约束(如用户名、邮箱重复)。
- 常见场景:
- 测试用例中使用固定用户名(如
testuser
),多次执行导致重复。 - 数据库已有相同数据(如历史测试残留)。
- 测试用例中使用固定用户名(如
3.2 解决方案
3.2.1 测试数据唯一性处理
-
方案 1:生成随机用户名
// 测试用例中动态生成唯一用户名 username := fmt.Sprintf("testuser_%d", time.Now().UnixNano())
-
方案 2:使用数据库事务回滚(测试环境)
func TestUserDAO_Create(t *testing.T) { db := setupTestDB() // 开启事务 defer db.Rollback() // 测试结束后自动回滚,不保存数据 user := &model.User{Username: "testuser"} // 执行创建操作... }
3.2.2 数据库层预防
- 在模型中声明唯一索引(同 Error 1170 解决方案),利用 GORM 提前校验唯一性:
type User struct { Email string `gorm:"type:varchar(255);uniqueIndex;not null" json:"email"` // 邮箱唯一索引 // 其他字段... }
四、完整修复流程总结
4.1 模型定义最佳实践
package model
import (
"time"
"gorm.io/gorm"
)
type User struct {
ID uint `json:"id" gorm:"primaryKey"`
Username string `json:"username" gorm:"type:varchar(255);uniqueIndex;not null"`
Password string `json:"-" gorm:"type:varchar(255);not null"` // 存储bcrypt哈希(60字符)
Email string `json:"email" gorm:"type:varchar(255);uniqueIndex;not null"`
Role string `json:"role" gorm:"type:enum('admin','editor','user');default:user"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime:milli"` // 自动生成创建时间(毫秒级)
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime:milli"` // 自动更新时间(毫秒级)
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` // 软删除字段(带索引)
}
4.2 数据库表结构脚本
CREATE DATABASE IF NOT EXISTS user_management DEFAULT CHARACTER SET utf8mb4;
USE user_management;
CREATE TABLE IF NOT EXISTS users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
role ENUM('admin', 'editor', 'user') DEFAULT 'user',
created_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3),
updated_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
deleted_at TIMESTAMP(3) NULL,
INDEX idx_deleted_at (deleted_at)
) ENGINE=InnoDB;
4.3 测试数据生成(存储过程)
DELIMITER $$
CREATE PROCEDURE GenerateUserData()
BEGIN
DECLARE i INT DEFAULT 0;
WHILE i < 100 DO
INSERT INTO users (username, password, email, role) VALUES (
CONCAT('user_', UUID()), -- 唯一用户名
'$2a$10$DWbP8QH...', -- 预先生成的bcrypt哈希
CONCAT('user_', i, '@example.com'),
ELT(FLOOR(1 + RAND() * 3), 'admin', 'editor', 'user')
);
SET i = i + 1;
END WHILE;
END$$
DELIMITER ;
五、总结
- GORM 标签的重要性:通过
gorm:"type:xxx"
显式指定字段类型,避免自动推断导致的类型不匹配(如string
→TEXT
)。 - 唯一约束的两层校验:模型层(GORM 标签)+ 数据库层(索引)共同确保数据唯一性,提前拦截冲突。
- 测试数据管理:测试中使用随机值或事务回滚,避免固定值引发的重复问题。
通过以上方案,可有效解决 GORM 操作 MySQL 时的索引和唯一键冲突问题,提升代码健壮性。