news 2026/9/30 15:09:17

SQL中MD5加密的实现与避坑:从批量初始化到跨库校验

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SQL中MD5加密的实现与避坑:从批量初始化到跨库校验

最近帮业务部门处理了一桩挺典型的存量数据改造:一张几十万行的用户表要批量初始化密码。我第一反应是写个Python脚本连上数据库循环UPDATE,结果跑了大半天才推进不到一半,旁边的DBA瞄了一眼说:SQL里直接算md5不就行了,一行UPDATE的事,何必在应用层磨蹭。我这才意识到,MD5这种哈希算法,很多人写代码时用得顺手,一进SQL语境就开始露怯。这篇文章就把我在几种主流数据库里用SQL实现md5加密方法的完整思路、各种写法、触雷细节和校验手段一次说透,希望对正在做存量数据清洗、迁移校验或者跨库对账的同学有帮助。

1. 为什么要在SQL层面算MD5:先把应用场景理清楚

很多人第一反应是:MD5不就是一个函数吗,需要什么章节单独讲?在实际项目里,什么时候应该让数据库来算,什么时候应该在应用层算,这里面是有讲究的。搞清楚了场景,你才知道该用哪种SQL写法,也才知道哪些坑必须提前堵。

1.1 批量密码初始化和脱敏:一行UPDATE解决几十万行

最常见的第一类场景是存量用户的密码初始化或脱敏替换。假设你要把所有老用户的密码统一重置为临时密码,如果用应用层脚本,需要先SELECT出来,再逐条UPDATE,网络往返次数爆炸,几十万行跑起来慢得让人怀疑人生。直接在SQL里用MD5函数把值算好再写进去,一条UPDATE就能完成全量更新。

这类操作在MySQL里大概长这样:

UPDATE users SET pass_hash = MD5(CONCAT('init#', user_id, '#tmp')) WHERE pass_hash IS NULL;

这里给密码手动拼接了一个带user_id的“盐”再哈希。哪怕是临时密码,也别直接对同一个固定字符串做MD5,否则全表的哈希值一个模子刻出来,一旦一个被反推出来,整张表等于裸奔。加个user_id进去,至少让每个用户的值不同,这是成本最低的防御。

另外,这种批量操作对DBA来说是家常便饭,但对开发者来说,一位资深从业者的习惯是:先确认目标表的数据量级,再决定是直接一条UPDATE还是做分批UPDATE。几十万行一条UPDATE往往能扛住,几百万行就建议按ID分段循环处理了,原因后面性能部分会细说。

1.2 数据迁移和ETL后的一致性校验

第二类场景是数据搬家之后的核对。数据库版本升级、从A环境同步到B环境、或者做了分库分表,迁移完你总得验证数据到底丢没丢、改没改。逐字段比对在超大表上不现实,常见的做法是对每行关键字段拼成一个字符串,算一个MD5作为该行的“指纹”,再对整张表汇总指纹。两边指纹对得上,基本可以认为数据一致。

这类校验在SQL里做非常顺手,因为数据本来就在库里,不需要把整张表导出来再处理。你可以在迁移前后各跑一遍同样的SQL,把结果对比一下。文章第五部分会给可直接抄的完整示例。

1.3 指纹、去重、签名:MD5当业务ID用

第三类场景是把MD5当作业务标识符使用。比如采集到的URL文本特别长,直接建索引不划算;消息队列里的消息体很大,需要幂等去重;外部接口回调要做签名验证。这些时候把原始内容先做一次MD5,得到一个固定的32位定长字符串,就可以当成短ID、唯一键或者签名因子来用。这也是为什么搜索“SQL md5”的人里,有很大一部分是在做去重和签名相关的事。

这类场景对SQL的要求通常是:能不能在查询里直接对某个字段做MD5,然后参与JOIN、GROUP BY或者建索引。答案是能,但具体怎么写、有哪些性能陷阱,第三和第五部分会详细展开。

2. 主流数据库的MD5写法:别用MySQL的直觉去套SQL Server

不同数据库的MD5函数差异比想象中大得多。MySQL里一个MD5()走天下,到SQL Server就变成HASHBYTES,到Oracle又变成DBMS_CRYPTO.HASH。如果你只熟悉MySQL,很容易在SQL Server上写出一个不存在的函数名。

2.1 MySQL的MD5():最省事但也有小个性

MySQL的用法最简单:

SELECT MD5('hello'); -- 5d41402abc4b2a76b9719d911017c592

返回值是固定32位的小写十六进制字符串,这个结论很重要,后面讲跨库比对要一直用到。它有几个小个性:

  • 对NULL传入会返回NULL,而不是空串的MD5值,这会影响拼接字段时的结果。
  • 输出固定小写,如果你想要大写,需要自己包一层UPPER()。
  • MySQL 8.0依然保留MD5(),但如果你是为了安全用途而做口令哈希,官方也建议改用SHA2()函数,后面第四部分会写。

在MySQL里用MD5做条件查询时有个容易忽略的问题:如果你写WHERE MD5(url) = 'xxx',MySQL不会走索引,每行都要现场算一遍,全表扫描没跑。正确做法是增加冗余列存哈希值,再给这一列建索引,这个坑我放到第五部分去重场景里细讲。

2.2 SQL Server的HASHBYTES:为什么结果里多了0x或者大写了?

SQL Server里没有MD5()函数,对应的是HASHBYTES,语法是HASHBYTES('MD5', 输入)。但这里有个非常经典的坑:HASHBYTES返回的不是字符串,而是VARBINARY类型。你直接SELECT出来的结果在客户端里显示成一串二进制,有些人可能不理解为啥和MySQL结果对不上。

正确的转换姿势是这样:

-- 不带0x前缀的大写十六进制字符串,长度正好32位 SELECT CONVERT(VARCHAR(32), HASHBYTES('MD5', 'hello'), 2); -- 5D41402ABC4B2A76B9719D911017C592 -- 如果你想要小写,再加一层LOWER SELECT LOWER(CONVERT(VARCHAR(32), HASHBYTES('MD5', 'hello'), 2)); -- 5d41402abc4b2a76b9719d911017c592

CONVERT的第三个参数是style,2表示“不带0x前缀的十六进制”。如果你用了style 1,结果会带0x前缀,那长度就变成34了,VARCHAR(32)会截断,这是很多人对不上的另一个原因。

SQL Server还有一个超级隐蔽的坑:输入的类型不同,MD5结果完全不同。'hello'这个字符串字面量在SQL Server里是VARCHAR,算出来的MD5和MySQL一致;但如果写N'hello',也就是NVARCHAR类型,底层字节变成UTF-16LE编码,算出来的MD5就完全不一样了。这个问题在第三部分会重点展开,因为它是跨库对不上的头号元凶。

2.3 PostgreSQL、Oracle和SQLite的写法

PostgreSQL比SQL Server友好,内置了md5()函数,直接返回32位小写十六进制:

SELECT md5('hello'); -- 5d41402abc4b2a76b9719d911017c592

Oracle稍微绕一点。老一点的写法是用DBMS_OBFUSCATION_TOOLKIT.MD5,返回大写字符串;更通用、更推荐的是DBMS_CRYPTO.HASH,需要转成十六进制展示:

SELECT LOWER(RAWTOHEX( DBMS_CRYPTO.HASH( UTL_RAW.CAST_TO_RAW('hello'), DBMS_CRYPTO.HASH_MD5 ) )) AS md5_result FROM DUAL;

这段代码的逻辑是:UTL_RAW.CAST_TO_RAW把字符串转成RAW类型,DBMS_CRYPTO.HASH用MD5算法算出RAW类型的哈希值,RAWTOHEX转成十六进制字符串,最后LOWER统一成小写。注意执行DBMS_CRYPTO需要当前用户具备相应权限,否则会报权限不足。

SQLite就特殊了,标准SQLite默认根本不带MD5函数。想在SQLite里直接用md5()只能靠加载第三方扩展或者自定义函数,多数情况下不划算。实际项目里更常见的做法是在应用层用程序算好MD5,再把结果写进SQLite表。

把几种数据库的差异收成一张表,做跨库开发时直接对照着看:

数据库推荐写法返回格式备注
MySQLMD5(...)32位小写十六进制字符串NULL返回NULL
SQL ServerHASHBYTES('MD5', ...)VARBINARY,需配合CONVERT和LOWERVARCHAR和NVARCHAR结果不同
PostgreSQLmd5(...)32位小写十六进制字符串核心内置函数,无需扩展
OracleDBMS_CRYPTO.HASH配合RAWTOHEXHASH原始值需转十六进制需要DBMS_CRYPTO执行权限
SQLite不支持内置应用层算好再入库可加载扩展,但不推荐

3. 大小写、类型与NULL:三个最容易翻车的细节

就算函数用对了,还有三个细节会让人焦头烂额。我见过太多人信誓旦旦地说“两个库算出来的MD5不一样”,最后排查下来,根本不是算法问题,而是输出格式、输入类型和空值处理不一致。

3.1 同样是MD5,为什么不同库输出的格式不一样

MD5算法本身是公开标准,不管你用什么语言、什么数据库实现,只要输入的是同一段字节,输出的一定是同一个160位二进制结果的十六进制表示。区别只在于:这个“十六进制表示”在展示时,有的库返回大写,有的库返回小写,有的库还带个0x前缀。

这就导致了一个很常见的现象:MySQL算出来是5d41402abc4b2a76b9719d911017c592,SQL Server直接转出来是5D41402ABC4B2A76B9719D911017C592,Oracle的DBMS_OBFUSCATION_TOOLKIT也是大写。肉眼看上去完全不一样,但其实是同一个值。

解决办法很粗暴也很有效:跨库比对时,统一先做一次规范化处理,保险起见全部转成小写,再去掉可能的0x前缀。我的习惯是确立一个内部约定:所有哈希结果统一采用小写无前缀十六进制字符串作为交换格式,这样不管数据来自哪个库,落到对账脚本里都是同一种形态。

3.2 NULL参与计算:空值带来的“假问题”

MD5是对字符串做哈希的,所以一个很自然的思路是:把多个字段拼起来再哈希。但SQL里拼接字段时,NULL往往比你想的更淘气。

MySQL的MD5(NULL)返回NULL,PostgreSQL的md5(NULL)也返回NULL,SQL Server的HASHBYTES('MD5', NULL)同样返回NULL。而Oracle的一些旧接口甚至可能直接报错。如果你拼的字段里有一个是NULL,整行的哈希结果就变成NULL,最后汇总对账的时候,两边都显示NULL,你还以为对上了。

更隐蔽的是不同函数对NULL的处理逻辑还不一样。CONCAT('a', NULL)在MySQL里返回NULL,但CONCAT_WS('|', 'a', NULL)会忽略NULL,返回a。这两种拼接方式算出来的MD5完全不一样,如果迁移前后的SQL没有保持完全一致,结果必然对不上。

处理原则是:先定好规则,再写SQL。如果业务上空值和空字符串语义等价,就用COALESCE(字段, '')把NULL统一成空串;如果语义不等价,那就得考虑在SQL里区分场景。最怕的是稀里糊涂换了个拼接函数,然后拿着对不上的结果怀疑算法有问题。

3.3 字符集与字节:MD5加密的其实是字节序列

这是很多资深开发者都会栽跟头的知识点。MD5算法从底层看,接受的是字节序列,不是“字符串”这个概念。同样的字符串内容,在不同编码下对应的字节完全不同,算出来的MD5自然天差地别。

举个最典型的例子:在MySQL里,如果连接字符集是utf8mb4,MD5('中文')是对UTF-8编码的字节算哈希;如果把连接字符集改成gbk,同一个MD5('中文')就是对GBK编码的字节算哈希,结果完全不同。SQL Server那边更夸张,VARCHAR类型按数据库默认代码页解释,NVARCHAR按UTF-16LE解释,同一个'hello',写成'hello'和N'hello',MD5结果天差地别。

我在这里验证一下具体现象:

-- SQL Server中以下两个结果完全不同 SELECT LOWER(CONVERT(VARCHAR(32), HASHBYTES('MD5', 'hello'), 2)); SELECT LOWER(CONVERT(VARCHAR(32), HASHBYTES('MD5', N'hello'), 2));

原因就是底层参与哈希的字节序列不一样。所以做跨库MD5比对时,最优先要统一的是输入编码。我在项目中一般会先在SQL里显式把输入转成同一种编码:

-- MySQL:把字符串统一成utf8mb4后再算 SELECT MD5(CONVERT('中文' USING utf8mb4));

现实环境里的数据可能是各种渠道汇入的,光靠SQL层统一编码有时候并不够。更稳妥的方案是:应用层读取数据时统一转成UTF-8,再把规范化后的字符串交给SQL或程序处理。这个思路在第六部分对账环节还会配合黄金样本一起讲。

4. MD5不能存密码,但也不是废物:安全边界先说透

聊到MD5,总有人问“能不能用来加密密码”。这里必须把话说清楚:MD5不是加密算法,是散列算法,官方名称叫MD5消息摘要算法。它能从任意长度输入算出固定128位摘要,但这个过程不可逆,你不能从哈希值还原出原始数据。网上那些“MD5解密”网站,本质上不是真的解密,而是拿海量常见字符串预先算好MD5建了字典,输入一个哈希值去查这个字典,能查出来只是因为它碰巧在字典里。所以网上吵得热闹的“MD5解密”,准确说应该叫“MD5查表”。

4.1 为什么MD5不适合做口令哈希

原因可以总结为三点:

  1. 速度太快了。MD5设计出来就是追求效率的,现代机器上每秒能算几千万甚至上亿次。攻击者拿到一个MD5哈希值,可以极快地暴力穷举常见密码,或者用彩虹表直接查。
  2. 存在碰撞。2012年前后研究人员已经构造出了真实的MD5碰撞案例,两个不同的内容算出了同一个哈希值。对于口令校验这种依赖唯一性的场景,这是不可接受的。
  3. 彩虹表太成熟。针对常见弱口令的预计算表网上到处都是,就算你对标准密码做了MD5,也挡不住查表攻击。

在SQL Server、MySQL这类数据库里用HASHBYTES或MD5()算出来的哈希值,如果直接作为生产环境的用户密码存储字段,那基本等于给攻击者送菜。就算加了个固定的盐,也只解决了“同样密码产生同样哈希”的问题,缓解不了整体强度的不足。

4.2 哪些场景仍然可以放心用MD5

说它不适合存密码,不等于MD5这个算法就该被扔进垃圾桶。在非对抗性场景,它依然是性价比极高的校验工具:

  • 数据校验:传输或迁移大文件时,对比两边的MD5判断文件是否完整、备份是否损坏。这是MD5最广泛也最合适的使用场景,特别适合搭配SQL做行级和表级指纹。
  • 去重和幂等:对URL、大文本、JSON内容算一个MD5作为指纹,用来判断数据是否重复,或者作为幂等键。这里的对抗目标是“重复数据”,不是“黑客”,MD5完全够用。
  • 缓存与寻址:很多系统用内容哈希做缓存Key或内容寻址,拿MD5值当短ID,速度远快于直接比对原始大字符串。

记住一个判断逻辑:如果场景里没有人会蓄意构造和你作对的数据,MD5可以放心用;如果数据可能被人恶意篡改或构造碰撞,就别碰MD5。这个边界划清楚,用起来才踏实。

4.3 想在SQL里做更安全的哈希:SHA-2系列写法

如果你的业务确实需要在SQL层做口令哈希兜底,至少应该用SHA-2系列替代MD5。SHA-256的输出是256位,碰撞难度和暴力破解成本都高出一大截,速度也明显更慢,这对口令哈希来说是优点。

MySQL里直接用SHA2():

SELECT SHA2('hello', 256); -- 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

SQL Server里用HASHBYTES指定算法名:

SELECT LOWER(CONVERT(VARCHAR(64), HASHBYTES('SHA2_256', 'hello'), 2));

PostgreSQL需要pgcrypto扩展:

-- 需要先执行 CREATE EXTENSION pgcrypto; SELECT LOWER(ENCODE(DIGEST('hello', 'sha256'), 'hex'));

Oracle用DBMS_CRYPTO,把算法换成DBMS_CRYPTO.HASH_SH256,其他写法不变。还要说明一点:即使换成SHA-256,光在SQL里哈希依然不够,真正的生产级口令存储还需要加随机盐多次迭代,业界标准是bcrypt、scrypt或argon2这类专用算法。SQL层的哈希只是没办法时的兜底方案。

5. 真实项目里的三种落地玩法,可以直接抄

讲了这么多原理和函数,接下来直接给三个能搬进项目的实操方案。这三段SQL都是我在真实场景里用过的,结构和注释都保留在可复用的状态。

5.1 存量用户表批量初始化密码

场景:用户表新增了密码哈希字段,现在要给所有老用户初始化一个临时密码。一次性UPDATE全搞定:

UPDATE users SET pass_hash = MD5(CONCAT_WS('#', 'tmpPass', user_id, created_at)) WHERE pass_hash IS NULL;

注意我用的是CONCAT_WS而不是CONCAT,因为CONCAT只要其中一个字段是NULL,整个结果就是NULL,哈希值也会跟着变成NULL,这等于没处理。CONCAT_WS会自动跳过NULL字段,虽然在这里用不太到,但养成这个习惯能省掉很多坑。

量级再大一点,比如几百万行,建议分批执行。你可以按主键范围切段:

UPDATE users SET pass_hash = MD5(CONCAT_WS('#', 'tmpPass', user_id, created_at)) WHERE id BETWEEN 1 AND 100000 AND pass_hash IS NULL;

跑完看影响行数,确认没问题再跑下一个区间。一次UPDATE全表虽然SQL短,但会造成大事务、锁长时间持有、日志膨胀,生产环境这是很危险的操作。

这类初始化场景还有一个业务细节:临时密码初始化完,最好在系统里加一个标记字段,强制用户首次登录后改密,否则哈希值本身再安全也没意义。

5.2 表级数据指纹的迁移校验

数据迁移后要快速判断两边的表是否一致,一个直接的方法是按行MD5再汇总。小表可以在SQL里直接一次算完,MySQL里可以写成:

WITH row_hashes AS ( SELECT MD5(CONCAT_WS('|', id, name, created_at)) AS row_hash FROM users_order ) SELECT MD5(GROUP_CONCAT(row_hash ORDER BY row_hash)) AS table_fingerprint FROM row_hashes;

逻辑是这样:先对每一行所有关键字段拼接,算行级MD5指纹;再把所有行指纹按顺序拼起来,对整体再算一次MD5,得到表级指纹。迁移前跑一遍,迁移后跑一遍,两个table_fingerprint完全一致,基本可以认为数据一致。

但这里必须提醒一个MySQL的经典陷阱:GROUP_CONCAT默认的最大长度只有1024字节,如果你的表很大,行指纹拼接超过这个长度会被静默截断,最终算出的表级指纹就是错的。这个坑我踩过一次,排查了半天还以为是迁移丢了数据。大数据量表建议分片校验:按ID区间把数据拆成若干块,分别对每个分片计算表级指纹,再汇总比对,这样既能控制拼接长度,也方便定位差异发生在哪一段。

5.3 长URL去重:md5冗余列加唯一索引

很多采集表里存着大量URL,URL文本动辄几百上千字符,直接建索引很长、效率也不好。比较优雅的办法是加一个冗余的MD5列,再在这个列上建立唯一索引,把“比对长字符串”变成“比对固定32位字符串”。

MySQL里可以用生成列,建表时直接声明:

CREATE TABLE url_clean ( id INT PRIMARY KEY AUTO_INCREMENT, url TEXT NOT NULL, url_md5 CHAR(32) GENERATED ALWAYS AS (MD5(url)) STORED, UNIQUE KEY uk_url_md5 (url_md5) );

如果表已经建好了,也可以先加列再UPDATE回填:

ALTER TABLE url_clean ADD COLUMN url_md5 CHAR(32); UPDATE url_clean SET url_md5 = MD5(url); ALTER TABLE url_clean ADD UNIQUE KEY uk_url_md5 (url_md5);

有了唯一索引,重复URL在写入阶段就会被拦截。如果只是想清理已经存在的重复数据,则可以用自连接删除保留最小ID:

DELETE t1 FROM url_clean t1 JOIN url_clean t2 ON t1.url_md5 = t2.url_md5 AND t1.id > t2.id;

这里最想强调的性能要点是:不要在实际查询里写WHERE MD5(url) = 'xxx'。因为MD5(url)是对字段做函数运算,MySQL无法使用普通索引,只能全表扫描。你需要的是预先算好并存储的url_md5列,然后WHERE url_md5 = 'xxx',这样才能走索引。这个思路和搜索热词里的“慢sql优化”是同一个逻辑:能提前算好的结果,就不要放在查询条件里临时算。

6. 跨库计算结果不一致?五招搞定结果校对与调优

最后这部分专门给那些正在做跨数据库迁移、数据同步和报表对账的人。我可以直接说结论:跨库MD5对不上,99%是因为输入不一致,而不是算法不一致。MD5是国际标准算法,任何实现只要符合标准,对同一段字节输出的摘要都一样。那问题出在哪?基本集中在编码、类型、空格、NULL和拼接顺序这五类。

6.1 先把输入规范化:编码、大小写、空格

我的习惯是动手前先把“哈希输入协议”用文字写明,字段包括:参与哈希的字段顺序、字段之间的分隔符、NULL的替换规则、空字符串是否保留、日期字段格式化成字符串的样式、输入统一采用什么字符集编码、最终输出的十六进制要不要统一小写。把这些定了,再写SQL就顺畅得多。

实际操作层面的注意事项:

  • 字符集:跨库比对前,确认两边都按同一种编码解释字符串。MySQL里尽量显式CONVERT(... USING utf8mb4),SQL Server里格外小心VARCHAR和NVARCHAR混用,Oracle里注意数据库字符集。
  • 字符串尾部空格:CHAR类型会自动补空格,VARCHAR不会。'abc'和'abc '在SQL比较时可能被忽略尾随空格,但在MD5计算时底层字节不一样,结果就不同。所以要么统一TRIM,要么明确保留原始空格。
  • 日期字段:先把DATETIME格式化到统一格式,比如DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s'),不要依赖各种数据库的隐式转换,隐式转换的默认格式五花八门。
  • NULL规则:不同库的拼接函数处理NULL的逻辑完全不同,必须显式用COALESCE统一替换,并且规定替换值是什么,空字符串和NULL不能混为一谈。

6.2 用Python的hashlib结果当黄金样本

跨库对账最有效的排查手段,是拿一个可信的第三方实现作为“黄金样本”。Python的hashlib是标准库,跨平台结果稳定,特别适合用来反查SQL哪里不对。

先算好标准答案:

import hashlib # 按UTF-8编码算,这是最通用的约定 print(hashlib.md5('hello'.encode('utf-8')).hexdigest()) # 5d41402abc4b2a76b9719d911017c592 print(hashlib.md5('中文'.encode('utf-8')).hexdigest()) # e10adc3949ba59abbe56e057f20f883e 这行备注是示例,实际请以运行结果为准

然后到SQL那边分别用不同数据库跑同一个输入,把SQL结果和Python结果摆在一起。如果对不上,第一步检查编码,第二步检查NULL处理,第三步检查拼接顺序和分隔符。我总结的排查顺序基本屡试不爽:先看是不是S输入字符串的类型引入了不同字节,再看有没有隐式截断,最后才考虑是不是写法问题。

6.3 性能取舍:在SQL算还是拉出去算

最后一个实际问题:数据量到了亿级别,到底该不该让数据库来干这活?我的建议是分情况:

  • 一次性批量清洗:百万级以内的表,直接UPDATE,数据库算MD5的效率远高于应用循环,省掉网络开销和解释器循环,实测基本是秒级到分钟级的差异。
  • 高频在线查询:不要在查询条件里实时算MD5,一定要冗余哈希列加索引,把计算前置到数据写入或更新的时候。
  • 超大表全表处理:几亿行的表在数据库里全表扫描做MD5运算,会产生严重的CPU压力和事务日志膨胀,单机SQL不一定是最优解。这种情况更适合用Spark、ClickHouse或者Flink分批并行计算,算完再回写数据库。

还要注意一个容易被忽略的资源问题:对多列大文本做MD5时,数据库要先把拼接结果完整物化到内存,这个临时字符串的大小是参与拼接字段的总长度。如果字段里有大的TEXT、CLOB,拼接后的字符串可能动辄几十MB,大量并发执行时内存压力很大。所以做批量哈希之前,先看字段平均长度,再决定是直接拼还是只取关键列。

我现在的习惯是:凡是MD5计算结果要跨系统共享,一律先定“哈希输入协议”,然后用Python的hashlib出黄金样本,最后才在SQL里写对应的表达式。看起来多花了几分钟,实际上每次都能帮我把“看似玄学的对不上”变成“清清楚楚的字节差异”。MD5这个算法确实上了年纪,但只要把安全边界和输入规范化这两件事守住,它在SQL世界里依然是把能干活的瑞士军刀。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 15:08:59

宇视录像机备份录像配置指导(新版网页)

功能介绍NVR-BXXX.50.13.XXXX 及之后版本(如 NVR-B5301.50.13.250529)的宇视录像机采用新版界面。本文介绍如何在新版网页界面中,将存储在录像机硬盘上的录像文件下载到电脑文件夹。特别提醒:NVR升级至新版界面后将无法回退至旧版…

作者头像 李华
网站建设 2026/9/30 15:04:07

一文讲透降AIGC:检测原理、自检方法与实战改写

前几天一个读研的朋友半夜发消息给我,说导师通知她论文进了“疑似AIGC痕迹”名单,让她准备复核材料。她特别委屈:整篇论文都是自己一个字一个字敲出来的,连初稿都是先在纸上写提纲再往电脑里誊的,怎么就判成AI了呢&…

作者头像 李华
网站建设 2026/9/30 15:02:33

用 Jev 与 Laravel AI SDK 检测垃圾邮件和自动回复

Jev 与 LLM 有何不同 Jev 由 TypeSafe 开发。LLM 会生成文本:向它提出问题,它会写出答案;若想获得结构化数据,还得在提示词中提出要求,并期待它按要求返回。Jev 则直接输出数值判断:只需向它提供状态信息和…

作者头像 李华
网站建设 2026/9/30 15:02:29

Altium Designer进阶:从画好板到交付靠谱PCB的实战经验

画了几年Altium Designer的板子,你可能会有一种感觉:软件功能基本都认识,从原理图到PCB再到Gerber的流程也走得很顺,但设计交付的质量总差着一口气。要么是规则设得太糙,板厂返回来问一堆问题;要么是3D模型…

作者头像 李华
网站建设 2026/9/30 14:59:32

金九银十|2026Java 后端八股汇总,面试高频题 + 详细解答

或许这份面试题还不足以囊括所有 Java 问题,但有了它,我相信你一定不会“败”的很惨,因为有了它,足以应对目前市面上绝大部分的 Java 面试了,因为这篇文章不论是从深度还是广度上来讲,都已经囊括了非常多的…

作者头像 李华