如果您不需要每个 ip 访问时间戳精确到秒,您可以将每天划分为一系列时间段(可能每 10 或 5 分钟一个段)。每个日期时间段在时间段表中都有一个 id。然后,您可以为 ip 表中的每个唯一 IP 地址创建一个 id。
然后您有一个连接表,您可以在其中将 IP 地址(外键)与时间段(外键)与计数(无符号整数)相关联。因此,您的核心访问行数据现在简化为 2 个 id 和 1 个无符号整数(最重要的是,没有字符串)。
因此,当您收到来自 IP 的请求时,您确定当前时间段,如果该时间段不存在,则为新时间段创建一行。如果当前时间段与 IP 没有关联,则创建一个新的行,计数为 1。如果该时间段和 IP 有一行,则增加计数。
通过以这种方式规范化数据/表并稍微降低准确性,您可以实现一种信息压缩形式。玩弄时间段间隔以找到最佳权衡。例如,如果您不需要几分钟甚至几小时的查询粒度,您可以将您的时间段设置为一天。
更新:
对,所以上面的所有内容都是压缩来自同一 IP 地址的多个命中。与唯一命中相比,您获得的重复命中越多,这显然更有效。如果您只关心独特的命中,则完全无关紧要。
有多种方法可以将 IPv4 地址压缩为无符号整数(32 位)。只需将每个部分分别移位a.b.c.d
到字节0xff000000
, 0x00ff0000
, 。0x0000ff00
0x000000ff
这样,每个 IP 使用 4 个字节而不是字符串;此时存储外键(无论如何至少需要 4 个字节)是没有用的。因此,您可以只拥有一个包含字段的非规范化表:(IPv4 作为编码的无符号整数,日期时间/时间戳作为 4 字节整数)。您可能会用一天和一个计数替换日期时间,具体取决于您是否计算多次点击。如果多次点击不算数,你真的可以用一个 int 表示 IP 和一个 int 表示日期。
如果 day-granulartiy 是您所需的最低值,则还有一个更进一步的选择:您可以在每天结束时清除此 IP db 表,并将聚合查询的结果仅存储在数据库中。其余数据可以每天存档并从 IP db 表中删除。这意味着您的表只需要一个字段:编码为无符号整数的 IP。在这一点上,问题就变成了每天构建大量独特的整数集。
您还可以将时间(或时间段)展平/非规范化为 int(甚至更小),具体取决于您想要记录时间的频率/粒度,以及您是否选择聚合/归档/清除 IP db 表定期。
以压缩方式存储多个 IP 地址的另一种方法是使用trie数据结构,但它不直接映射到数据库存储(与内存数据结构相比)。通过 SQL 存储树结构(例如 trie)的一种方法是使用Materialized Path方法 - 但是,这种方法无法实现良好的数据压缩,并且查询开销可能不值得。