ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

1GB硬盘能存多少条MySQL数据?InnoDB容量估算全拆解

1GB硬盘能存多少条MySQL数据?InnoDB容量估算全拆解 1GB的硬盘能存多少条MySQL数据这问题我几乎每次给团队做容量规划时都会遇到。第一次被问到时我也想直接甩一个数字过去比如“大概几百万条”但后来发现只要这么回答第二天就会有人回来补一句我一张表没几条数据文件却大得离谱你怎么解释其实“1GB能存多少条MySQL数据”这句话真正要拆开的不是“多少条”而是“什么样的表、什么行格式、多少索引、有没有大字段、系统日志占了多少”。同样一个1GB硬盘可能存下几千万条只有ID和数字的记录也可能只塞一两千条带着大JSON的业务数据。这篇文章不打算给你一个万能答案而是把从“硬盘容量”到“MySQL表能装几行”中间的每一层都拆开再给你几个可以直接用的估算方法和排查SQL。看完你至少能回答两个问题这张表实际占了多少空间设计表的时候怎么估容量。1. 先别急着算1GB和MySQL这两件事定义都没统一1.1 1GB是厂商的GB还是程序眼里的GiB你以为的1GB和硬盘厂商标的1GB严格说不是同一个数字。硬盘厂商习惯按十进制算1GB 1,000,000,000字节。而操作系统、MySQL这些软件内部按二进制算1GB 1,073,741,824字节。两者差了约73MB也就是7.3%左右。这个问题在日常买硬盘时无所谓但做容量规划时很要命。你用fdisk -l看一块标称1GB的硬盘系统识别出来的容量通常是1,073,741,824字节但如果你买的是独立显卡、U盘这些消费级存储标称1GB可能实际只有大概1,000,000,000字节甚至因为文件系统元数据占用可用空间还要更少。MySQL的innodb_page_size、data_length这些指标底层全部以字节为单位计算最后换算时也是按二进制单位。所以在这篇文章里我先统一按 1GiB 1,073,741,824 字节来算。如果你想更严谨先把磁盘真实容量查出来再进入后面的公式。1.2 MySQL的数据文件不只有表很多人一听到“1GB硬盘存MySQL数据”第一反应是“用户表能占1GB”。实际上你往里灌数据之前MySQL自己已经先吃掉了一部分空间。拿一套默认配置的MySQL 8.0举例数据目录里通常有ibdata1系统表空间默认12MB起步元数据、崩溃恢复信息都在里面undo_001和undo_002回滚段文件默认各10多MBredo log文件MySQL 8.0按innodb_redo_log_capacity控制默认100MB左右#ib_16384_0.dblwr这类双写缓冲区文件也是十几MB起步如果你开了binlogbinlog文件默认最多1GB一个。也就是说一个刚装好的MySQL光系统文件可能就占150MB以上。所谓“1GB硬盘能存多少数据”如果指的是“一台能跑MySQL的机器”那么用户表实际可用的空间可能只剩800MB左右。如果问的是“一个.ibd表文件能装多少行”那系统占用可以暂时放一边只算表空间本身。这个问题必须先说清楚否则后面所有数字都是空中楼阁。1.3 默认存储引擎InnoDB决定了怎么算MySQL 5.7和8.0的默认存储引擎都是InnoDB。MyISAM还在但做新项目基本没人会用。InnoDB的数据组织方式和MyISAM完全不同MyISAM表数据和索引分开两个文件行数据按插入顺序堆在.MYD里InnoDB则把表数据和主键索引放在同一个B树文件里每一行都作为主键索引的叶子节点存在。这个区别直接影响到“能存多少条”的计算方式。InnoDB不是按“一行多少字节”直接除1GB就完事它有一个最小分配单位叫做“页”page默认16KB。写入数据时行先放进页里页满了再申请新页表空间的大小其实是“页的个数 × 16KB”而不是“行的字节数总和”。所以后面你看到的每一条估算本质上都在回答一个问题16KB的页里能塞多少行然后1GB能分成多少个页。2. 一行记录的真实占用比你想的啰嗦2.1 row_format和隐藏列每条记录都有“出场费”建表时你可以指定行格式ROW_FORMAT常见有COMPACT、DYNAMIC。MySQL 5.7、8.0默认是DYNAMIC但底层思路和COMPACT一致每条记录除了你自己的字段外还要额外保存一堆管理和事务信息。InnoDB的聚簇索引记录大概包含这些隐藏部分记录头record header约5字节DB_TRX_ID事务ID6字节用于MVCCDB_ROLL_PTR回滚指针7字节用于指向undo log如果你建表时没有显式主键InnoDB还会额外生成一个6字节的DB_ROW_ID。这也是我强烈建议所有表都建主键的原因之一省6字节是小事物理存储更可控才是关键。也就是说每条记录天然要背负约18字节的额外开销和你的业务字段一点关系都没有。你建一张只有一个TINYINT字段的表一行也得占20多字节不可能低于这个数。2.2 varchar/char的字节陷阱字符集才是大头很多人算行大小只看类型。INT是4字节BIGINT是8字节DATETIME在MySQL 5.6.4以后默认5字节这些没问题。但到了VARCHAR就很容易翻车。VARCHAR(50)里的50不是50字节是50个字符。如果表的默认字符集是utf8mb4一个汉字最多占4字节一个emoji也可能占4字节纯ASCII字符才是1字节。所以VARCHAR(50)理论上最多200字节最少50字节取决于实际存进去的内容。除了数据本身变长字段还需要记录实际长度。一个VARCHAR不超过255字节时长度标识占1字节如果字段内容可能超过255字节长度标识要占2字节。CHAR则固定按定义长度占空间定义时同样要考虑字符集。CHAR(10)在utf8mb4下就是固定40字节。很多老项目把手机号设计成CHAR(11)因为纯ASCII没什么浪费但把昵称、备注这类字段误用CHAR每行就会白白多占几十字节。2.3 大字段溢出页TEXT/BLOB并没有“塞在行里”如果一行里有TEXT、BLOB或者特别长的VARCHARInnoDB不会傻到把全部内容堆在主键叶子页里。默认的DYNAMIC行格式下大字段的完整内容会被存到“溢出页”off-page storage而在表数据页里只保存一个20字节左右的指针。听起来好像很省空间但注意溢出页也是一个完整的16KB页只是里面装的是大字段碎片。如果一个TEXT字段实际只有200字节它也要占用一个溢出页的管理开销不可能只算200字节。更极端的情况是JSON类型内部实现也是基于二进制JSON格式结构体大、更新代价高占空间一点都不含糊。所以“一行字段多、字段大”时存储空间不是按“字段字节数求和”这么简单而是“主键叶子页里的紧凑行 一个或多个溢出页碎片”。这也是为什么很多人在information_schema里看到表的data_length远大于自己手算的行大小总和。2.4 一个真实例子空表到底多大为了让你感受一下“表文件总是比想象大”可以在MySQL里做个小实验CREATE DATABASE demo; CREATE TABLE demo.t ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL ) ENGINEInnoDB;然后去看这个表的磁盘文件文件t.ibd一开始通常有96KB、112KB这样的大小而不是0字节这是因为表空间初始化、页分配、段管理都需要元数据一个空表也要占用至少一个或多个“区”extent一个区默认1MB但初始化时有压缩分配机制。这就是为什么后面估算容量时必须把“表文件固定开销”也算进去。几十行的表表文件本身的大小可能比实际数据大几十倍。3. 从行到页再到B树一条公式算出可存行数3.1 16KB页里到底有多少字节是“能装行”的InnoDB一个默认页是16384字节但这16384字节不是都用来装记录还有页头、页尾、行目录数组等结构。做容量估算时不需要跟页内部字节较劲到个位数直接用“每页可用约16000字节”就足够精确。但每行还要额外占一个行目录项record pointer通常2字节。于是“每页能装多少行”大约可以这样算每页可容纳行数 16000 / (平均行大小 2)这里的“平均行大小”必须包含业务字段字节数变长字段长度标识记录头、事务ID、回滚指针等隐藏开销可空字段位图那一小部分成本通常每行按1字节预留。3.2 索引页也在占地方B树不是只存叶子InnoDB表的主键索引是一棵B树非叶子节点不会存完整行只存“索引键 指向子页的指针”。如果你的主键是INT非叶子节点里一条记录大约8字节左右一个页能放上千条。这意味着对于一棵三层B树如果叶子页有5万个上层非叶子页可能只有几十个甚至几个对总容量影响可以忽略。真正要命的是二级索引。每建一个二级索引就等于额外建立一棵B树树上的叶子节点存储“索引键 主键值”。同样是INT主键INT索引一个二级索引的叶子记录大约8字节1GB的硬盘里可能再造出一大堆索引页。很多业务表出现“数据没多少表文件却巨大”的第一原因不是行太宽而是索引太多。后面第4部分我会专门说怎么查索引占用。3.3 三个典型场景直接算给你看我统一用1GiB 1,073,741,824字节默认页16KB先假设表文件能完整吃掉这1GB忽略系统日志。页总数1,073,741,824 / 16,384 65,536页场景一窄表两个INTCREATE TABLE t1 ( id INT PRIMARY KEY, score INT NOT NULL ) ENGINEInnoDB;平均行大小4 4 18 26字节隐藏开销约18字节。算上行目录2字节每页装16000 / 28 ≈ 571行总行数571 × 65,536 ≈ 37,421,000行大概三千七百万行。这就是纯数字窄表能做到的上限。场景二常见业务表一行约50字节CREATE TABLE t2 ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, created_at DATETIME NOT NULL ) ENGINEInnoDB;假设name平均20字节ASCII情况字段加起来4 1 20 5 30字节加上隐藏开销18字节平均行大小48字节再加2字节行目录每页装16000 / 50 320行总行数320 × 65,536 ≈ 20,970,000行所以一张主键INT、一个短VARCHAR、一个时间的表1GB大概能存两千万行。这个数字在很多互联网业务里已经不算小了。场景三平均行大小1KB的业务表如果业务表有十几个字段再加一两个TEXT平均行大小约1000字节每页只能装16000 / 1002 ≈ 15行总行数15 × 65,536 ≈ 983,000行连一百万都不到。但注意如果TEXT字段很多且走溢出页实际行大小可能更接近指针大小而不是内容大小这种情况会更复杂。把上面三个场景放进一张表场景平均行大小含开销每页行数1GB可存行数约两个INT28字节5713700万INT VARCHAR(50) DATETIME50字节3202100万宽表/带TEXT1000字节1598万3.4 别忽略页填充率、碎片、日志上面的计算有一个理想假设每个页都装满表空间里没有垃圾页。现实中B树会发生页分裂删除操作会留下空洞随机主键会让页填充率下降。有人统计过InnoDB页的平均填充率能做到约75%到85%左右但这不是稳定值。另外InnoDB写入数据时还要用redo log、undo log这些也会消耗磁盘。1GB的磁盘如果正在跑业务事务提交越快redo日志占用的临时空间越大。还有临时表、排序文件、连接过程中的临时结果集都可能把磁盘撑爆。所以我的经验是真正做容量规划时计算出的理论行数至少留30%的余量也就是实际可安全存储行数 ≈ 理论行数 × 0.7如果表上有多个二级索引这个余量还要进一步压缩。4. 命令行里怎么验证别再拍脑袋估容量4.1 一条SQL看表实际占用MySQL给了一个很方便的视图information_schema.tables不用查操作系统文件就能看到表占用。比如SELECT table_name, table_rows, ROUND(data_length / 1024 / 1024, 2) AS data_mb, ROUND(index_length / 1024 / 1024, 2) AS index_mb, ROUND((data_length index_length) / 1024 / 1024, 2) AS total_mb FROM information_schema.tables WHERE table_schema demo AND table_name t;data_lengthInnoDB下表示主键索引聚簇索引叶子节点占用的总字节数index_length表示所有二级索引占用的总字节数table_rows只是一个估算值不一定准确不能作为精确行数。如果想看更准的行数只能SELECT COUNT(*)大表上执行要谨慎。4.2 用“影子表”实测平均行大小你可能会问平均行大小到底怎么量最简单的办法是拿一张业务表插入一批真实数据或者抽样数据然后对比占用变化。比如有一张t_order表先记下当前data_length再插入1万行测试数据再看data_length变化。假设插入前data_length是50MB插入后变成60MB那么平均每行占用(60 - 50) × 1024 × 1024 / 10000 ≈ 1048字节这个方法不精确但比纯按类型猜可靠很多因为有隐藏列、变长字段、溢出页和页填充率都已经被自动算进去了。如果想更稳可以多插几批数据取平均值。-- 插入前先记录 SELECT ROUND(data_length / 1024 / 1024, 2) FROM information_schema.tables WHERE table_schema test AND table_name t_order; -- 插入1万行 INSERT INTO t_order (...) VALUES ...; -- 插入后再记录做差4.3 文件系统层面怎么确认如果开了innodb_file_per_table且是独立表空间也可以直接在操作系统上观察.ibd文件大小ls -lh /var/lib/mysql/demo/t.ibd但注意表文件大小和data_length不是完全一样的。.ibd文件包含页管理信息、空闲页、碎片还有可能预分配的空间所以通常会略大于SQL查到的data_length index_length。两者对不上不用慌。4.4 给“1GB硬盘”留多少系统空间如果你真的想把MySQL跑在一个1GB的小硬盘上容量规划可以按这个顺序来先算MySQL系统文件占用通常给200MB以上再算binlog和redo log动态占用至少给总空间的15%到20%剩下的才给表数据为临时排序、临时表预留少量空间。所以现实中一块1GB硬盘能用来放业务表数据的可能只有600MB到700MB。如果业务表按前面的“场景二”每行50字节那实际可存约650MB / 50字节 ≈ 1300万行这时的“1GB能存多少条”就从理论两千万变成了实际一千多万。可见定义不同答案差得很远。5. 常见问题与避坑记录5.1 为什么表文件比估算值大那么多我见过很多次这种情况按字段类型算出平均行大小只有100字节结果实际表文件膨胀到理论的3倍。原因通常有这几个页碎片太多。频繁UPDATE变长字段或者频繁DELETE会让页内部留下空洞随机主键让B树频繁页分裂。比如UUID做主键插入顺序随机页分裂率高二级索引太多。一张表如果有10个二级索引index_length可能比data_length还大大字段溢出页碎片化。每个TEXT/BLOB都可能分配独立页即使数据只有几十字节。排查时先看index_length是否异常如果二级索引是主因砍掉低频查询的索引是最直接的优化。5.2 DELETE之后文件不缩小不代表“空间丢了”InnoDB默认不会因为DELETE就把表文件缩回去。你删掉的行所占据的页面会被标记为“可复用”后续新插入的数据可以占用这些空洞但文件大小不会降。这是正常行为不是空间泄漏。如果确实需要收缩表文件可以用OPTIMIZE TABLE demo.t;这个操作会重建表让文件变小。但注意两个坑重建过程中需要临时空间表越大临时空间需求越大线上高负载期间不要执行它可能锁表或带来较大的IO压力。我的建议是先看碎片比例。如果data_length里实际数据是10MB但表文件有100MB这时值得做一次重建如果表文件只比数据量大20%没必要折腾。5.3 二级索引多1GB被“偷”得更快回到标题“1GB可以存多少条MySQL数据”很多人默认只算表行数据忘了二级索引也是硬盘里的“数据”。比如CREATE TABLE t3 ( id INT PRIMARY KEY, uid INT NOT NULL, status TINYINT NOT NULL, INDEX idx_uid (uid), INDEX idx_status (status) ) ENGINEInnoDB;两个二级索引每个都对应一棵B树索引叶子节点保存“索引列 主键列”。如果业务查询真的需要这些索引这块空间省不掉如果索引只是顺手建的那它就是在白白偷走可用容量。排查方法很简单SELECT table_name, index_name, ROUND(stat_value * innodb_page_size / 1024 / 1024, 2) AS index_mb FROM mysql.innodb_index_stats WHERE table_name t3;也可以对比information_schema.tables里的index_length和data_length如果索引占比超过50%就要认真评估是否保留所有索引了。5.4 我在容量规划上的几条实际经验这些年做过很多次容量评估最后沉淀下来几条土办法写在这里供你参考第一不要相信“一张表能存多少行”这个问题存在标准答案。它一定建立在行格式、字段设计、索引策略、日志配置之上。别人告诉你的“几百万行”很可能和他的表结构强绑定换到你的场景完全不适用。第二用“平均行字节模型”做快速判断。建表时按真实业务数据抽样统计每行大概多少字节再留出100%的放大系数这比花几个小时查官方文档有用得多。比如估算每行100字节实际计划容量时按200字节算基本能覆盖碎片和二级索引。第三容量规划不是“存得下”就完事。1GB硬盘就算理论能存一千万行查询性能也早就崩了。InnoDB的数据量越大缓冲池命中率越难维持全表扫描耗时越长。更合理的做法是给业务表设置一个可接受的行数上限超过后做归档或分库分表。第四如果你想复现本文的计算最好直接拿你线上的一张表做实验看data_length、index_length、COUNT(*)算出实际每行字节数然后除以可用空间再乘以0.7。这个数才是可以写进监控告警里的安全容量。回到最初的问题1GB硬盘能存多少条MySQL数据窄表可以到几千万宽表可能不到一百万带一堆索引的中间表在一两百万附近波动。数字本身不重要重要的是你现在知道它是由哪些因素决定的也知道怎么去验证和翻盘。下次再有人问你这个问题你大可以先反问一句你的行多大索引多少日志留了多少空间这一问问题本身就解答了一半。
返回列表