哈希冲突比你想象的多

哈希函数是映射函数,它把输入的数据值经过一定的转换算法,映射成为新的数据值,哈希算法质量的好坏,是由产生的数据值的精确度决定的,理想的哈希函数有两个特性:对于同一个输入值,产生相同的哈希值;对于不同的输入值,产生不同的哈希值。对于不同的输入值,产生相同的哈希值,这就叫冲突,冲突越少,哈希算法的质量越高。SQL Server内置三个哈希函数,2个校验和函数(checksum 和 binary_checksum),以及一个冲突更少的哈希函数HashBytes,这三个函数都无法提供100%的精确度,如果业务逻辑要求不允许有误差,那么不要使用任何哈希函数,只要是哈希函数,就会存在冲突。

一,校验和

在一些数据表中,如果存在多个字符串字段,并且字符串非常长,在比较字符串是否相同时,使用校验和函数,将其转换成 int 类型,能够提高数据比较的速度。但是不要忽略官方文档的警告(Caution):使用校验和函数,有时不能探测到数据的更新,除非应用程序能够容忍偶尔的丢失更新。一旦将校验和函数用于实际的项目中,你会发现,不是偶尔,而是经常会发生探测不到数据更新的异常:

BINARY_CHECKSUM(*) will return a different value for most, but not all, changes to the row, and can be used to detect most row modifications.

However, there is a small chance that the checksum will not change. For this reason, we do not recommend using CHECKSUM to detect whether values have changed, unless your application can tolerate occasionally missing a change.

由于存在侥幸心理,在项目中使用了校验函数对字符串字段进行比较,代码大致如下:url,why 和 description 字段都是挺长的字符串字段,特别是description,平均长度是1163字符。

;merge target as t 
using source as s 
    on t.id=s.id
when matched 
    and binary_checksum(t.url,t.why,t.description)
            <>binary_checksum(s.url,s.why,s.description)
....

大多数情况下,函数工作正常,但是,常在河边走,哪能不湿鞋,在一个周末,领导一个短信,我回来加班了,校验函数不能探测出更新的数据,导致数据同步出现问题,对ScoreCard和绩效影响较大,必须及时修改。

如图,Source 和 target 的Description 字段不一致,但是校验函数:binary_checksum(url,description,why) 返回的值是相同的,痛定思痛,为了避免以后的数据更新被异常丢失,决定在ETL中不再使用校验和函数,而是直接使用字符串进行比对。

校验和函数对长字符串产生的校验值质量不是很高,那对短的字符串了?经过多次尝试,发现校验函数在短的字符串上更容易出现更新丢失的情况,鉴于校验函数的这些缺点,强烈建议不要在项目中使用校验和函数。直接使用字符串进行比较,保证万无一失,相比数据更新出错,数据同步稍稍慢点,更能被容忍。

在比较长的字符串时,直接比较会比较慢,使用校验和比较速度会比较高,但是 binary_checksum 和 checksum 产生的校验和的质量比较低,建议不要再项目中使用校验函数。当字符串比较短时,很大可能产生Hash 冲突,例如:下面的脚本返回两行数据,每行的两个数据列结果相同,相比较而言,使用 checksum 比 binary_checksum效果更好。

--database collation is  SQL_Latin1_General_CP1_CI_AS
select checksum(N'us',N'FL',N'Land O Lakes'),checksum(N'us',N'FL',N'Land O'' Lakes')
select binary_checksum(N'gb',N'c6',N'bude'),binary_checksum(N'no',N'',N'myre')

二,HashBytes函数

HashBytes 函数使用不同级别的加密算法,能够产生高质量的哈希值,大幅度提高识别数据差异的准确性,但是HashBytes函数无法提供100%的准确度,如果业务逻辑要求不允许有误差,那么不要使用任何Hash 函数,只要是Hash函数,就会存在冲突。HashBytes 函数对于相同的文本,有时会产生不同的哈希值。

When an MD5 hash algorithm is specified, the probability of HashBytes returning the same result for two different inputs is much lower than that of CHECKSUM.

HashBytes 只能有一个输入参数,参数的数据类型是varchar、nvarchar 或 varbinary,并且输入字符的最大有效字节数量是8000Bytes,产生的哈希值跟输入值的数据类型相关。

1,指定Hash算法

使用 HashBytes需要指定算法,SQL Server内置7种计算HashValue的算法,推荐使用MD5来计算数据的差异。

  HASHBYTES ( '<algorithm>', { @input | 'input' } ) 
  <algorithm>::= MD2 | MD4 | MD5 | SHA | SHA1 | SHA2_256 | SHA2_512

输入参数的数据类型为 varchar、nvarchar 或 varbinary,输入参数的有效字节数量最大是8000bytes。HashBytes函数的返回值的数据类型是 varbinary ,不同的算法,返回的字节长度是不同的

The output conforms to the algorithm standard: 128 bits (16 bytes) for MD2, MD4, and MD5; 160 bits (20 bytes) for SHA and SHA1; 256 bits (32 bytes) for SHA2_256, and 512 bits (64 bytes) for SHA2_512.

三,影响HashBytes函数的因素

1,HashBytes函数受输入数据类型的影响

select HASHBYTES('SHA1',N'123abc') as HashNChar, HASHBYTES('SHA1','123abc') as HashChar;

2,HashBytes函数受输入字符大小写的影响

select hashbytes('SHA1','eric') as UpperCase,hashbytes('SHA1','Eric') as LowerCase

3,有效输入是8000Bytes

如果输入字符串的字节数量大于8000Bytes,那么HashBytes 把超过8000Bytes的字符舍弃,只对前8000Bytes进行Hash计算。SQL Server 不会提供任何提示或warning,在使用时,必须保证,输入字符串的字节数量不能超过8000。

如果是Unicode字符,那么输入字符串字符数量不能超过4000个;如果是varchar字符,那么输入字符串的数量不能超过8000个。

select hashbytes('SHA1',replicate('eric',2000)) ,hashbytes('SHA1',replicate('eric',4000))


参考文档:

HASHBYTES (Transact-SQL)

posted @ 2017-05-03 10:13  悦光阴  阅读(4364)  评论(4编辑  收藏  举报