惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
P
Palo Alto Networks Blog
T
ThreatConnect
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
T
True Tiger Recordings
P
Privacy & Cybersecurity Law Blog
B
Blog
IT之家
IT之家
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
C
Comments on: Blog
Microsoft Azure Blog
Microsoft Azure Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
N
News and Events Feed by Topic
NISL@THU
NISL@THU
腾讯CDC
雷峰网
雷峰网
Security Latest
Security Latest
李成银的技术随笔
M
Microsoft Research Blog - Microsoft Research
L
LangChain Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Check Point Blog
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
博客园 - Franky
N
News | PayPal Newsroom
V
V2EX
A
About on SuperTechFans
The Register - Security
The Register - Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
MyScale Blog
MyScale Blog
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
爱范儿
爱范儿
A
Arctic Wolf
L
LINUX DO - 最新话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - 喜欢Ⅰ

人类随机数趣闻 - 即使人们会觉得它更随机,但实际上它更不随机 C 里面如何使用链表 list 项目代码套路 墨菲定律 - 人类一种误判心理 消息队列, 一种取舍的选择 Redis Stream CORS 跨域请求一种后端适配解决方案 自我的智慧 市场教父 André Kostolany Exception Handling Considered Harmful 面试题: 字符串转整型 终结者 atomic 原子自增工程案例 - 喜欢Ⅰ HTTP 尝试获取 Client IP 对炒股看法 吃饱年代 我是个怎样的人 格林童话之祖父和孙子 Linux 守护进程 智慧 ~ 引子 ~ 三则故事 交易人生
MySQL CREATE TABLE Template 模板设计简单交流
喜欢Ⅰ · 2022-03-21 · via 博客园 - 喜欢Ⅰ
倒排索引为什么叫倒排索引?

更加具体点, 我们看看 MySQL InnoDB 引擎中怎么定义和实现 AUTO_INCREMENT.

1. InnoDB 中 AUTO_INCREMENT 配置大致说明

  • 1.1 innodb_auto_inc_lock_mode=0 (traditional lock mode)
  • 获取表锁 (AUTO-INC 锁, 特殊表锁), 语句执行结束后释放, 不需要等事务结束.
  • 分配的值也是一个个分配,是连续的. (如果事务 rollback 了这个 auto_increment 值就会浪费掉, 从而造成间隙)
  • 1.2 innodb_autoinc_lock_mode=1 (consecutive lock mode, MySQL 8.0 之前默认选项)
  • 对于不确定插入数量的语句(例如 INSERT ... SELECT, REPLACE ... SELECT 和 LOAD DATA)
  • innodb_autoinc_lock_mode=0 一样获取表锁, 其他的确定数量的语句在执行前先批量获取 id,
  • 之后再走的是轻量级互斥锁, 如果其他事务已经获取表锁, 这个时候也需要等待.
  • 1.3 innodb_autoinc_lock_mode=2 (interleaved lock mode, MySQL 8.0+ 默认)
  • 采用乐观锁, CAS 更新计数器获取. 正常情况性能最好, 因为没有表锁和轻量级互斥锁.
  • 但在高并发引发的 高 CPU load 场景会适得其反, 加剧这种 CPU 浪费.
  • AUTO_INCREMENT CAS 频率高, 同一个语句操作内部 CAS INC 大概率也会让 id 间隙变大.

2. InnoDB 中 AUTO_INCREMENT 实现大致思路

在 MySQL 8.0 之前 AUTO_INCREMENT 值存储在内存中. 每次重启通过 select max id 初始化值.

-- 大致方式通过行级锁(排他锁) MAX(id) -> AUTO_INCREMENT init value
SELECT MAX(ai_col) FROM [table_name] FOR UPDATE;

在 MySQL 8.0 之后, 持久化存储在磁盘. 每次更新会写入 redo log 中, 也会刷入 innodb 引擎系统表中记录下来.

如果 MySQL 正常关闭重启, 会从引擎系统表中获取计数器的值.

如果 MySQL 故障重启, 也会从引擎系统表中获取计数器的值;

并且从最后一个检查点开始扫描 redo log 中记录的计数器值; 取二者最大值作为新值.

但是这个处理逻辑也不能保证最后拿到的值是正确的.

如果在系统文件落盘前崩溃, 那么就可能拿到一个之前使用过的值. 这也是数据备份和同步时候可能引发主键冲突根源.

小部分场景

通过对 InnoDB 的 AUTO_INCREMENT 了解, 大致猜测到他的优缺点和使用领域以及现状.

交流 1: 如果考虑分布式场景呢, 高性能领域呢 ?

这时候推荐使用分布式唯一 ID 生成算法器. (用更复杂大炮干复杂长枪) 替代 AUTO_INCREMENT.

补充说明, 在普通领域 AUTO_INCREMENT 也是个长枪级别 ID 生成器.

原理

-- sequece id 生成器表
CREATE TABLE sequece (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '物理主键, 自增 id',
    stub char(1) NOT NULL DEFAULT '' COMMENT '打桩靶子',
    UNIQUE KEY unique_key_stub (stub)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT = '古老分布式 id 生成器';


-- 获取 id
DELIMITER $
BEGIN
REPLACE INTO sequece(stub) VALUES ('X');
SELECT LAST_INSERT_ID();
COMMIT
$

部署

我们也可以多台机器部署, 设置不同 AUTO_INCREMENT step, 让每个 sequece 产生不同号码.

例如部署 step = 2 个服务结点, 并行获取数据.

一个 from 1, 3, 5, 7, 9 ...

一个 from 2, 4, 6, 8, .. .

详细部署操作指导

-- step 标识增长步长, 也标识分布式机器数

show global variables like 'auto_increment%'

+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| auto_increment_increment | 1     |
| auto_increment_offset    | 1     |
+--------------------------+-------+

-- auto_increment_increment 全局步长
-- auto_increment_offset 自增起始值

-- 设定自增步长
-- set session 设置当前会话链接, set global 设置当前 ID 机器
set global auto_increment_increment=step

for i : [0, step)
    CREATE TABLE sequece (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '物理主键, 自增 id',
        stub char(1) NOT NULL DEFAULT '' COMMENT '打桩靶子',
        UNIQUE KEY unique_key_stub (stub)
    ) ENGINE=InnoDB AUTO_INCREMENT = [offset + i] DEFAULT CHARSET=utf8mb4 COMMENT = '古老分布式 id 生成器';

这种古老 MySQL 分布式 ID 生成器, 方案成熟部署简单. 在高并发领域存在 DB 性能瓶颈.

如果考虑高可用主从架构, 在主服务挂了, 从服务顶上时候存在重复发号可能.

关于 分布式唯一 ID 其它业界解决方案, 后面有机会再聊.

交流 2: 为什么官方推荐 AUTO_INCREMENT 当主键, 而很少见到 UUID 等等?

MySQL InnoDB 引擎默认主键索引是 B+ 线索树索引, 也称为聚簇索引(聚集索引, row key 和 row value 存在连续内存中),

为何叫聚簇索引呢?

在 InnoDB 中, 每个表都会有一个聚簇索引, 在定义了主键(primary key)的情况下,

主键所在的列会被作为聚簇索引存储. 所谓聚簇索引,意思是数据实际上是存储在索引的叶子节点上,

「聚簇」的含义就是和相邻的数据紧凑地存储在一起.

因为不值得同时把数据行存储在两个不同的位置,所以一个表只能有一个聚簇索引.

关于 InnoDB 选择哪个列作为聚簇索引存储,大概的优先级为:

如果定义了主键(primary key), 则使用主键;

如果没有定义主键, 则选择第一个不包含 NULL(NOT NULL)的 UNIQUE KEY;

如果也没有, 则会隐式定义一个主键作为聚簇索引.

MySQL 读取磁盘上的数据是一页一页读取的, 如果某条我们要处理的数据在某一页中,

但是这一页其他数据我们都不关心, 这样的请求多了, 性能会急剧下降, 类似于 CPU 的并发杀手 false sharing.

伪共享 (false sharing) 的非标准定义为:
缓存系统中是以缓存行 (cache line) 为单位存储的. 当多线程修改互相独立的变量时,
如果这些变量共享同一个缓存行, 就会无意中影响彼此的性能, 这就是伪共享.

按照 B+ 线索平衡树的原理, AUTO_INCREMENT 的 ID 能保证最新的数据在一页中被读取, 而且减少了 B+ 树分裂翻转.

UUID 等唯一 ID 由于无序, 插入时, B+ 树会不断翻转, 并且最新的数据可能不在同一页.

很可能会出现, 最新一条数据, 和好几年前的数据在同一页.

例如购物和支付交易的订单, 节日促销的抽奖活动这类业务都有这样的使用场景, 访问频率在最近一天, 一周,

或者几个月内比较活跃, 而超过一段时间内的数据很少访问.

当然架构设计是当下业务和未来业务场景之间取舍.

抛开 MySQL AUTO_INCREMENT 的 ID 分布式和锁性能瑕疵, 在尝试分库分表时候他就变得有点累赘. 

一定要用小业务也可以试试研究 MySQL UUID_SHORT() 函数,生成一个 64 位唯一无符号整数. 

UUID_SHORT() 返回值的构造是这样的:(server_id & 255) << 56 + (server_startup_time_in_seconds << 24) + incremented_variable++;

  • 在 server_id 当前服务器的值介于 0 和 255 之间,您的设置主从服务器中是唯一的
  • 您不会在 mysqld restarts 之间设置服务器主机的系统时间
  • UUID_SHORT() 在 mysqld 重启 之间, 你平均每秒调用的次数少于 1600 万次

详细可以查阅更多资料, 也是一种选择. 

后记

行业技术升级变革同样也是是有条不絮.

本文以非常小的点 CREATE TABLE 为切入同大家交流, 欢迎补充纠错. 

对于我们奋斗在一线开发工程师而言, 最主要不是虚拟世界航母上神仙斗法, 更多是现实世界通过技术传承为商业项目把桩打稳打牢固.