如何使我的 select 语句确定性地只匹配我的数据集的 1/n?

How can I make my select statement deterministically match only 1/n of my dataset?

我正在处理来自 MySQL table 的数据,其中每一行都有一个与之关联的 UUID。编辑:“UUID”实际上是作业文本的 MD5 散列 (VARCHAR)。

我的 select 查询类似于:

SELECT * FROM jobs ORDER BY priority DESC LIMIT 1

我现在只有 运行 一个工作节点,但想在不改变架构的情况下将其扩展到多个节点。

问题是这些作业需要一些时间,现在横向扩展超过一个会引入竞争条件,即多个节点在同一作业完成和更新行之前正在处理该作业。

是否有一种优雅的方法可以通过为每个工作节点指定一些修饰符配置值来有效地“分片”客户端的数据?我的第一个想法是像这样使用 MOD 函数:

SELECT * FROM jobs WHERE UUID MOD 2 = 0 ORDER BY priority DESC LIMIT 1SELECT * FROM jobs WHERE UUID MOD 2 = 1 ORDER BY priority DESC LIMIT 1

在这种情况下,我会将两个工作人员配置为“0”和“1”。但这并没有给我一个均匀的分布(不知道为什么)并且感觉很笨重。有没有更好的方法?

问题是您将 ID 存储为十六进制字符串,如 acbd18db4cc2f85cedef654fccc4a4d8。 MySQL 不会为您转换十六进制。相反,如果它以字母开头,你得到 0。如果它以数字开头,你得到起始数字。

  • select '123abc' + 0 = 123
  • select 'abc123' + 0 = 0

16 个中有 6 个以字母开头,因此它们都是 0 和 0 mod 任何一个都是 0。16 个中剩下的 10 个将是某个数字,因此将正确分配,16 个中的 5 个将为 0,16 个中有 5 个将为 1。6/16 + 5/16 = 69% 将为 0,这非常接近您观察到的 72%。


要做到这一点,我们需要将 128 位十六进制字符串转换为 64 位无符号整数。

  1. 使用 left(uuid, 16)right(uuid, 16) 分割 64 位。
  2. 使用 conv.
  3. 将十六进制(基数 16)转换为十进制(基数 10)
  4. cast 结果为无符号 bigint。如果我们跳过这一步 MySQL 似乎会使用失去准确性的浮点数。
select cast(conv(right(uuid, 16), 16, 10) as unsigned) mod 2

美丽。

这将只使用 128 位校验和中的 64 位,但为此目的应该没问题。


请注意,此技术适用于 MD5 校验和,因为它是伪随机的。它将与默认MySQLuuid() function which is a UUID version 1一起工作。 UUIDv1 是一个时间戳 + 一个固定的 ID,并且总是 mod 相同。

UUIDv4,这是一个随机数,可以。

在修改之前将十六进制字符串转换为十进制:

where CONV(substring(uuid, 1, 8), 16, 10) mod 2 = 1

一个合理的散列函数应该为此目的分布得足够均匀。

使用子字符串只转换一小部分,这样转换就不会溢出十进制范围,并且可能表现不佳。位的任何子集也应该分布均匀。