UUID主键性能杀手,写入慢4倍,索引大3倍,一个改动即可解决。
我最近在搞数据库,发现好多人喜欢用UUID做主键,觉得全局唯一挺方便。但实际用起来,写入速度慢得吓人,数据量大了以后查询也卡。今天就跟大家聊聊这个坑,以及怎么避免,都是我自己踩过和学到的经验。
先说为什么UUID会让数据库变慢。数据库里数据是按主键顺序存的,就像书按页码排好。自增主键每次都加在末尾,新数据直接放后面,很顺畅。但UUID是随机的,新数据可能插到中间任何位置,就像在书中间乱加页码,书页就得拆开重排。
这个过程叫页分裂,特别费时间。磁盘要读写,CPU要算,频率一高,整个数据库就卡住了。我做过测试,插入十万条数据,用自增主键几秒钟搞定,用UUID要等半天,慢了三四倍。高并发时更惨,四个线程一起写,UUID性能直接掉到原来的五分之一。
除了写入慢,存储空间也浪费。UUID字符串占36个字节,自增整数才8个字节,差了四倍多。二级索引还会复制主键,整个表体积膨胀两到三倍很正常。内存有限,大索引挤占了其他数据缓存,查询就更慢了。
查询性能也有差距。主键等值查询,UUID比整数慢七八倍。范围查询更夸张,慢十几倍。因为数据分布太散,磁盘要来回找,找不到连续的区域。
那怎么办?很多人说不能用UUID,其实不是不能,得用对版本。UUID有好几种,v4是完全随机的,最差。v7是新标准,带时间戳,天然有序,插入位置集中,性能好很多。如果已经用了v4,可以改成v7,或者把v4存成二进制格式,去掉连字符,从36字节压缩到16字节,比较快。
![]()
还有一个办法是应用层配合。不要在客户端生成UUID,由服务端批量生成,按排序后统一插入,减少页分裂。或者用一些顺序生成器,比如Java的UUID.nameUUIDFromBytes,但要注意不能重复。
如果不想折腾,直接换主键类型也行。推荐ULID,128位,前48位是时间戳,后面随机,可排序,存储为26字节或16字节,性能接近自增,又保留了全局唯一。用Snowflake算法也可以,64位整数,紧凑,趋势递增,但得注意时钟回拨问题,可以用美团的Leaf方案改进。
还有一种稳妥的做法:主键用自增整数,再加一个UUID字段,设为唯一索引,对外暴露用。这样内部性能好,外部又能防猜。缺点是多了个索引,写入时多一次检查。
具体用哪个,得看场景。单库单表写入多,直接自增整数最省事。微服务分库分表,需要全局唯一,用ULID或Snowflake。对外暴露ID怕被遍历,就用自增加UUID字段。如果已经有大量UUIDv4数据,可以慢慢迁移到UUIDv7或ULID,不用一次性全改。
最后说一句,UUID不是垃圾,但用不对地方就是坑。主键最好满足三个条件:唯一、有序、紧凑。UUIDv4一个都不满足,所以别直接拿来用。如果必须用UUID,选v7存二进制,或者换ULID,改动不大,效果很明显。没有万能的方案,根据业务挑合适的,才是正经事。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.