作为一个微服务的开发者, 通常都要自己来负责数据库表的创建。一个常见的问题是, 如何定义主键的类型?
通常我们会有两种做法。
1)主键选长整型, 并且自增的。
2)主键选择UUID类型。对于Mysql 8.0以前的版本来说就是varchar(36), 8.0以后的版本来说就是varbinary(16). 注意UUID有不同的版本, mysql实现的是版本v1, 是基于时间的一种uuid。 注意和java默认的uuid的实现(v4)并不相同。
使用UUID作为主键类型的好处:UUID全局唯一,可以支持分库分表。
UUID是一个随机值, 所以内容无法猜测。 这意味拿到一个已知的ID, 用户没有办法知道下一个ID是什么。然而如果是整型自增, 用户获得了当前的ID, 那么他就可以根据主键自增的原则遍历得到其他的数据库记录。 如果一个程序的用户权限管理不够完善, 可能会引发相应的数据安全问题。
可以方便的进行数据的迁移和复制。
UUID的内容可以不依赖于数据库产生。
使用UUID作为主键类型的坏处:存储字节数长。对于Mysql 8.0以前的版本就是varchar(36), 8.0以后的版本就是varbinary(16).
UUID是一个随机值, 插入的时候会导致索引的效率大大降低,导致大量的IO操作。
那么如何解决使用uuid作为主键带来的Performance的问题呢? 首先我们知道Mysql8 引入了两个函数可以再UUID和Binary[]之间方便的进行转换, 分别叫做UUID_TO_BIN 和 BIN_TO_UUID,默认都是一个参数。
我们可以使用SQL语句"INSERT INTO t VALUES(UUID_TO_BIN(UUID())"进行多条UUID数据的插入, 然后在再将他们读出来,那么读出来的数据如下图所示:
可以看到连续生成的uuid, 他们的后半部分是相同, 不同的只有前半部分。这就是前面提到的为什么会有性能问题的原因。 幸运的是, MySQL的UUID_TO_BIN函数还有第二参数swap, 可以将时间相关的位数低位和高位进行调换, 这样把快速变化的部分放到了右边, 可以显著的提高索引的效率。swap_flag is 0, the two-argument form is equivalent to the one-argument form. The binary result is in the same order as the string argument.
If swap_flag is 1, the format of the return value differs: The time-low and time-high parts (the first and third groups of hexadecimal digits, respectively) are swapped. This moves the more rapidly varying part to the right and can improve indexing efficiency if the result is stored in an indexed column.
我们再修改上面的插入语句, 并且将uuid_to_bin的swap参数设置为true, 那么同样的SQL语句你读到的结果就是如下图所示:
总结:
UUID唯一性的特点使它作为主键带来了很多的优势,比较大的问题主要是无序性带来的索引性能的下降。 使用mysql8自带的uuid_to_bin可以方便的将时间相关的字符高低位进行互换,从而解决了这个性能上的问题。
参考资料: