MYSQL 大量插入数据失败后,磁盘空间却被占用

本文探讨了在MySQL中大量插入数据后磁盘空间的占用情况。通过Python脚本模拟插入100万条记录到数据库,观察数据_free的变化。在数据插入失败的情况下,磁盘空间占用并未显著减少。文章还提到了使用OPTIMIZE TABLE进行空间优化,并通过查询信息表获取数据库的详细占用情况。

最近有人问,在MYSQL中大量插入数据失败后,磁盘空间被占用了不少,然后磁盘空间到底怎么样, 我们先模拟一下这个环节.

先找一个大表,或者现生成一个

#!/usr/bin/python3
# -*- coding: UTF-8 -*-
import mysql.connector
from mysql.connector import errorcode
import sys
import threading
import time
def main():
    try:
        mysqlconn = mysql.connector.connect(host="192.168.198.66", user="admin", password="1234.Com",database='test')
        mycursor = mysqlconn.cursor()
        mycursor.execute("create database IF NOT EXISTS test_p")
        mycursor.execute("drop table if exists test_p ")
        mycursor.execute(
            "create table test_p(id INT auto_increment,name VARCHAR(256), marks smallint,create_date datetime,primary key(id))")
        i = 1
        while i < 1000000:
            value = (i, 1)
            sql_stm = """insert into test_p (name,marks,create_date) values (%s,%s,now())"""
            mycursor.execute(sql_stm, value)
            mysqlconn.commit()
            print(i)
            i += 1
        mycursor.close
        mysqlconn.close
    except mysql.connector.Error as err:
        if err.errno == errorcode.ER_ACCESS_DENIED_ERROR:
            print("Something is wrong with your user name or password")
        elif err.errno == errorcode.ER_BAD_DB_ERROR:
            print("Database does not exist")
        else:
            print(err)
    else:
        mysqlconn.close()

if __name__ == "__main__":
    main()

下面是MYSQL 的页面定义, 以及图形化后的页面形式.

通过上面的信息我们大致知道 这个48MB的磁盘空间里面的数据,共占用了 3072 PAGES ,B-tree node 使用了 2461 , 估计熟悉MYSQL的小伙伴们,头脑里面已经有了那个 树形的图.

 SELECT  table_schema as 'DATABASE',  table_name as 'TABLE',  CONCAT(ROUND(( data_length + index_length ) / ( 1024 * 1024 ), 2), 'M') 'TOTAL',  CONCAT(ROUND(data_free / ( 1024 * 1024 ), 2), 'M') 'DATAFREE'  FROM information_schema.TABLES  where table_schema='test' and table_name='test_p';

从上面的脚本中我们获得,仅仅插入的表中,我们的 data_free 就有6MB .

下面我们来进行这个测试

我们让数据插入,人为的失败.在看磁盘空间的占用方式,的确,数据插入成功和失败占用的磁盘空间并没有差.

由于计算方式,上图给出的datafree 并不准.

我们在证明一下到底实际占用的空间是多少,在操作完  OPTIMIZE TABLE  test_1;  后

页面重新分配

<think>嗯,用户这次问的是MySQL插入百万数据后磁盘占用明显的原因。这个问题挺有意思的,因为直觉上插入数据应该会显著增加磁盘占用才对。让我先梳理下可能的原因。 用户可能是在做性能测试或者数据迁移时遇到这个现象。ta的困惑点在于:明明插入大量数据为什么df命令显示磁盘空间没怎么变化?这背后肯定有MySQL的存储机制在起作用。 从技术角度看,首先要考虑InnoDB的存储机制。InnoDB是实时分配物理空间的,它的表空间文件(ibd)是预分配的。就像引用[4]提到的,MySQL8默认配置下,表空间文件初始大小是96KB,每次按特定比例扩展。所以插入百万数据可能还没触发文件扩展。 然后是页面填充率的问题。InnoDB的数据页默认16KB,新插入数据会优先填满现有页面的空闲空间。只有当所有页面都满了才会分配新页面。用户插入数据可能正好利用了这些"空隙"。 表碎片也是关键因素。特别是当用户反复删除又插入数据时,会产生大量碎片空间。这些空间会被新插入数据复用,看起来就像磁盘没增加一样。 另外要检查索引状态。如果表有索引,插入数据时需要维护索引结构。但引用[2]指出索引维护会消耗资源,而索引本身也占用空间过如果用户插入数据主键连续,可能B+树分裂较少,空间增长就明显。 还有redo日志的机制。MySQL会先写日志再写数据盘,在checkpoint之前数据可能还在内存缓冲池里。这时候用df命令确实看空间变化。 对了,innodb_fill_factor参数也影响页面填充率。默认100%的页面在插入时可能还有预留空间(PCT_FREE),特别是对于非主键索引页。 文件系统层面也有讲究。比如ext4/xfs的稀疏文件特性,或者fallocate的预分配机制,都可能让文件实际占用小于逻辑大小。 最后想到临时表场景。引用[3]讨论过批量插入临时表的优化,如果用户用的是内存临时表,数据根本会写盘;如果是磁盘临时表,用完就删自然空间。 用户可能需要具体排查方法。比如查看information_schema的表大小统计,用SHOW TABLE STATUS看Data_free碎片空间,或者检查binlog/undo日志状态。实际案例中,常见原因就是碎片复用和预分配机制。 要要提醒用户注意性能问题?引用[1][2]都提到百万插入时的锁竞争和IO瓶颈。虽然这次问题聚焦空间占用,但优化插入性能时关闭索引、批量提交等手段确实会影响空间表现。</think>在MySQL插入百万数据后磁盘占用明显,通常由以下核心机制共同作用导致: --- ### **一、InnoDB存储引擎的核心机制** 1. **预分配空间(表空间扩展)** InnoDB的表空间文件(`*.ibd`)采用**预分配策略**。当文件写满时,MySQL会一次性扩展一定大小(如4MB/16MB),而非逐条分配。若插入数据量未触发下一次扩展,磁盘占用会立即变化。 - 验证命令: ```sql SHOW TABLE STATUS LIKE '表名'; -- 查看Data_length(逻辑大小)和Data_free(碎片空间) ``` 2. **数据页复用与碎片空间** - **删除数据后的空间标记**:删除数据时,InnoDB仅标记对应数据页为"可复用",物理空间释放。 - **新数据填充碎片**:新插入数据优先使用这些碎片空间,而非申请新磁盘空间。 - 现象:若表中存在大量删除操作,插入百万数据可能完全被碎片空间吸收。 3. **变更缓冲(Change Buffer)延迟写入** 对于非唯一索引的修改(如二级索引更新),InnoDB可能将变更暂存于内存中的**Change Buffer**,后续异步合并到磁盘。短期内磁盘写入量可能低于预期。 --- ### **二、文件系统与操作系统的优化** 1. **稀疏文件(Sparse Files)** 现代文件系统(如ext4/XFS)支持稀疏文件:当InnoDB预分配表空间时,系统可能仅记录逻辑大小,实际占用物理块按需分配。`df`命令显示的"已用空间"可能小于文件逻辑大小。 2. **操作系统缓存(Page Cache)** 数据写入时,操作系统优先缓存到内存的**Page Cache**,异步刷盘。若未触发`fsync`,数据尚未落盘,磁盘占用变。 - 风险:此时服务器宕机会丢失数据(需平衡性能与持久性)。 --- ### **三、日志与事务机制的影响** 1. **redo日志的循环写入** redo log(`ib_logfile*`)是固定大小的循环文件。事务提交时写入redo log,但**日志空间被覆盖复用**,会持续增长。 2. **undo日志的延迟清理** 长事务或未提交的事务可能使undo日志保留旧数据版本,但undo表空间有独立回收机制(如`innodb_undo_log_truncate`),短期插入通常引发显著增长。 --- ### **四、索引维护的隐藏成本** 1. **索引页面分裂的延迟性** 新数据插入可能导致B+树索引页分裂,但若主键连续递增,分裂频率较低。且分裂后新页面可能复用预分配空间(参考第一节)。 2. **索引统计信息更新** 索引统计信息(如`innodb_stats_persistent`)的更新可能消耗内存和CPU,但**直接增加磁盘占用**。 --- ### **五、数据压缩与行格式** - 若使用**压缩行格式**(如`ROW_FORMAT=COMPRESSED`)或**透明页压缩**(`innodb_compression_algorithm`),数据物理大小可能远小于逻辑大小。 - 验证方法: ```sql SELECT * FROM information_schema.INNODB_TABLESPACES WHERE NAME = '表空间名'; -- 查看FILE_SIZE(逻辑大小)与 ALLOCATED_SIZE(实际占用) ``` --- ### **六、排查建议** 1. **确认实际磁盘占用** ```bash du -sh /var/lib/mysql/数据库名/表名.ibd # 查看物理文件大小 ls -lh /var/lib/mysql/数据库名/表名.ibd # 对比逻辑大小 ``` 2. **检查表空间碎片** ```sql SELECT TABLE_NAME, DATA_FREE FROM information_schema.TABLES WHERE TABLE_SCHEMA = '数据库名'; -- DATA_FREE > 1MB 表明存在碎片空间复用 ``` 3. **监控存储增长趋势** 使用工具(如`pt-diskstats`)持续观察磁盘I/O和空间变化,排除延迟刷盘影响。 --- ### **总结原因** | **现象** | **根本原因** | |--------------------|----------------------------------| | 插入空间未增加 | 数据填充碎片空间或未触发表空间扩展 | | 文件逻辑大但物理小 | 稀疏文件特性 | | 写入后`df`无变化 | 数据缓存在Page Cache未刷盘 | > **关键提示**:若需立即释放碎片空间,可执行`OPTIMIZE TABLE 表名;`(锁表且耗时),或使用`ALTER TABLE 表名 ENGINE=InnoDB;`重建表。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值