使用长哈希码作为SQL Server主键是否可行?有无替代方案?
问题描述
我有一台西门子面板,会将告警记录保存至CSV文件中。该文件包含约20个字段,例如编码毫秒级时间的整数、可读格式的时间字符串等。
面板会将新记录追加至文件末尾,直至达到10000条上限;当保存完最后一条记录后,下一条新记录会覆盖文件第一行,形成循环存储机制。
我希望将这些记录同步至SQL Server数据库,为此设置了每5至10分钟读取一次该文件的进程。同步前需要识别新增记录,仅将新数据保存至数据库。
但该CSV文件没有任何唯一字段——即使两条记录的毫秒级时间及所有字段内容完全一致,我也会将它们视为重复记录。
由于无法在数据库中设置由20个字段组成的联合主键,我计划创建对应所有字段的Alarma类,将类的哈希值作为ID字段作为主键,代码如下:
internal class Alarma { public long Id { get; init; } public long TimeMs { get; init; } public int MsgProc { get; init; } public int StateAfter { get; init; } public int MsgClass { get; init; } public int MsgNumber { get; init; } public string? Var1 { get; init; } public string? Var2 { get; init; } public string? Var3 { get; init; } public string? Var4 { get; init; } public string? Var5 { get; init; } public string? Var6 { get; init; } public string? Var7 { get; init; } public string? Var8 { get; init; } public string? TimeString { get; init; } public string? MsgText { get; init; } public string? Plc { get; init; } private Alarma() { Id = this.GetHashCode(); } public Alarma(long parmaTimeMs, int paramMsgProg, int paramStateAfeter, int paramMsgClass, int paramMsgNumber, string? paramVar1, string? paramVar2, string? paramVar3, string? paramVar4, string? paramVar5, string? paramVar6, string? paramVar7, string? paramVar8, string? paramTimeString, string? paramMsgText, string? paramPlc) { TimeMs = parmaTimeMs; MsgProc = paramMsgProg; StateAfter = paramStateAfeter; MsgClass = paramMsgClass; MsgNumber = paramMsgNumber; Var1 = paramVar1; Var2 = paramVar2; Var3 = paramVar3; Var4 = paramVar4; Var5 = paramVar5; Var6 = paramVar6; Var7 = paramVar7; Var8 = paramVar8; TimeString = paramTimeString; MsgText =paramMsgText; Plc = paramPlc; Id = this.GetHashCode(); } public override bool Equals(object obj) { if (obj is Alarma other) { return TimeMs == other.TimeMs && MsgProc == other.MsgProc && StateAfter == other.StateAfter && MsgClass == other.MsgClass && MsgNumber == other.MsgNumber && Var1 == other.Var1 && Var2 == other.Var2 && Var3 == other.Var3 && Var4 == other.Var4 && Var5 == other.Var5 && Var6 == other.Var6 && Var7 == other.Var7 && Var8 == other.Var8 && TimeString == other.TimeString && MsgText == other.MsgText && Plc == other.Plc; } return false; } public long GetHashCode() { long hash = 17; hash = hash * 31 + TimeMs.GetHashCode(); hash = hash * 31 + MsgProc.GetHashCode(); hash = hash * 31 + StateAfter.GetHashCode(); hash = hash * 31 + MsgClass.GetHashCode(); hash = hash * 31 + MsgNumber.GetHashCode(); hash = hash * 31 + (Var1?.GetHashCode() ?? 0); hash = hash * 31 + (Var2?.GetHashCode() ?? 0); hash = hash * 31 + (Var3?.GetHashCode() ?? 0); hash = hash * 31 + (Var4?.GetHashCode() ?? 0); hash = hash * 31 + (Var5?.GetHashCode() ?? 0); hash = hash * 31 + (Var6?.GetHashCode() ?? 0); hash = hash * 31 + (Var7?.GetHashCode() ?? 0); hash = hash * 31 + (Var8?.GetHashCode() ?? 0); hash = hash * 31 + (TimeString?.GetHashCode() ?? 0); hash = hash * 31 + (MsgText?.GetHashCode() ?? 0); hash = hash * 31 + (Plc?.GetHashCode() ?? 0); return hash; } }
方案分析与替代方案
哈希码作为主键的可行性分析
直接用自定义哈希码作为主键存在不可忽视的风险:
- 哈希冲突风险:尽管你采用了常见的哈希组合方式,但long型(64位)的哈希空间有限,必然存在不同记录生成相同哈希值的可能。一旦发生冲突,后续插入数据库时会因主键唯一约束导致其中一条记录丢失,无法满足完整同步的需求。
- 跨环境一致性问题:.NET中字符串的
GetHashCode()结果依赖于框架版本和运行平台,同一记录在不同环境下可能生成不同的哈希值,导致重复插入或无法匹配已存在的记录。
替代方案
方案1:加密哈希(SHA-256)作为唯一约束字段
放弃自定义哈希,改用加密级别的哈希函数生成唯一标识:
- 将
Alarma类的所有字段按固定顺序拼接(null值替换为空字符串),生成一个完整的内容字符串。 - 使用SHA-256算法对该字符串生成哈希值,转换为十六进制或Base64字符串,存入数据库的
ContentHash字段,并设置为唯一约束。 - 数据库表使用自增bigint作为主键,插入前先通过
ContentHash查询是否已存在该记录,避免重复。
优点:加密哈希的冲突概率极低,跨环境一致性强;自增主键保证查询性能。
缺点:哈希字段长度较长,但对于告警记录这类数据量,性能影响可忽略。
方案2:跟踪文件读取位置与循环状态
结合CSV的循环存储特性,通过记录读取状态识别新增记录:
- 每次读取文件后,保存上次读取的字节偏移量和已读取的总记录数到本地配置文件或数据库。
- 下次读取时:
- 若文件记录数未达10000条:直接从上次偏移量读取新增记录。
- 若文件已满:若上次读取到文件末尾,则从开头读取被覆盖的记录;否则读取上次偏移量到末尾的内容,再读取开头到被覆盖位置的内容。
- 为每条记录生成本地自增ID作为数据库主键,同时用SHA-256哈希作为唯一约束字段避免重复。
优点:从根源上避免重复读取,无需依赖记录内容判断,适配循环存储场景。
缺点:需要维护读取状态,逻辑稍复杂;进程崩溃后可能需重新同步部分记录,可通过定期备份状态缓解。
方案3:GUID主键+哈希辅助去重
为每条记录生成唯一GUID作为数据库主键,同时用哈希字段辅助判断重复:
- 读取CSV记录时,为每条记录生成一个GUID作为主键。
- 生成记录内容的SHA-256哈希值,存入
ContentHash字段并设置唯一约束。 - 插入前先查询
ContentHash是否存在,存在则跳过,否则插入。
优点:实现简单,GUID保证主键唯一性,哈希字段快速判断重复。
缺点:GUID作为主键会有轻微的索引性能损耗,对告警记录场景影响不大。
内容的提问来源于stack exchange,提问作者Álvaro García
相关产品推荐
相关产品推荐

