MySQL最大建议行数2000W?老司机做了个实验……

打印 上一主题 下一主题

主题 525|帖子 525|积分 1575

 
 
一、背景
 
作为在后端圈开车的多年老司机,是不是经常听到过,“mysql 单表最好不要超过 2000w”,“单表超过 2000w 就要考虑数据迁移了”,“你这个表数据都马上要到 2000w 了,难怪查询速度慢”。
 
这些名言民语就和“群里只讨论技术,不开车,开车速度不要超过 120 码,否则自动踢群”,只听过,没试过,哈哈。
 
下面我们就把车速踩到底,干到 180 码试试…….
 
二、实验
 
实验一把看看……建一张表:
  1. CREATE TABLE person(
  2. id int NOT NULL AUTO_INCREMENT PRIMARY KEY comment '主键',
  3. person_id tinyint not null comment '用户id',
  4. person_name VARCHAR(200) comment '用户名称',
  5. gmt_create datetime comment '创建时间',
  6. gmt_modified datetime comment '修改时间'
  7. ) comment '人员信息表';
复制代码
 
插入一条数据:
  1. insert into person values(1,1,'user_1', NOW(), now());
复制代码
  1.  
复制代码
利用 mysql 伪列 rownum 设置伪列起始点为 1
  1. select (@i:=@i+1) as rownum, person_name from person, (select @i:=100) as init;
  2. set @i=1;
复制代码
 
运行下面的 sql,连续执行 20 次,就是 2 的 20 次方约等于 100w 的数据;执行 23 次就是 2 的 23 次方约等于 800w , 如此下去即可实现千万测试数据的插入,如果不想翻倍翻倍的增加数据,而是想少量,少量的增加,有个技巧,就是在 SQL 的后面增加 where 条件,如 id > 某一个值去控制增加的数据量即可。
  1. insert into person(id, person_id, person_name, gmt_create, gmt_modified)
  2. select @i:=@i+1,
  3. left(rand()*10,10) as person_id,
  4. concat('user_',@i%2048),
  5. date_add(gmt_create,interval + @i*cast(rand()*100 as signed) SECOND),
  6. date_add(date_add(gmt_modified,interval +@i*cast(rand()*100 as signed) SECOND), interval + cast(rand()*1000000 as signed) SECOND)
  7. from person;
复制代码
 
此处需要注意的是,也许你在执行到近 800w 或者 1000w 数据的时候,会报错:The total number of locks exceeds the lock table size,这是由于你的临时表内存设置的不够大,只需要扩大一下设置参数即可。
  1. SET GLOBAL tmp_table_size =512*1024*1024; (512M)
  2. SET global innodb_buffer_pool_size= 1*1024*1024*1024 (1G);
复制代码
 
先来看一组测试数据,这组数据是在 mysql 8.0 的版本,并且是在我本机上,由于本机还跑着 idea , 浏览器等各种工具,所以并不是机器配置就是用于数据库配置,所以测试数据只限于参考。
 

 

 
看到这组数据似乎好像真的和标题对应,当数据达到 2000w 以后,查询时长急剧上升;难道这就是铁律吗?
 
那下面我们就来看看这个建议值 2kw 是怎么来的?
 
三、单表数量限制
 
首先我们先想想数据库单表行数最大多大?
  1. CREATE TABLE person(
  2. id int(10) NOT NULL AUTO_INCREMENT PRIMARY KEY comment '主键',
  3. person_id tinyint not null comment '用户id',
  4. person_name VARCHAR(200) comment '用户名称',
  5. gmt_create datetime comment '创建时间',
  6. gmt_modified datetime comment '修改时间'
  7. ) comment '人员信息表';
复制代码
看看上面的建表 sql,id 是主键,本身就是唯一的,也就是说主键的大小可以限制表的上限,如果主键声明 int 大小,也就是 32 位,那么支持 2^32-1 ~~ 21 亿;如果是 bigint,那就是 2^62-1 ?(36893488147419103232),难以想象这个的多大了,一般还没有到这个限制之前,可能数据库已经爆满了!!
 
有人统计过,如果建表的时候,自增字段选择无符号的 bigint , 那么自增长最大值是 18446744073709551615,按照一秒新增一条记录的速度,大约什么时候能用完?
 

 
四、表空间
 
下面我们再来看看索引的结构,对了,我们下面讲内容都是基于 Innodb 引擎的,大家都知道 Innodb 的索引内部用的是 B+ 树
 

 
这张表数据,在硬盘上存储也是类似如此的,它实际是放在一个叫 person.ibd (innodb data)的文件中,也叫做表空间;虽然数据表中,他们看起来是一条连着一条,但是实际上在文件中它被分成很多小份的数据页,而且每一份都是 16K。大概就像下面这样,当然这只是我们抽象出来的,在表空间中还有段、区、组等很多概念,但是我们需要跳出来看。
 

 
五、页的数据结构
 
因为每个页只有 16K 的大小,但是如果数据很多,那一页肯定就放不下这些数据,那数据肯定就会被分到其他的页中,所以为了把这些页关联起来,肯定就会有记录前后页地址,方便找到对应页;同时每页都是唯一的,那就会需要有一个唯一标志来标记页,就是页号;页中会记录数据所以会存在读写操作,读写操作会存在中断或者其他异常导致数据不全等,那就会需要有校验机制,所以里面还有会校验码,而读操作最重要的就是效率问题,如果按照记录一个个进行遍历,那肯定是很费劲的,所以这里面还会为数据生成对应的页目录(Page Directory); 所以实际页的内部结构像是下面这样的。
 

 
从图中可以看出,一个 InnoDB 数据页的存储空间大致被划分成了 7 个部分,有的部分占用的字节数是确定的,有的部分占用的字节数是不确定的。
 
在页的 7 个组成部分中,我们自己存储的记录会按照我们指定的行格式存储到 User Records 部分。
 
但是在一开始生成页的时候,其实并没有 User Records 这个部分,每当我们插入一条记录,都会从 Free Space 部分,也就是尚未使用的存储空间中申请一个记录大小的空间划分到 User Records 部分,当 Free Space 部分的空间全部被 User Records 部分替代掉之后,也就意味着这个页使用完了,如果还有新的记录插入的话,就需要去申请新的页了。这个过程的图示如下。
 

 
刚刚上面说到了数据的新增的过程。
 
那下面就来说说,数据的查找过程,假如我们需要查找一条记录,我们可以把表空间中的每一页都加载到内存中,然后对记录挨个判断是不是我们想要的,在数据量小的时候,没啥问题,内存也可以撑;但是现实就是这么残酷,不会给你这个局面;为了解决这问题,mysql 中就有了索引的概念;大家都知道索引能够加快数据的查询,那到底是怎么个回事呢?下面我就来看看。
 
六、索引的数据结构
 
在 mysql 中索引的数据结构和刚刚描述的页几乎是一模一样的,而且大小也是 16K, 但是在索引页中记录的是页 (数据页,索引页) 的最小主键 id 和页号,以及在索引页中增加了层级的信息,从 0 开始往上算,所以页与页之间就有了上下层级的概念。
 

 
看到这个图之后,是不是有点似曾相似的感觉,是不是像一棵二叉树啊,对,没错!它就是一棵树,只不过我们在这里只是简单画了三个节点,2 层结构的而已,如果数据多了,可能就会扩展到 3 层的树,这个就是我们常说的 B+ 树,最下面那一层的 page level =0, 也就是叶子节点,其余都是非叶子节点。
 

 
看上图中,我们是单拿一个节点来看,首先它是一个非叶子节点(索引页),在它的内容区中有 id 和 页号地址两部分,这个 id 是对应页中记录的最小记录 id 值,页号地址是指向对应页的指针;而数据页与此几乎大同小异,区别在于数据页记录的是真实的行数据而不是页地址,而且 id 的也是顺序的。
 
七、单表建议值
 
下面我们就以 3 层,2 分叉(实际中是 M 分叉)的图例来说明一下查找一个行数据的过程。
 
比如说我们需要查找一个 id=6 的行数据,因为在非叶子节点中存放的是页号和该页最小的 id,所以我们从顶层开始对比,首先看页号 10 中的目录,有 [id=1, 页号 = 20],[id=5, 页号 = 30], 说明左侧节点最小 id 为 1,右侧节点最小 id 是 5;6>5, 那按照二分法查找的规则,肯定就往右侧节点继续查找,找到页号 30 的节点后,发现这个节点还有子节点(非叶子节点),那就继续比对,同理,6>5&&6</strong>>>></strong>
参考资料
 

  • https://www.jianshu.com/p/cf5d381ef637
  • https://www.modb.pro/db/139052
  • 《MYSQL 内核:INNODB 存储引擎 卷 1》
 
作者丨京东云开发者

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

用户国营

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表