如何解决非主键字段触发MySql Duplicate field异常
解决MySQL插入非主键字段报Duplicate Field错误的方案
兄弟,这个问题我太熟了!咱们一步步拆解原因,然后给你搞定:
核心原因分析
你遇到的Duplicate field错误,本质不是代码层面的问题——大概率是你的数据库daily_production表的Date字段被设置了「唯一约束(UNIQUE KEY)」。哪怕它不是主键,只要有唯一约束,数据库就不允许插入重复的Date值,这才是报错的根源。
另外你的DataGridView重复检查逻辑也有两个小漏洞:
- 找到重复时没把
bolDuplicateWasFound设为True,逻辑不严谨(不过你用了Exit Sub,暂时没影响,但还是得修正); - 双重循环会重复检查同一对行,效率太低,而且没考虑数据库里已经存在的旧数据——你只检查了DataGridView内部的重复,没检查数据库里的现有记录,这才是最容易触发报错的点!
具体解决步骤
1. 先确认数据库的唯一约束
登录MySQL,执行这条命令查看表结构:
SHOW CREATE TABLE daily_production;
看输出里有没有类似UNIQUE KEY Date (Date)的语句:
- 如果有:说明这个字段确实有唯一约束。如果业务上允许重复Date,就删除约束:
如果业务要求Date必须唯一,那咱们就继续往下优化代码,确保插入的Date在数据库中是唯一的。ALTER TABLE daily_production DROP INDEX Date;
2. 修复DataGridView内部的重复检查逻辑
把你原来的双重循环改成用HashSet做单次遍历,效率更高,逻辑更清晰:
Dim bolDuplicateWasFound As Boolean = False Dim existingDates As New HashSet(Of String)() For x As Integer = 0 To DataGridView1.Rows.Count - 1 ' 跳过DataGridView的空行(如果允许新增行的话) If DataGridView1.Rows(x).IsNewRow Then Continue For Dim currentDate As String = DataGridView1.Rows(x).Cells("Date").Value.ToString() If existingDates.Contains(currentDate) Then MessageBox.Show("Duplicate Found: " & currentDate) bolDuplicateWasFound = True Exit For Else existingDates.Add(currentDate) End If Next If bolDuplicateWasFound Then Exit Sub End If
3. 添加数据库现有数据的重复检查
只检查DataGridView内部还不够,必须确认要插入的Date没在数据库里存在过。这里用批量查询的方式,效率更高:
' 先收集所有要插入的Date Dim datesToInsert As New List(Of String)() For x As Integer = 0 To DataGridView1.Rows.Count - 1 If DataGridView1.Rows(x).IsNewRow Then Continue For datesToInsert.Add(DataGridView1.Rows(x).Cells("Date").Value.ToString()) Next ' 检查数据库中是否存在这些Date If datesToInsert.Count > 0 Then Dim checkCommand As New MySqlCommand() checkCommand.Connection = db.getConnection ' 构造参数化查询(如果Date是VARCHAR类型),避免SQL注入 Dim paramPlaceholders As String = String.Join(",", Enumerable.Repeat("@date", datesToInsert.Count)) checkCommand.CommandText = $"SELECT Date FROM daily_production WHERE Date IN ({paramPlaceholders})" ' 添加参数 For i As Integer = 0 To datesToInsert.Count - 1 checkCommand.Parameters.Add($"@date{i}", MySqlDbType.VarChar).Value = datesToInsert(i) Next Try db.getConnection.Open() Dim reader As MySqlDataReader = checkCommand.ExecuteReader() While reader.Read() Dim duplicateDate As String = reader("Date").ToString() MessageBox.Show("Date already exists in database: " & duplicateDate) bolDuplicateWasFound = True End While reader.Close() Finally If db.getConnection.State = ConnectionState.Open Then db.getConnection.Close() End If End Try If bolDuplicateWasFound Then Exit Sub End If End If
注意:如果你的Date字段是MySQL的DATE类型,记得把MySqlDbType.VarChar改成MySqlDbType.Date,避免字符串转换问题。
4. 插入时添加异常捕获(兜底方案)
哪怕做了前面的检查,还是可能因为并发操作(比如别人同时插入了相同的Date)导致报错,所以必须加异常处理:
Try db.getConnection.Open() Dim command As MySqlCommand For i As Integer = 0 To DataGridView1.Rows.Count - 1 If DataGridView1.Rows(i).IsNewRow Then Continue For command = New MySqlCommand("INSERT INTO `daily_production`(`Year`, `Month`, `Date`, `White`, `Yellow`) VALUES (@yea,@mon,@dat,@whit,@yel)", db.getConnection) command.Parameters.Add("@yea", MySqlDbType.VarChar).Value = DataGridView1.Rows(i).Cells(0).Value.ToString() command.Parameters.Add("@mon", MySqlDbType.VarChar).Value = DataGridView1.Rows(i).Cells(1).Value.ToString() command.Parameters.Add("@dat", MySqlDbType.VarChar).Value = DataGridView1.Rows(i).Cells(2).Value.ToString() command.Parameters.Add("@whit", MySqlDbType.VarChar).Value = DataGridView1.Rows(i).Cells(3).Value.ToString() command.Parameters.Add("@yel", MySqlDbType.VarChar).Value = DataGridView1.Rows(i).Cells(4).Value.ToString() command.ExecuteNonQuery() Next MessageBox.Show("Data Inserted Successfully", "TDP Thermoline", MessageBoxButtons.OK, MessageBoxIcon.Information) Catch ex As MySqlException If ex.Number = 1062 Then ' MySQL重复键错误的代码 MessageBox.Show("Duplicate Date found in database. Please check your data.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error) Else MessageBox.Show("Insert failed: " & ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error) End If Finally ' 确保连接关闭,不要随便Dispose(除非你确定以后不再用这个连接对象) If db.getConnection.State = ConnectionState.Open Then db.getConnection.Close() End If End Try
最后提醒
- 不要随便调用
db.getConnection.Dispose(),如果你的连接是从连接池获取的,Close()就足够了,Dispose()会直接销毁连接,影响连接池的复用。 - 如果
Year和Month可以通过Date字段计算出来,建议不要单独存储,避免数据不一致的问题。
内容的提问来源于stack exchange,提问作者Madu




