Gil*_*ili 4 mysql varchar unique-constraint
我想将 URL 存储在数据库列中,并强制执行值必须唯一的约束。不幸的是,MySQL 对索引键的长度有限制,这意味着只检查 URL 的前 X 个字符的唯一性。因此,我遇到了误报,其中两个不同的 URL 触发了约束集成违规,因为前 X 个字符恰好是相同的。
有没有办法在 VARCHAR 列上强制执行唯一性而对其长度没有任何限制?
例如,是否可以在前 X 个字符上创建非唯一索引,然后在其余字符相同时触发块 INSERT?
我们不断为您提供不直接回答问题的答案,因为这就是我们解决此问题的方式。无限长度的索引不切实际且效率低下,但唯一的哈希提供了足以完成任务的解决方案,因为发生有意义碰撞的可能性极低。
与其他提供的解决方案类似,我的标准方法不预先检查重复项——从这个意义上说它是乐观的:它依赖于数据库的约束检查,假设大多数插入不是重复的,所以没有意义浪费时间试图确定它们是否是。
工作,经过测试的示例(5.7.16,向后兼容 5.6;以前的版本没有内置TO_BASE64()功能):
CREATE TABLE web_page (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
url LONGTEXT NOT NULL,
url_hash CHAR(24) COLLATE ascii_bin,
PRIMARY KEY(id),
UNIQUE KEY(url_hash),
KEY(url(16))
)ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED;
Run Code Online (Sandbox Code Playgroud)
请注意,我正在存储哈希的 base64 版本。与以二进制形式存储相比,这是 4:3 大小的权衡,因为它使表内容和错误消息人类可读,并且表压缩部分抵消了低效率。哈希列具有唯一约束。数据类型是CHAR,不是VARCHAR,因为这消除了存储大小所需的字节——散列始终是固定大小。该列使用ascii带有ascii_bin(区分大小写)排序规则的字符集,使列和唯一索引尽可能小。
该url_hash由触发器设置,下面,但是触发并没有检查碰撞-没有必要检查,因为在url_hash的唯一约束。数据库将阻止重复插入。
请注意,应该声明 url_hashNOT NULL但 MySQL 在BEFORE INSERT触发器触发之前而不是之后错误地强制执行了此操作,因此我们受到了限制。触发器确实防止它为空。
url 列的前缀索引长度为 16,这是任意选择的。这不是唯一约束,只是用于查找的索引,它可能比您希望的要短,但它的长度对我们在这里解决的问题没有操作影响。
这是设置 url_hash 的触发器。INSERT当我们插入行时,我们不需要在语句中包含这个值。
DELIMITER $$
DROP TRIGGER IF EXISTS web_page_bi $$
CREATE TRIGGER web_page_bi BEFORE INSERT ON web_page FOR EACH ROW
BEGIN
SET NEW.url_hash = TO_BASE64(UNHEX(MD5(NEW.url)));
END $$
DELIMITER ;
Run Code Online (Sandbox Code Playgroud)
您还需要一个更新触发器,如果表应该是不可变的,则阻止更新,或者在 URL 更改时更新哈希。我们还需要这个触发器来确保 url_hash 列不会被不当设置,NULL因为 MySQL 中的限制不允许我们像我们应该的那样实际声明它。
现在,进行测试。
mysql> INSERT INTO web_page (url) VALUES ('http://example.com/');
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM web_page;
+----+---------------------+--------------------------+
| id | url | url_hash |
+----+---------------------+--------------------------+
| 1 | http://example.com/ | pr8XV//wV/JmtpffnPF2/Q== |
+----+---------------------+--------------------------+
1 row in set (0.00 sec)
Run Code Online (Sandbox Code Playgroud)
到现在为止还挺好。现在,一个不同的网址:
mysql> INSERT INTO web_page (url) VALUES ('http://example.net/');
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM web_page;
+----+---------------------+--------------------------+
| id | url | url_hash |
+----+---------------------+--------------------------+
| 1 | http://example.com/ | pr8XV//wV/JmtpffnPF2/Q== |
| 2 | http://example.net/ | ZVk/eLfvBI6tHN0Luj3NnQ== |
+----+---------------------+--------------------------+
2 rows in set (0.00 sec)
Run Code Online (Sandbox Code Playgroud)
仍然有效。现在,重复。
mysql> INSERT INTO web_page (url) VALUES ('http://example.com/');
ERROR 1062 (23000): Duplicate entry 'pr8XV//wV/JmtpffnPF2/Q==' for key 'url_hash'
Run Code Online (Sandbox Code Playgroud)
完美的。如果您想要比 MD5 提供的哈希冲突风险更低,请使用 SHA 变体,增加data_hashto的长度CHAR_LENGTH(TO_BASE64(UNHEX( /* your hash function */ )))以适应使用中的哈希算法生成的值。
| 归档时间: |
|
| 查看次数: |
3464 次 |
| 最近记录: |