当前位置: 首页 > news >正文

MySQL发号问题的分析和改进

这是学习笔记的第 2253 篇文章

读完需要

5

分钟

速读仅需3分钟

关于发号器的使用,其实有一个大背景,那就是关于主键的一些设计问题,在MySQL中如果一张表没有主键,实际的数据处理就有点麻烦了。

因为在InnoDB存储引擎中,表都是按照主键的顺序进行存放的,我们叫做聚簇索引表或者索引组织表(IOT)

(1)显式的创建主键Primary key。

(2)判断表中是否有非空唯一索引,如果有,则为主键。

(3)如果都不符合上述条件,则会生成UUID的一个隐式主键(6字节大)

可以使用类似的SQL来看到这个隐藏列,select _rowid from test ;

这和主键有什么关系?主要是因为有些时候我们创建主键就是为了创建而创建,没有实际的业务含义,所以会形成一种使用习惯,那就是启用自增列。

自增列的问题很多,有些几句话还说不清楚,大体有如下的一些问题

  • 自增列没有业务含义

  • 过度依赖自增列

  • 自增列和状态值主键并存,反而影响业务逻辑和性能

  • MySQL历史遗留bug,在MySQL 8.0该问题才修复

到了这里,我们的需求也基本明确了,我们所说的发号器其实就是要确保每次取到的ID号都是唯一的,当然也显而易见是趋势递增的。

我来说一个初版的发号器实现,假设我们创建一张表test_inc,假设按照业务逻辑,自增列的初始值为1000,则建表语句为:

create table test_inc(id int primary key auto_increment,flag varchar(20)) 
engine=innodb auto_increment=1000;
alter table test_inc modify flag varchar(20) unique;

建表语句为:

mysql> show create table test_inc\G
*************************** 1. row ***************************
       Table: test_inc
Create Table: CREATE TABLE `test_inc` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `flag` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `flag` (`flag`)
) ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=latin1

使用replace into的逻辑来进行id的初始化:

replace into test_inc(flag) values('1');

数据结果为:

mysql> select *from test_inc;
+------+------+
| id   | flag |
+------+------+
| 1000 | 1    |
+------+------+
1 row in set (0.00 sec)


mysql> replace into test_inc(flag) values('1');
Query OK, 2 rows affected (0.01 sec)


mysql> select *from test_inc;
+------+------+
| id   | flag |
+------+------+
| 1001 | 1    |
+------+------+
1 row in set (0.00 sec)

得到当前的写入id值为:

mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|             1001 |
+------------------+
1 row in set (0.00 sec)

按照这个逻辑在多会话并发中依然可以得到期望中的ID自增效果,这些也是last_insert_id()本身的功能范围,就不再赘述了。 

但是这种方案在RDS环境中会碰到一个很尴尬的问题,那就是RDS的服务会做内部切换,而切换后的ID值就会重置。

假设从库的id当前值为1002,在从库切换后,会提升为主库,即可以实现读写,那么在新主库上执行replace into语句结果就会让人奇怪,完整的模拟过程如下:

mysql> select * from test_inc;
+------+------+
| id   | flag |
+------+------+
| 1002 | 1    |
+------+------+
1 row in set (0.00 sec)


mysql>  replace into test_inc(flag) values('1');
Query OK, 2 rows affected (0.02 sec)


mysql> select * from test_inc;
+------+------+
| id   | flag |
+------+------+
| 1001 | 1    |
+------+------+
1 row in set (0.00 sec)


mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|             1001 |
+------------------+
1 row in set (0.00 sec)

碰到这种情况就很尴尬了。好端端的自增ID一下子被打回了原形,而如果结合主从复制的过程和replace into的逻辑,其实也不难分析出这个问题。

简而言之,对于自增列的使用,在如上的场景中是不能够胜任ID自增的逻辑的,可能会产生断层,我们可以通过别的方式来实现。 

在数据库中不难发现这样的设计成平,比如Oracle里面的sequence就是一个例子。 

我们干脆来实现下这个sequence的简单逻辑吧。 

mysql> create table sequence(id int primary key auto_increment);
Query OK, 0 rows affected (0.05 sec)


mysql> insert into sequence values(last_insert_id());
Query OK, 1 row affected (0.01 sec)

接下来需要做两类场景的测试,

验证方法一样:

mysql> select * from sequence;
+------+
| id   |
+------+
| 1003 |
+------+
1 row in set (0.00 sec)


mysql> update sequence set id=last_insert_id(id+1);
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0


mysql> select * from sequence;
+------+
| id   |
+------+
| 1004 |
+------+
1 row in set (0.00 sec)


mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|             1004 |
+------------------+
1 row in set (0.00 sec)

一种是多会话状态下验证ID自增的情况,使用last_insert_id()

另外一种则是在从库端直接刷新验证,通过测试可以快速验证得到这种方式的可行性。

当然这个还没有考虑复杂的并发场景,如果想提高吞吐量,可以考虑分布式ID的玩法。

QQ群号:763628645

QQ群二维码如下, 添加请注明:姓名+地区+职位,否则不予通过

订阅我的微信公众号“杨建荣的学习笔记”,第一时间免费收到文章更新。别忘了加星标,以免错过新推送提示。

7

   

近期热文

你可能也会对以下话题感兴趣。点击链接就可以查看。

相关文章:

  • 《心经》的力量
  • 对于新技术栈落地和架构思维的建议
  • 《KOF97》彩蛋揭秘:九龙城看台里塞进了21位大佬?
  • 最近收集的一些数据
  • 疫情下信息技术趋势的几点观察
  • 光大银行分布式实战:国内最大缴费平台的数据库架构转型
  • 职场建议:给新人和老鸟的几点建议
  • 教孩子骑自行车
  • 我用python掐指一算,2020高考分数和录取情况可能是这样
  • 免费好用的MySQL高可用方案
  • 数据说话:推荐MySQL类的一些图书
  • MySQL数据库升级的一些坑
  • 如何做一个聪明的老人,如何让自己更幸福?我有4个小建议
  • 技术分享 | MySQL binlog 日志解析
  • 工作中的半成品
  • 「前端早读君006」移动开发必备:那些玩转H5的小技巧
  • egg(89)--egg之redis的发布和订阅
  • 机器学习学习笔记一
  • 基于Android乐音识别(2)
  • 批量截取pdf文件
  • 数据结构java版之冒泡排序及优化
  • 提醒我喝水chrome插件开发指南
  • 写给高年级小学生看的《Bash 指南》
  • 新年再起“裁员潮”,“钢铁侠”马斯克要一举裁掉SpaceX 600余名员工 ...
  • ​ ​Redis(五)主从复制:主从模式介绍、配置、拓扑(一主一从结构、一主多从结构、树形主从结构)、原理(复制过程、​​​​​​​数据同步psync)、总结
  • #NOIP 2014# day.1 T2 联合权值
  • (003)SlickEdit Unity的补全
  • (二)windows配置JDK环境
  • (六)vue-router+UI组件库
  • (一)appium-desktop定位元素原理
  • (译) 理解 Elixir 中的宏 Macro, 第四部分:深入化
  • **CI中自动类加载的用法总结
  • .jks文件(JAVA KeyStore)
  • .NET8.0 AOT 经验分享 FreeSql/FreeRedis/FreeScheduler 均已通过测试
  • .vimrc php,修改home目录下的.vimrc文件,vim配置php高亮显示
  • /proc/stat文件详解(翻译)
  • @DependsOn:解析 Spring 中的依赖关系之艺术
  • @RequestParam详解
  • [.net] 如何在mail的加入正文显示图片
  • [20171101]rman to destination.txt
  • [2544]最短路 (两种算法)(HDU)
  • [AIGC] Kong:一个强大的 API 网关和服务平台
  • [android] 天气app布局练习
  • [Angularjs]asp.net mvc+angularjs+web api单页应用
  • [C# 开发技巧]如何使不符合要求的元素等于离它最近的一个元素
  • [C++提高编程](三):STL初识
  • [C进阶] 数据在内存中的存储——浮点型篇
  • [dfs] 图案计数
  • [Docker]十二.Docker consul集群搭建、微服务部署,Consul集群+Swarm集群部署微服务实战
  • [flask]http请求//获取请求体数据
  • [LeetCode]-225. 用队列实现栈
  • [SP1043] GSS1 - Can you answer these queries I
  • [VulnHub靶机渗透]:billu_b0x 快速通关
  • [创业] 让创业者惊讶的19个真相
  • [导入]MsAjax Lib- Array.indexOf 函数