首页 > 数据库 >MySQL原理之UUID主键分析,插入或更新语法分析

MySQL原理之UUID主键分析,插入或更新语法分析

时间:2024-09-11 21:13:07浏览次数:10  
标签:count 语法分析 UUID 主键 插入 user key word id

目录

1 MySQL不能用UUID做主键

1.1 前言

mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复雪花id(long形且唯一,单机递增),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究竟有什么坏处?

1.2 mysql和程序实例

1.2.1 准备工作

要说明这个问题,我们首先来建立三张表
分别是user_auto_key,user_uuid,user_random_key分别表示自动增长的主键,uuid作为主键,随机key作为主键,其它我们完全保持不变.
根据控制变量法,我们只把每个表的主键使用不同的策略生成,而其他的字段完全一样,然后测试一下表的插入速度和查询速度:

注:这里的随机key其实是指用雪花算法算出来的前后不连续不重复无规律的id,一串18位长度的long值
id自动生成表:

create table user_key_auto(
	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 nulT 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

随机主键表:

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

1.2.2 开始测试

光有理论不行,直接上程序,使用spring的jdbcTemplate来实现增查测试:
技术框架:springboot+jdbcTemplate+junit+hutool,程序的原理就是连接自己的测试数据库,然后在相同的环境下写入同等数量的数据,来分析一下insert插入的时间来进行综合其效率,为了做到最真实的效果,所有的数据采用随机生成,比如名字、邮箱、地址都是随机生成。

import cn.hutool.core.collection.CollectionUtil;
import com.wyq.mysqldemo.databaseobject.UserKeyAuto;
import com.wyq.mysqldemo.databaseobject.UserKeyRandom;
import com.wyq.mysqldemo.databaseobject.UserKeyUUID;
import com.wyq.mysqldemo.diffkeytest.AutoKeyTableService;
import com.wyq.mysqldemo.diffkeytest.RandomKeyTableService;
import com.wyq.mysqldemo.diffkeytest.UUIDKeyTableService;
import com.wyq.mysqldemo.util.JdbcTemplateService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.StopWatch;
import java.util.List;
@SpringBootTest
class MysqlDemoApplicationTests {

    @Autowired
    private JdbcTemplateService jdbcTemplateService;

    @Autowired
    private AutoKeyTableService autoKeyTableService;

    @Autowired
    private UUIDKeyTableService uuidKeyTableService;

    @Autowired
    private RandomKeyTableService randomKeyTableService;


    @Test
    void testDBTime() {

        StopWatch stopwatch = new StopWatch("执行sql时间消耗");


        /**
         * auto_increment key任务
         */
        final String insertSql = "INSERT INTO user_key_auto(user_id,user_name,sex,address,city,email,state) VALUES(?,?,?,?,?,?,?)";

        List<UserKeyAuto> insertData = autoKeyTableService.getInsertData();
        stopwatch.start("自动生成key表任务开始");
        long start1 = System.currentTimeMillis();
        if (CollectionUtil.isNotEmpty(insertData)) {
            boolean insertResult = jdbcTemplateService.insert(insertSql, insertData, false);
            System.out.println(insertResult);
        }
        long end1 = System.currentTimeMillis();
        System.out.println("auto key消耗的时间:" + (end1 - start1));

        stopwatch.stop();


        /**
         * uudID的key
         */
        final String insertSql2 = "INSERT INTO user_uuid(id,user_id,user_name,sex,address,city,email,state) VALUES(?,?,?,?,?,?,?,?)";

        List<UserKeyUUID> insertData2 = uuidKeyTableService.getInsertData();
        stopwatch.start("UUID的key表任务开始");
        long begin = System.currentTimeMillis();
        if (CollectionUtil.isNotEmpty(insertData)) {
            boolean insertResult = jdbcTemplateService.insert(insertSql2, insertData2, true);
            System.out.println(insertResult);
        }
        long over = System.currentTimeMillis();
        System.out.println("UUID key消耗的时间:" + (over - begin));

        stopwatch.stop();


        /**
         * 随机的long值key
         */
        final String insertSql3 = "INSERT INTO user_random_key(id,user_id,user_name,sex,address,city,email,state) VALUES(?,?,?,?,?,?,?,?)";
        List<UserKeyRandom> insertData3 = randomKeyTableService.getInsertData();
        stopwatch.start("随机的long值key表任务开始");
        Long start = System.currentTimeMillis();
        if (CollectionUtil.isNotEmpty(insertData)) {
            boolean insertResult = jdbcTemplateService.insert(insertSql3, insertData3, true);
            System.out.println(insertResult);
        }
        Long end = System.currentTimeMillis();
        System.out.println("随机key任务消耗时间:" + (end - start));
        stopwatch.stop();


        String result = stopwatch.prettyPrint();
        System.out.println(result);
    }

1.2.3 程序写入结果

user_key_auto写入结果:
在这里插入图片描述

user_random_key写入结果:
在这里插入图片描述

user_uuid表写入结果:
在这里插入图片描述

1.2.4 效率测试结果

在这里插入图片描述

在已有数据量为130W的时候:我们再来测试一下插入10w数据,看看会有什么结果:
在这里插入图片描述

可以看出在数据量100W左右的时候,uuid的插入效率垫底,并且在后序增加了130W的数据,uuid的时间又直线下降

时间占用量总体可以打出的效率排名为:auto_key>random_key>uuid,uuid的效率最低,在数据量较大的情况下,效率直线下滑。那么为什么会出现这样的现象呢?带着疑问,我们来探讨一下这个问题:

1.3 使用uuid和自增id的索引结构对比

1.3.1 自增id

使用自增id的内部结构
在这里插入图片描述
自增的主键的值是顺序的,所以Innodb把每一条记录都存储在一条记录的后面。当达到页面的最大填充因子时候(innodb默认的最大填充因子是页大小的15/16,会留出1/16的空间留作以后的修改):

  • 下一条记录就会写入新的页中,一旦数据按照这种顺序的方式加载,主键页就会近乎于顺序的记录填满,提升了页面的最大填充率,不会有页的浪费
  • 新插入的行一定会在原有的最大数据行下一行,mysql定位和寻址很快,不会为计算新行的位置而做出额外的消耗
  • 减少了页分裂和碎片的产生

1.3.2 uuid

使用uuid的索引内部结构
在这里插入图片描述

因为uuid相对顺序的自增id来说是毫无规律可言的,新行的值不一定要比之前的主键的值要大,所以innodb无法做到总是把新行插入到索引的最后,而是需要为新行寻找新的合适的位置从而来分配新的空间。
这个过程需要做很多额外的操作,数据的毫无顺序会导致数据分布散乱,将会导致以下的问题:

  • 写入的目标页很可能已经刷新到磁盘上并且从缓存上移除,或者还没有被加载到缓存中,innodb在插入之前不得不先找到并从磁盘读取目标页到内存中,这将导致大量的随机IO
  • 因为写入是乱序的,innodb不得不频繁的做页分裂操作,以便为新的行分配空间,页分裂导致移动大量的数据,一次插入最少需要修改三个页以上
  • 由于频繁的页分裂,页会变得稀疏并被不规则的填充,最终会导致数据会有碎片
  • 在把随机值(uuid和雪花id)载入到聚簇索引(innodb默认的索引类型)以后,有时候会需要做一次OPTIMEIZE TABLE来重建表并优化页的填充,这将又需要一定的时间消耗。

结论:使用innodb应该尽可能的按主键的自增顺序插入,并且尽可能使用单调的增加的聚簇键的值来插入新行

1.4 自增id缺点

那么使用自增id就完全没有坏处了吗?并不是,自增id也会存在以下几点问题:

  • 别人一旦爬取你的数据库,就可以根据数据库的自增id获取到你的业务增长信息,很容易分析出你的经营情况
  • 对于高并发的负载,innodb在按主键进行插入的时候会造成明显的锁争用,主键的上界会成为争抢的热点,因为所有的插入都发生在这里,并发插入会导致间隙锁竞争
  • Auto_Increment锁机制会造成自增锁的抢夺,有一定的性能损失
    Auto_increment的锁争抢问题,如果要改善,需要调优innodb_autoinc_lock_mode的配置

点击了解MySQL自增锁机制

参考连接:https://mp.weixin.qq.com/s/px-vRWikt8xp7b3ZCr27uw

1.5 雪花算法

点击了解MySQL分布式环境下生成全局自增有序ID(雪花算法Snowflake)

2 插入或更新

MySQL中假如想要在插入时没有就插入有则更新,可以用MyISAM引擎的merge或者InnoDB引擎的on duplicate key,现在主要是用InnoDB,下面主要介绍on duplicate key

2.1 on duplicate key

2.1.1 定义

如果在INSERT语句末尾指定了 on duplicate key update,并且插入行后会导致在一个UNIQUE索引或PRIMARY KEY中出现重复值,则在出现重复值的行执行UPDATE;如果不会导致唯一值列重复的问题,则插入新行。

2.1.2 values函数

values函数使用说明:在一个 INSERT … ON DUPLICATE KEY UPDATE … 语句中,可以在 UPDATE 子句中使用 VALUES(col_name ) 函数,用来访问来自该语句的 INSERT 部分的列值。换言之,UPDATE 子句中的 VALUES(col_name ) 访问需要被插入的 col_name 的值 , 并不会发生重复键冲突。这个函数在多行插入中特别有用。

values(col_name)函数只是取当前插入语句中的插入值,并没有累加功能。 如:count = values(count) 取前面 insert into 中的 count 值,并更新。
当有多条记录冲突,需要插入时,前面的更新值都被最后一条记录覆盖,所以呈现出取最后一条更新的现象。如:count = count + values(count) 依然取前面 insert into 中的 count 值,并与原记录值相加后更新回数据库,这样,当多条记录冲突需要插入时,就实现了不断累加更新的现象
注意VALUES() 函数只在 INSERT ... UPDATE 语句中有意义,而在其它情况下只会返回 NULL

2.1.3 注意事项

注意事项:

  • insert into ... on duplicate key update ... values() 这个语句,尽管在冲突时执行了更新,并没有插入,但是发现依然会占用 id 序号(自增),出现很多丢失的 id
  • 因为这是个插入语句,所以不能加where条件
  • 如果是插入操作,受到影响行的值为1;如果更新操作,受到影响的行的值为2;如果更新的数据和已有的数据比对一样(就相当于没变,所有值保持不变),受到影响的行的值为0
  • 更新的内容中unique key或者primary key最好按照一个来判断是否重复,不然不能保证语句执行正确(有任意一个unique key重复就会走更新);尽量不对存在多个唯一键的talbe使用该语句,避免可能导致数据错乱
  • 在有可能有并发事物执行的insert语句下不要使用该语句,可能导致产生dead lock
  • 如果数据表id是自动递增的不建议使用该语句;id不连续,如果前面更新的比较多,新增的下一条会相应跳跃的更大
  • 该语句是mysql独有的语法,如果可能涉及到其他数据库语言要慎重使用

2.1.4 操作分析

创建案例表 word_count(单词计数表)
  use test;
  CREATE TABLE IF NOT EXISTS word_count (
  	id int(11) NOT NULL AUTO_INCREMENT,
  	word varchar(64) NOT NULL,
  	count int(11) DEFAULT 0,
  	date date NOT NULL,
  	PRIMARY KEY (id),
  	UNIQUE KEY word (word, date)  // (word,date) 两字段组合唯一
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
  

执行第一次:(首次数据库表中没有数据,正常插入)
insert into word_count (word, count, date) values 
('a',5,curdate()) 
on duplicate key update count=values(count);
  # 结果显示:
  id   word    count    date 
  1    a       5        2023-03-11
  
执行第二次:(与第一次的唯一(word,date)冲突,执行更新)
insert into word_count (word, count, date) values 
('a',6,curdate()) 
on duplicate key update count=values(count);
  # 结果显示:
  id   word    count    date 
  1    a       6        2023-03-11  (更新)
  
执行第三次:
insert into word_count (word, count, date) values 
('a',6,curdate()-1),    // 取前一天,不会冲突
('a',7,curdate()) // 冲突
on duplicate key update count=values(count);
  # 结果显示:
  id   word    count    date 
  1    a       7        2023-03-11  (更新)
  3    a       6        2023-03-10  (新插入)
  
执行第四次:(更新冲突的最后一条插入值)
insert into word_count (word, count, date) values 
('a',2,curdate()),  // 冲突
('a',1,curdate())  // 冲突
on duplicate key update count=values(count);
  # 结果显示:
  id   word    count    date 
  1    a       1        2023-03-11  (更新最后一条插入值)
  3    a       6        2023-03-10  (不变)
  
执行第五次:(更新冲突的累加插入值)
insert into word_count (word, count, date) values 
('a',2,curdate()),
('a',1,curdate()) 
on duplicate key update count=count+values(count); // 实现每行累加
  # 结果显示:
  id   word    count    date 
  1    a       4        2023-03-11
  3    a       6        2023-03-10
 
执行第六次:(无冲突插入,观察 id 键值,出现了很多丢失,id 直接跳到了 9)
insert into word_count (word, count, date) values 
('b',2,curdate())
on duplicate key update count=count+values(count);
  # 结果显示:
  id   word    count    date 
  1    a       4        2019-06-26
  3    a       6        2019-06-25
  9    b       2        2019-06-26

2.2 replace info

2.2.1 定义

咱们在运用数据库时能够会常常遇到这种状况。若是一个表在一个字段上建立了索引,当咱们再向这个表中运用现已存在的键值插进一条记载,那将会抛出一个主键抵触的过错。当然,咱们能够想用新记载的值来掩盖本来的记载值。若是运用传统的做法,有必要先运用 DELETE 句子删去原先的记载,然后再运用 INSERT 插进新的记载。而在MySQL中为咱们供给了一种新的解决方案,这即是 REPLACE 句子。
运用REPLACE插进一条记载时,若是不重复,REPLACE就和INSERT的功用相同,若是有重复记载,REPLACE就运用新记载的值来更换本来的记载值。

运用REPLACE的最大优点即是能够将DELETEINSERT合二为一,构成一个原子操作。这样就能够不用思考在一起运用DELETEINSERT时增加业务等杂乱操作了。
在运用REPLACE时,表中有必要有索引,并且这个索引地点的字段不能答应空值,不然REPLACE就和INSERT彻底相同的

在履行REPLACE后,体系回来了所影响的行数,若是回来1,阐明在表中并没有重复的记载,若是回来2,阐明有一条重复记载,体系主动先调用了DELETE删去这条记载,然后再记载用INSERT来刺进这条记载。若是回来的值大于2,那阐明有多个仅有索引,有多条记载被删去和刺进。

replace具备替换拥有唯一索引或者主键索引重复数据的能力,也就是如果使用replace into插入的数据的唯一索引或者主键索引与之前的数据有重复的情况,将会删除原先的数据,然后再进行添加。
语法:replace into table( col1, col2, col3 ) values ( val1, val2, val3 )
语义:向table表中col1, col2, col3列replace数据val1,val2,val3

2.2.2 操作

先查询:select * from word_count
# 结果显示:
1	a	9	2023-03-11
3	a	7	2023-03-10
12	c	4	2023-03-11
13	b	1	2023-03-11

执行replace into 语句:
replace into word_count (word, count, date) values ('b',1,curdate());

再次查询:select * from word_count
# 结果显示:
1	a	9	2023-03-11
3	a	7	2023-03-10
12	c	4	2023-03-11
14	b	1	2023-03-11

2.2.3 MySQL中特殊插入语法

MySQL中的INSERT句子和规范的INSERT不太相同,在规范的SQL句子中,一次刺进一条记载的INSERT句子只要一种办法。

INSERT INTO tablename(列名…) VALUES(列值);

而在MySQL中还有别的一种办法,如下的方法把列名和列值成对呈现和运用,如下面的句子将产生中样的作用

INSERT INTO tablename SET column_name1 = value1, column_name2 =
value2,…;

2.3 INSERT IGNORE

MySQL中,可以使用INSERT IGNORE语句来忽略重复的记录。如果尝试插入的数据会导致重复键错误,那么INSERT IGNORE会跳过这条记录,而不是报错。

例如:

INSERT IGNORE INTO table (column1, column2) VALUES ('value1', 'value2');

在这个例子中,如果('value1', 'value2')会导致重复键错误,那么这条插入语句就会被忽略,而不会插入数据。同时,这条SQL语句也不会报错,而是会继续执行下一条SQL语句(如果有的话)。

需要注意的是,INSERT IGNORE不仅会忽略重复键错误,还会忽略其他一些错误(例如数据溢出)。因此,应当确保要插入的数据确实是想要插入的,并且确实希望在出错时跳过插入,而不是报错。

标签:count,语法分析,UUID,主键,插入,user,key,word,id
From: https://www.cnblogs.com/jingzh/p/18409003

相关文章

  • 【昌哥IT课堂】MySQL8.0新特性之不可见主键
     一、概述作为MySQLDBA,相信大家都经历过在复制模式下,如果没有主键,遇到loaddata,大事务,ddl等有大量表数据行扫描的行为时,会带来严重的主从延迟,给数据库稳定性和数据一致性带来隐患。MySQL8.0.30新版本为我们提供了一个新特性-(GeneratedInvisiblePrimaryKeys)简称GI......
  • [MySQL]为什么主键最好是有序递增的
    为什么主键索引最好是有序递增的我们在建表的时候,都会默认将主键索引设置为自增的,具体为什么要这样做呢?又什么好处?InnoDB创建主键索引默认为聚簇索引,数据被存放在了B+Tree的叶子节点上。也就是说,同一个叶子节点内的各个数据是按主键顺序存放的,因此,每当有一条新的数据插入时......
  • MySQL零基础入门教程-8.1 表的连接\增删数据、表结构的增删改、字段约束(非空、唯一
    教程来源:B站视频BV1Vy4y1z7EX001-数据库概述_哔哩哔哩_bilibili我听课收集整理的课程的完整笔记,供大家学习交流下载:夸克网盘分享本文内容为完整笔记的第三篇目录1、表怎么进行连接的2、insert语句可以一次插入多条记录吗?可以的!3、快速创建表?【了解内容】4、将查询结果......
  • 如何在Java服务中实现分布式ID生成:雪花算法与UUID的对比
    如何在Java服务中实现分布式ID生成:雪花算法与UUID的对比大家好,我是微赚淘客返利系统3.0的小编,是个冬天不穿秋裤,天冷也要风度的程序猿!在现代分布式系统中,唯一标识符(ID)的生成是一个关键问题。常见的ID生成方案包括雪花算法(Snowflake)和UUID(通用唯一识别码)。本文将对这两种方案进行详......
  • mysql为什么不推荐uuid做主键?
    在MySQL中,不推荐使用UUID作为主键的主要原因还是性能问题,其次是可读性差和浪费存储空间。性能问题:UUID是128位的字符串,通常被表示为32个字符的十六进制数。相比自增的整数(如AUTO_INCREMENT),UUID更大,占用的存储空间也更多,这会增加索引大小,导致查询变慢,尤其是在大表中。无序......
  • 自增主键去哪了?---一次开发过程中的思考
    前情提要:最近新接了一个需求,需要去创建两张表,其中有一张表需要根据业务id和业务类型建立唯一索引,对数据唯一性进行约束。因为涉及到业务嘛,表结构就进行缩略了表结构示例如下:CREATETABLE`example_table`(`id`bigint(20)unsignedNOTNULLAUTO_INCREMENTCOMMENT'......
  • 放弃使用UUID,ULID才是更好的选择!
    ULID:UniversallyUniqueLexicographicallySortableIdentifier(通用唯一词典分类标识符)UUID:UniversallyUniqueIdentifier(通用唯一标识符)#1.为什么不选择UUIDUUID目前有5个版本:版本1:在许多环境中是不切实际的,因为它需要访问唯一的,稳定的MAC地址,容易被攻击;版本2:将......
  • MybatisPlus 主键策略之type=IdType.ASSIGN_ID等详解
    雪花算法(雪花)是微博开源的分布式ID生成算法其核心思想就是:使用一个64位的长型的数字作为全局唯一ID,主要介绍了MybatisPlus 主键策略(type=IdType.ASSIGN_ID等详解),需要的朋友可以参考下: 我们可以通过@TableId注解的 类型属性来设置主键id的增长策略,一共有几个多个主键策略,......
  • UUIDV7: 我就是要用UUID做主键
    一直以来的互联网谣言:UUID是不适合用作数据库主键的.为什么?因为UUID是全随机的,对于数据库的索引不友好,插入时可能导致大量的索引树的分支合并.UUID根据RFC4122的描述,UUID被设计用于去中心化的ID生成格式128bit,16个字节,示例:f81d4fae-7dec-11d0-a765-00a0c91e6......
  • UUIDV7: 我就是要用UUID做主键
    一直以来的互联网谣言:UUID是不适合用作数据库主键的.为什么?因为UUID是全随机的,对于数据库的索引不友好,插入时可能导致大量的索引树的分支合并.UUID根据RFC4122的描述,UUID被设计用于去中心化的ID生成格式128bit,16个字节,示例:f81d4fae-7dec-11d0-a765-00a0c91e6......