将 User-Agent 字符串的哈希存储在 MySQL table 中:如果不存在则插入,return id

Store hashes of User-Agent strings in a MySQL table: insert if not exists, return id

受Whosebug上以下两个答案的启发,我尝试实现一个table,目的是在其中存储User-Agent字符串:

这是我的 table 结构:

CREATE TABLE IF NOT EXISTS ua_strings (
    ua_id INTEGER PRIMARY KEY AUTO_INCREMENT, 
    ua_hash BINARY(16), 
    ua TEXT, 
    UNIQUE KEY ua_hash (ua_hash)
);

我想实现以下目标:

到目前为止我想出了这个解决方案:

INSERT IGNORE INTO ua_strings (ua_hash, ua) VALUES (UNHEX(MD5('test')), 'test');
SELECT ua_id FROM ua_strings WHERE ua_hash = UNHEX(MD5('test'));

最重要的是摆脱INSERT IGNORE。我发现即使失败也会增加主键。您可以通过这种方式快速烧掉 40 亿个密钥。先做SELECT,无论如何这将是最常见的情况。

我的第一个想法是将逻辑放入数据库函数中。这为您提供了封装的所有好处。然后您可以稍后更改它的工作方式。

我的第二个目标是摆脱那个散列。它有效地取代了 ua 上的索引。由于您只需要等价性检查就可以提高性能,因此哈希索引是理想的选择,但大多数 MySQL table 格式不支持这些索引。

我会在用户代理的前 255 个字节上使用索引,这应该足以让 MySQL 完成它的工作。如果您需要做的不仅仅是简单的获取,这还可以为您提供完整索引的好处。

CREATE TABLE IF NOT EXISTS ua_strings (
    ua_id INTEGER UNSIGNED PRIMARY KEY AUTO_INCREMENT, 
    ua TEXT,
    KEY(ua(255))
);

函数看起来像这样(注意,我不是最擅长编写 MySQL 函数的人)。

DELIMITER //
CREATE FUNCTION get_ua_id (ua_string TEXT)
RETURNS INTEGER
BEGIN
    DECLARE ret INTEGER;

    SELECT ua_id INTO ret FROM ua_strings WHERE ua = ua_string;

    /* It's not in the table, put it in the table */
    CASE WHEN ROW_COUNT() = 0 THEN
        INSERT INTO ua_strings (ua) VALUES (ua_string);
        SELECT LAST_INSERT_ID() INTO ret;
    ELSE BEGIN END;
    END CASE;

    RETURN ret;
END//
DELIMITER ;

具有散列的函数看起来非常相似。隐藏函数中的实现细节并对两者进行基准测试。

并且真的不要使用 MD5。使用 SHA1 不会影响性能,您可以为每个条目留出额外的 4 个字节,这将避免隐藏的问题。使用 MD5 就像说 "Even though there's locks better in every way, I'll use this crappy lock because I don't think this door is important right now"。您不是安全专家(我也不是),不知道哪些部分重要哪些不重要。只需对所有内容进行适当的锁定即可。如果 SHA1 被证明是一些巨大的性能问题,由于函数的封装,您可以随时更改它。

无论基准测试结果如何,我敢打赌,分析将揭示您的选择对它所属的任何系统的性能没有影响。使用更简单、更灵活的索引选项,如果以后出现问题,请对其进行优化。