今天咱们聊聊这个“UUID和雪花ID作为MySQL主键”这事儿。
我呢,有个朋友前几天被老板怼了一顿,原因就是他在数据库表设计里用上了UUID作主键。结果呢,老板一脸嫌弃,说什么插入性能低、查询效率差等等,让他回去改成自增ID。
咱们今天就来聊聊为什么UUID和雪花ID这类随机主键有时候会踩坑,还得乖乖回到老实的自增ID的怀抱。
1. 自增ID vs UUID/雪花ID:谁才是MySQL的“亲生子”?
MySQL里,官方推荐的是auto_increment自增ID,按理说就是它的亲生孩子,默认就是最优化的插入性能。
UUID和雪花ID,属于那种“自由散漫”的类型,表面上挺灵活,但在MySQL里一不小心就会出幺蛾子。咱们来看看UUID和雪花ID到底有啥坏处,为什么容易被老板怼。
UUID的坏处?UUID呢,看上去随机又独特,但其实它的随机性会带来很多麻烦。简单来说,UUID和雪花ID是“碎片制造机”,在数据库插入时会频繁导致页分裂,写操作成本高,读取效率也低。用它做主键,大数据量一上来,性能差距就现形了。
2. 表设计和实验设置:三张表的比拼
咱们动手实验一下,通过三张表来测测谁的表现更好。表设计如下:
-- 使用自增ID的表
CREATE TABLE user_auto_key (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id BIGINT(64) NOT NULL DEFAULT 0,
user_name VARCHAR(64) NOT NULL DEFAULT '',
sex INT(2) NOT NULL,
address VARCHAR(255) NOT NULL DEFAULT '',
city VARCHAR(64) NOT NULL DEFAULT '',
email VARCHAR(64) NOT NULL DEFAULT '',
state INT(6) NOT NULL DEFAULT 0,
PRIMARY KEY(id),
KEY user_name_key(user_name)
) ENGINE=INNODB;
-- 使用UUID的表
CREATE TABLE user_uuid (
id VARCHAR(36) NOT NULL,
user_id BIGINT(64) NOT NULL DEFAULT 0,
user_name VARCHAR(64) NOT NULL DEFAULT '',
sex INT(2) NOT NULL,
address VARCHAR(255) NOT NULL DEFAULT '',
city VARCHAR(64) NOT NULL DEFAULT '',
email VARCHAR(64) NOT NULL DEFAULT '',
state INT(6) NOT NULL DEFAULT 0,
PRIMARY KEY(id),
KEY user_name_key(user_name)
) ENGINE=INNODB;
-- 使用随机ID的表
CREATE TABLE user_random_key (
id BIGINT(64) NOT NULL DEFAULT 0,
user_id BIGINT(64) NOT NULL DEFAULT 0,
user_name VARCHAR(64) NOT NULL DEFAULT '',
sex INT(2) NOT NULL,
address VARCHAR(255) NOT NULL DEFAULT '',
city VARCHAR(64) NOT NULL DEFAULT '',
email VARCHAR(64) NOT NULL DEFAULT '',
state INT(6) NOT NULL DEFAULT 0,
PRIMARY KEY(id),
KEY user_name_key(user_name)
) ENGINE=INNODB;
3. 程序实现:用Spring Boot搞性能测试
我们用Spring Boot + JDBC Template + JUnit + Hutool来完成这次性能测试,用的都是市面上比较常用的工具,目的是对比这三种主键在不同数据量下的插入和查询性能。
简单代码示例:
@Service
public class JdbcTemplateService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void insertData(String tableName, List<User> users) {
String sql = "INSERT INTO " + tableName + " (id, user_id, user_name, sex, address, city, email, state) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
User user = users.get(i);
ps.setObject(1, user.getId());
ps.setLong(2, user.getUserId());
ps.setString(3, user.getUserName());
ps.setInt(4, user.getSex());
ps.setString(5, user.getAddress());
ps.setString(6, user.getCity());
ps.setString(7, user.getEmail());
ps.setInt(8, user.getState());
}
@Override
public int getBatchSize() {
return users.size();
}
});
}
}
4. 测试结果:插入和查询性能大比拼
插入10万、100万条数据后,各种主键的表现如下:
自增ID:表现最优,插入速度最快,查询也快。自增ID按序插入,数据页都被填得满满的,读写都高效。
随机ID:还算过得去,比UUID要好一些,插入速度中等,查询略慢。因为随机ID有一定的规律性,不至于完全打乱数据页。
UUID:最差!插入10万数据就已经感到吃力了,100万数据基本上就是灾难现场,随机性带来的频繁页分裂让数据库效率低得可怜。
所以总结排名:auto_key > random_key > uuid,而且随着数据量增加,UUID的劣势越来越明显。
5. 为什么UUID的性能差?
自增ID的优点:
顺序插入:自增ID按顺序插入,数据页几乎没有碎片,写操作快,读操作也高效。
存储优化:顺序插入的数据行被紧密存放在一起,数据页利用率高,查询效率也有保证。
UUID的坑点:
随机性高:UUID是无序的,插入新数据时数据库得花大力气去找位置,数据页也会经常分裂,影响效率。
增加I/O成本:页分裂导致的数据碎片增多,磁盘I/O上升,需要定期清理碎片(比如跑OPTIMIZE TABLE)。
查询不友好:UUID查起来效率也不高,因为它既不连续也不友好,数据量一多,查询性能劣势就显现出来了。
6. 自增ID的缺点:并不是完美
当然了,自增ID虽然性能上优越,但也有它的缺点,比如:
业务信息泄露:ID的增长可以透露业务数据的增量,业务敏感性高的场景可能不适合。
高并发下的热点问题:自增ID在高并发场景下可能造成主键热点,导致锁竞争。
自增锁争夺:高并发下AUTO_INCREMENT锁竞争激烈,可能影响整体性能。
这些问题可以通过一些配置优化,比如innodb_autoinc_lock_mode,但总体来说,还是得权衡业务需求和性能之间的关系。
7. 实战选择:该选谁?
总结下来,MySQL官方推荐自增ID确实有它的道理,性能表现上几乎不拖后腿。当然,如果业务场景确实需要UUID或者雪花ID这种随机主键,比如多数据中心同步或全球唯一性,那就得权衡性能损失,用索引和分页策略弥补不足。
所以啊,千万别在表结构设计里一拍脑袋就用UUID了,没准哪天就被老板怼了
领取专属 10元无门槛券
私享最新 技术干货