首页 > 数据库 >2000万的行数还是 MySQL 表的限制吗?

2000万的行数还是 MySQL 表的限制吗?

时间:2024-09-10 09:02:30浏览次数:11  
标签:缓存 缓冲 MySQL 查询 person 2000 行数 time select

传闻

网络上一直流传着一种观点,认为在单个MySQL表中,数据的行数一旦超过2000万,表的性能就可能受到影响。这种观点主要源于早些时候使用HDD硬盘存储时的经验。

2024年了,当我们使用基于SSD的MySQL数据库时,这种判断是否依然有效。换句话说,基于现代存储技术,MySQL表的行数是否仍然需要限制在2000万以内,以保证性能不受影响?如果这种限制仍然存在,那么背后的原因又是什么呢?

带着这个疑问,开始我们的验证。

环境

MySQL 版本:8.0.25
服务器内存:16GB
EBS 存储类型:通用 SSD

验证过程

创建模拟数据:创建了9个表,分别包含 10万、20万、50万、100万、200万、500万、1000万、2000万、3000万、5000万和6000万行。

1.创建几个具有相同模式的表:
CREATE TABLE row_test(
`id` int NOT NULL AUTO_INCREMENT,
`person_id` int NOT NULL,
`person_name` VARCHAR(200),
`insert_time` int,
`update_time` int,
PRIMARY KEY (`id`),
KEY `query_by_update_time` (`update_time`),
KEY `query_by_insert_time` (`insert_time`)
);
# test client
INSERT INTO {table} (person_id, person_name, insert_time, update_time) VALUES ({person_id}, {person_name}, {insert_time}, {update_time})

# copy
create table <new-table> like <table>
insert into (`person_id`, `person_name`, `insert_time`, `update_time`)
select `person_id`, `person_name`, `insert_time`, `update_time` from

person_id、person_name、insert_time 和 update_time 的值是随机的。

3.执行以下sql查询来测试性能。

select count(*) from <table>                             -- full table scan
select count(*) from <table> where id = 12345 -- query by primary key
select count(*) from <table> where insert_time = 12345 -- query by index
select * from <table> where insert_time = 12345 -- query by index, but cause 2-times index tree lookup

4.查看innodb缓冲池状态

SHOW ENGINE INNODB STATUS
SHOW STATUS LIKE 'innodb_buffer_pool_page%'

5.每次在表上测试完重启数据库!刷新 innodb 缓冲池以避免读取旧缓存并得到错误结果!

验证结果

查询 1。select count(*) from <table>

 

这种查询会造成全表扫描,这是MySQL不擅长的。
No-cache round:(第一轮)当缓冲池中没有缓存数据时,第一次执行查询。
Cache round:(Other round)当缓冲池中已经有数据缓存时执行查询,通常在第一次执行之后。
1.第一次执行的查询运行时间比后面的要长

 

MySQL之所以能提高查询效率,是因为它使用了innodb_buffer_pool来缓存数据页。

在首次执行查询操作之前,缓冲池内并没有存储任何数据,所以它需要从.idb文件中读取大量的数据页到内存中。一旦首次查询执行完毕,相关的数据就会被存储在缓冲池里。这样一来,后续的查询操作就可以直接从内存中获取计算结果,而无需再次进行磁盘I/O操作,从而大大提高了查询速度。这个过程在MySQL中被称为缓冲池预热。

2.select count(*) from <table>将尝试将整个表加载到缓冲池

 

我对比了实验前后innodb_buffer_pool的统计数据。如果查询执行后,缓冲池的大小足够,那么缓冲池的使用变化应当与表的大小相等。反之,如果缓冲池不够大,那么只会有一部分表数据被缓存在缓冲池中。

这是因为执行查询select count(*) from table会涉及到全表扫描的操作,即逐行统计表中的行数。若表中数据未被缓存,那么这一操作就需要将整个表的数据加载到内存中。

为何会这样呢?原因在于InnoDB支持事务处理,它不能保证在不同时间点,事务所看到的数据视图是完全一致的。因此,进行全表扫描是确保获得准确行数的唯一可靠方法。

3.如果缓冲池不能容纳全表,查询延迟会爆发。
 

 

 

可以观察到innodb_buffer_pool的大小对查询性能有着显著的影响。具体而言,当缓冲池大小设定为11G时,查询延迟的显著增加发生在表的大小达到50M的时候。

接着,我将缓冲池大小调整为7G,并再次运行查询。这次,我发现查询延迟的显著增加出现在表大小为30M的时候。

最后,我将缓冲池的大小进一步减小到3G,并重新执行查询。此时,查询运行时间的显著增加发生在表大小达到20M的时候。

从这些实验结果中,可以明显看出,如果表中的数据不能被有效地缓存到innodb_buffer_pool中,那么执行类似于“select count(*) from <table>”的查询时,就需要进行成本较高的磁盘I/O操作来加载数据。这种磁盘I/O操作会直接导致查询运行时间的显著增加。

4. 在不缓存的情况下,查询运行时间与表大小呈线性关系,与缓冲池大小无关。
 

无缓存循环运行时间由磁盘 I/O 决定,与缓冲池大小无关。select count(*)使用相同 IOPS 的存储磁盘预热缓冲池没有区别。

5. 如果表无法完全缓存在缓冲池中,那么无缓存轮和有缓存轮之间的查询运行时间差是恒定的。

同时注意到,尽管如果表无法完全缓存在缓冲池中会导致查询运行时间的突增,但运行时间是可预测的。无缓存轮运行时间和有缓存轮运行时间之间的差值是恒定的,无论表的大小如何。原因是表的部分数据被缓存在缓冲池中,这个差值表示了从缓冲池而不是磁盘进行查询所节省的时间。

查询 2、3:select count(*) from <table> where <index_column> = 12345

上述查询在执行过程中会充分利用索引。由于查询不是范围查询,因此它只需要按照B+树的路径从上到下依次查找所需的页面。在查找的过程中,这些页面会被缓存到innodb缓冲池中,以便后续快速访问。

创建的测试表其B+树的深度均为3,因此,进行缓冲区预热时需要进行大约3至4次的I/O操作。这种预热过程平均耗时为4至6毫秒。在完成预热后,如果再次运行相同的查询,它将直接从内存中获取结果,此时耗时仅为0.5毫秒,这与网络往返时间(RTT)相当。

如果某个缓存页面长时间没有被访问并从缓冲池中移除,那么当需要再次访问该页面时,就必须从磁盘中重新加载,这最多可能需要4次磁盘I/O操作

查询 4:select * from <table> where <index_column> = 12345


这个查询过程中,需要进行两次索引查找操作。

原因在于,执行select *语句时,除了索引中包含的信息外,还需要获取person_name和person_id这两个字段的数据,而这两个字段并不包含在当前的索引中。因此,在查询执行的过程中,数据库引擎不得不分别查找两个不同的B+树结构。首先,它会通过insert_time对应的B+树来定位到目标行的主键值;随后,再利用这个主键值,去查找主键B+树,从而获取该行的完整数据记录。

整个查找过程可以参考下面的图示说明。


这就是我们在实际生产环境中应该尽量避免使用`select *`查询的原因。

根据我们在实验中所获得的数据,这种查询方式加载的页面块数量是查询2或查询3的两倍之多,最多可达到8倍。此外,`select *`查询的平均运行时间也明显较长,介于6至10毫秒之间,这同样是查询2或查询3所需时间的1.5到2倍。

为何会有这种传言


首先,我们需要深入了解InnoDB索引页的物理结构。在默认情况下,页面大小为16KB,其结构包括页眉、系统记录、用户记录、页面导向器和尾部等部分。实际上,用于存储实际数据的空间大约是15KB到14KB。

假设使用INT类型作为主键,它占用4字节,而每行的有效负载是1KB。那么,每个叶页能存储的行数大约是15行,因为除了数据本身,每行还需要额外的8字节用于存储指向该页的指针,所以总共是4字节(主键)+ 8字节(指针)= 12字节。

基于这样的计算,每个非叶页最多可以容纳的指针数量是15KB除以每个指针占用的12字节,即大约1280个指针。

如果有一个4层的B+树,那么它理论上最多可以容纳的行数就是每一层指针数量的乘积,即1280乘以1280再乘以15,等于大约24.6M行数据。

回顾过去,当HDD在市场上占据主导地位,而SSD对于数据库来说还是一种昂贵的技术时,4次随机I/O操作可能是我们可以接受的性能底线。而使用需要2次索引树查找的查询,其性能甚至可能更差。那时的工程师们努力控制索引树的深度,以免它们生长得过于庞大。

然而,随着技术的进步,现在SSD已经越来越普及,随机I/O操作的成本也相对较低。因此,我们或许可以重新评估那些十年前制定的规则。

顺便一提,如果是一个5层的B+树,它理论上可以容纳的行数会更多,计算下来大约是1280乘以1280再乘以1280再乘以15,即31.4B行数据。这个数量实际上已经超过了INT类型主键所能表示的最大行数。

当然,每行数据的大小不同,也会对B+树能够容纳的行数产生影响。这些不同的假设会导致不同的“软限制”,也就是实际使用中可能达到的最大行数,这个数字可能会小于或大于20M。例如,在我的实验中,每行数据大约是816字节(因为我使用了utf8mb4字符集,每个字符占用4个字节),在这种情况下,一个4层的B+树能够容纳的行数软限制大约是29.5M。

个人观点:考虑到SSD现在的普及,2000万行并不是MySQL表的一个非常有效的软限制。  

标签:缓存,缓冲,MySQL,查询,person,2000,行数,time,select
From: https://www.cnblogs.com/ataoxz/p/18405756

相关文章

  • MySQL timestamp和datetime用法详解
    一、MySQL中如何表示当前时间?其实,表达方式还是蛮多的,汇总如下:CURRENT_TIMESTAMPCURRENT_TIMESTAMP()NOW()LOCALTIMELOCALTIME()LOCALTIMESTAMPLOCALTIMESTAMP() 二、关于TIMESTAMP和DATETIME的比较一个完整的日期格式如下:YYYY-MM-DDHH:MM:SS[.fraction],它可分为两部......
  • MySQL 8.0修改密码
    最近系统升级牵涉到MySQL升级,需要升级到MySQL8.0,涉及MySQL用户的密码修改,特地记录一下!MySQL8.0前修改密码在MySQL8.0前,执行:SETPASSWORD=PASSWORD('[新密码]')进行密码修改,在MySQL8.0后,以上的方法使用root用户修改别的用户密码是报错的,因为MySQL8.0后修改了修改密码的方......
  • 【开源dcluster】Seatunnel数据同步之MySQL同步到doris
    源码Gitee地址:https://gitee.com/zhenglv123456/dcluster在线文档:https://47.121.127.33:8090/在线体验:http://36.155.14.171:12345/dolphinscheduler/ui/login账号密码:test/test123 创建同步任务操作步骤:1.点击创建任务 2.配置同步脚本 3.设置同步时间......
  • mysql 调优
    一、缓冲池​​​​​14.5.1BufferPool缓冲池是主内存中的一个区域,InnoDB在访问表和索引数据时将其缓存。缓冲池允许直接从内存访问经常使用的数据,从而加快处理速度。在专用服务器上,高达80%的物理内存通常分配给缓冲池。为了提高大容量读取操作的效率,缓冲池被划分为可能容纳多......
  • Python 操作 MySQL 数据库
    什么是MySQLdb?如何安装MySQLdb?数据库连接创建数据库表数据库插入操作数据库查询操作数据库更新操作删除操作执行事务错误处理Python标准数据库接口为PythonDB-API,PythonDB-API为开发人员提供了数据库应用编程接口。Python数据库接口支持非常多的数据库,你......
  • flask-docker更新(mysql更新为容器)
    前言之前用docker编写了一个flask的简单demo,但是里面的mysql用的本地的,当时是自己偷懒用了本地的,现在空余时间重新处理了mysql容器重新更新一下一、docker-compose.yml示例version:'3'services:flask:build:context:./appcontainer_name:flask......
  • Mysql Innodb存储引擎原理—链接如下
    MysqlInnodb存储引擎|ProcessOn免费在线作图,在线流程图,在线思维导图ProcessOn是一个在线协作绘图平台,为用户提供强大、易用的作图工具!支持在线创作流程图、思维导图、组织结构图、网络拓扑图、BPMN、UML图、UI界面原型设计、iOS界面原型设计等。同时依托于互联网实现了人......
  • 记录一次【截止目前最新版本MySql安装教程】MySql-9.0.1-winx64
    本次记录是目前最新版本9.0.1的安装记录,跟之前版本还是有区别的MySQL社区版下载地址:https://dev.mysql.com/downloads/mysql/安装整体步骤如下:下载MySQL版本;配置环境变量也可以不配置配置安装配置文件my.ini执行安装命令mysqld--install创建随机密码mysqld--initialize--con......
  • [MySQL]一次死锁排查
    字节面试题在可重复隔离级别下,这种情况会发生什么?答案:死锁下面我们用一个具体的例子来展示:首先我们有一张这样的表下面我们来执行一些语句,可以看到结果输出了,检测到发生了死锁Deadlockfoundwhentryingtogetlock;tryrestartingtransaction下面具体分析,首先......
  • 部署K8S集群(四):部署MySQL
    密码文件:MySQLSecret#VGVzdA==->Test#ZGJyZWFkZXI=->root#cGFzc0AxMjM0->pass@1234mkdir-p/opt/mysqlmkdir-p/data/mysqlcat>/opt/mysql/mysql-secret.yaml<<EOFapiVersion:v1kind:Secretmetadata:name:mysql-secrettype:......