暴走漫画网站建设中模板,天元建设集团有限公司张琥超,关于做网站的搞笑段子,自助建设网站平台本文已经收录到Github仓库#xff0c;该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点#xff0c;欢迎star~
Github地址
1 背景
作为在后端圈开车的多年…本文已经收录到Github仓库该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点欢迎star~
Github地址
1 背景
作为在后端圈开车的多年老司机是不是经常听到过“mysql 单表最好不要超过 2000w”,“单表超过 2000w 就要考虑数据迁移了”“你这个表数据都马上要到 2000w 了难怪查询速度慢”
这些名言民语就和 “群里只讨论技术不开车开车速度不要超过 120 码否则自动踢群”只听过没试过哈哈。
下面我们就把车速踩到底干到 180 码试试…….
2 实验
实验一把看看…
建一张表
CREATE TABLE person(
id int NOT NULL AUTO_INCREMENT PRIMARY KEY comment 主键,
person_id tinyint not null comment 用户id,
person_name VARCHAR(200) comment 用户名称,
gmt_create datetime comment 创建时间,
gmt_modified datetime comment 修改时间
) comment 人员信息表;插入一条数据
insert into person values(1,1,user_1, NOW(), now());利用 mysql 伪列 rownum 设置伪列起始点为 1
select (i:i1) as rownum, person_name from person, (select i:100) as init;
set i1;运行下面的 sql连续执行 20 次就是 2 的 20 次方约等于 100w 的数据执行 23 次就是 2 的 23 次方约等于 800w , 如此下去即可实现千万测试数据的插入如果不想翻倍翻倍的增加数据而是想少量少量的增加有个技巧就是在 SQL 的后面增加 where 条件如 id 某一个值去控制增加的数据量即可。
insert into person(id, person_id, person_name, gmt_create, gmt_modified)select i:i1,left(rand()*10,10) as person_id,concat(user_,i%2048),date_add(gmt_create,interval i*cast(rand()*100 as signed) SECOND),date_add(date_add(gmt_modified,interval i*cast(rand()*100 as signed) SECOND), interval cast(rand()*1000000 as signed) SECOND)from person;此处需要注意的是也许你在执行到近 800w 或者 1000w 数据的时候会报错The total number of locks exceeds the lock table size这是由于你的临时表内存设置的不够大只需要扩大一下设置参数即可。
SET GLOBAL tmp_table_size 512*1024*1024; 512M
SET global innodb_buffer_pool_size 1*1024*1024*1024 (1G);先来看一组测试数据这组数据是在 mysql8.0 的版本并且是在我本机上由于本机还跑着 idea , 浏览器等各种工具所以并不是机器配置就是用于数据库配置所以测试数据只限于参考。 最全面的Java面试网站 看到这组数据似乎好像真的和标题对应当数据达到 2000w 以后查询时长急剧上升难道这就是铁律吗
那下面我们就来看看这个建议值 2kw 是怎么来的
3 单表数量限制
首先我们先想想数据库单表行数最大多大
CREATE TABLE person(
id int(10) NOT NULL AUTO_INCREMENT PRIMARY KEY comment 主键,
person_id tinyint not null comment 用户id,
person_name VARCHAR(200) comment 用户名称,
gmt_create datetime comment 创建时间,
gmt_modified datetime comment 修改时间
) comment 人员信息表;看看上面的建表 sqlid 是主键本身就是唯一的也就是说主键的大小可以限制表的上限如果主键声明 int 大小也就是 32 位那么支持 2^32-1 ~~21 亿如果是 bigint那就是 2^62-1 36893488147419103232难以想象这个的多大了一般还没有到这个限制之前可能数据库已经爆满了
有人统计过如果建表的时候自增字段选择无符号的 bigint , 那么自增长最大值是 18446744073709551615按照一秒新增一条记录的速度大约什么时候能用完 4 表空间
下面我们再来看看索引的结构对了我们下面讲内容都是基于 Innodb 引擎的大家都知道 Innodb 的索引内部用的是 B 树 这张表数据在硬盘上存储也是类似如此的它实际是放在一个叫 person.ibd innodb data的文件中也叫做表空间虽然数据表中他们看起来是一条连着一条但是实际上在文件中它被分成很多小份的数据页而且每一份都是 16K。大概就像下面这样当然这只是我们抽象出来的在表空间中还有段、区、组等很多概念但是我们需要跳出来看。 5 页的数据结构
因为每个页只有 16K 的大小但是如果数据很多那一页肯定就放不下这些数据那数据肯定就会被分到其他的页中所以为了把这些页关联起来肯定就会有记录前后页地址方便找到对应页同时每页都是唯一的那就会需要有一个唯一标志来标记页就是页号
页中会记录数据所以会存在读写操作读写操作会存在中断或者其他异常导致数据不全等那就会需要有校验机制所以里面还有会校验码而读操作最重要的就是效率问题如果按照记录一个个进行遍历那肯定是很费劲的所以这里面还会为数据生成对应的页目录Page Directory; 所以实际页的内部结构像是下面这样的。 从图中可以看出一个 InnoDB 数据页的存储空间大致被划分成了 7 个部分有的部分占用的字节数是确定的有的部分占用的字节数是不确定的。
在页的 7 个组成部分中我们自己存储的记录会按照我们指定的行格式存储到 User Records 部分。
但是在一开始生成页的时候其实并没有 User Records 这个部分每当我们插入一条记录都会从 Free Space 部分也就是尚未使用的存储空间中申请一个记录大小的空间划分到 User Records 部分当 Free Space 部分的空间全部被 User Records 部分替代掉之后也就意味着这个页使用完了如果还有新的记录插入的话就需要去申请新的页了。
这个过程的图示如下 刚刚上面说到了数据的新增的过程。
那下面就来说说数据的查找过程假如我们需要查找一条记录我们可以把表空间中的每一页都加载到内存中然后对记录挨个判断是不是我们想要的在数据量小的时候没啥问题内存也可以撑但是现实就是这么残酷不会给你这个局面为了解决这问题mysql 中就有了索引的概念大家都知道索引能够加快数据的查询那到底是怎么个回事呢下面我就来看看。
6 索引的数据结构
在 mysql 中索引的数据结构和刚刚描述的页几乎是一模一样的而且大小也是 16K, 但是在索引页中记录的是页 (数据页索引页) 的最小主键 id 和页号以及在索引页中增加了层级的信息从 0 开始往上算所以页与页之间就有了上下层级的概念。
看到这个图之后是不是有点似曾相似的感觉是不是像一棵二叉树啊对没错它就是一棵树只不过我们在这里只是简单画了三个节点2 层结构的而已如果数据多了可能就会扩展到 3 层的树这个就是我们常说的 B 树最下面那一层的 page level 0, 也就是叶子节点其余都是非叶子节点。 看上图中我们是单拿一个节点来看首先它是一个非叶子节点索引页在它的内容区中有 id 和 页号地址两部分这个 id 是对应页中记录的最小记录 id 值页号地址是指向对应页的指针而数据页与此几乎大同小异区别在于数据页记录的是真实的行数据而不是页地址而且 id 的也是顺序的。
7 单表建议值
下面我们就以 3 层2 分叉实际中是 M 分叉的图例来说明一下查找一个行数据的过程。
比如说我们需要查找一个 id6 的行数据因为在非叶子节点中存放的是页号和该页最小的 id所以我们从顶层开始对比首先看页号 10 中的目录有 [id1, 页号 20],[id5, 页号 30], 说明左侧节点最小 id 为 1右侧节点最小 id 是 565, 那按照二分法查找的规则肯定就往右侧节点继续查找找到页号 30 的节点后发现这个节点还有子节点非叶子节点那就继续比对同理6567, 所以找到了页号 60找到页号 60 之后发现此节点为叶子节点数据节点于是将此页数据加载至内存进行一一对比结果找到了 id6 的数据行。
从上述的过程中发现我们为了查找 id6 的数据总共查询了三个页如果三个页都在磁盘中未提前加载至内存那么最多需要经历三次的磁盘 IO。
需要注意的是图中的页号只是个示例实际情况下并不是连续的在磁盘中存储也不一定是顺序的。
至此
我们大概已经了解了表的数据是怎么个结构了也大概知道查询数据是个怎么的过程了这样我们也就能大概估算这样的结构能存放多少数据了。
从上面的图解我们知道 B 数的叶子节点才是存在数据的而非叶子节点是用来存放索引数据的。
所以同样一个 16K 的页非叶子节点里的每条数据都指向新的页而新的页有两种可能
如果是叶子节点那么里面就是一行行的数据如果是非叶子节点的话那么就会继续指向新的页
假设
非叶子节点内指向其他页的数量为 x叶子节点内能容纳的数据行数为 yB 数的层数为 z
如下图中所示
Total x^(z-1) *y 也就是说总数会等于 x 的 z-1 次方 与 Y 的乘积。 X 在文章的开头已经介绍了页的结构索引也也不例外都会有 File Header (38 byte)、Page Header (56 Byte)、Infimum Supermum26 byte、File Trailer8byte, 再加上页目录大概 1k 左右我们就当做它就是 1K, 那整个页的大小是 16K, 剩下 15k 用于存数据在索引页中主要记录的是主键与页号主键我们假设是 Bigint (8 byte), 而页号也是固定的4Byte, 那么索引页中的一条数据也就是 12byte; 所以 x15*1024/12≈1280 行。 Y 叶子节点和非叶子节点的结构是一样的同理能放数据的空间也是 15k但是叶子节点中存放的是真正的行数据这个影响的因素就会多很多比如字段的类型字段的数量每行数据占用空间越大页中所放的行数量就会越少这边我们暂时按一条行数据 1k 来算那一页就能存下 15 条Y≈15。
算到这边了是不是心里已经有谱了啊
根据上述的公式Total x^(z-1) y已知 x1280,y15假设 B 树是两层那就是 Z 2 Total 1280 ^1 15 19200假设 B 树是三层那就是 Z 3 Total 1280 ^2 *15 24576000 约 2.45kw
哎呀妈呀这不是正好就是文章开头说的最大行数建议值 2000w 嘛对的一般 B 数的层级最多也就是 3 层你试想一下如果是 4 层除了查询的时候磁盘 IO 次数会增加而且这个 Total 值会是多少大概应该是 3 百多亿吧也不太合理所以3 层应该是比较合理的一个值。
到这里难道就完了
no
我们刚刚在说 Y 的值时候假设的是 1K 那比如我实际当行的数据占用空间不是 1K , 而是 5K, 那么单个数据页最多只能放下 3 条数据
同样还是按照 Z3 的值来计算那 Total 1280 ^2 *3 4915200 近 500w
所以在保持相同的层级相似查询性能的情况下在行数据大小不同的情况下其实这个最大建议值也是不同的而且影响查询性能的还有很多其他因素比如数据库版本服务器配置sql 的编写等等MySQL 为了提高性能会将表的索引装载到内存中。在 InnoDB buffer size 足够的情况下其能完成全加载进内存查询不会有问题。
但是当单表数据库到达某个量级的上限时导致内存无法存储其索引使得之后的 SQL 查询会产生磁盘 IO从而导致性能下降所以增加硬件配置比如把内存当磁盘使可能会带来立竿见影的性能提升哈。
8 总结
Mysql 的表数据是以页的形式存放的页在磁盘中不一定是连续的。
页的空间是 16K, 并不是所有的空间都是用来存放数据的会有一些固定的信息如页头页尾页码校验码等等。在 B 树中叶子节点和非叶子节点的数据结构是一样的区别在于叶子节点存放的是实际的行数据而非叶子节点存放的是主键和页号。
索引结构不会影响单表最大行数2kw 也只是推荐值超过了这个值可能会导致 B 树层级更高影响查询性能。
最后给大家分享一个Github仓库上面有大彬整理的300多本经典的计算机书籍PDF包括C语言、C、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等可以star一下下次找书直接在上面搜索仓库持续更新中~ Github地址