MySQL批量取消透明页压缩,解决磁盘碎片了高的问题

在这里插入图片描述

说明

上文介绍了《MySQL启用透明页压缩,导致宕机!》

https://www.modb.pro/db/1987415834479370240

本文介绍如何批量取消MySQL透明页压缩,测定解决此问题。

申请磁盘扩容,并取消MySQL表压缩

1.数据库全备

nohup sh /mysqldata/dbscripts/dump.sh 2>>/mysqldata/back/mysql_dump_err_20251111.log &

2.预估扩容磁盘空间

SELECT SUM(FILE_SIZE-ALLOCATED_SIZE)/1024/1024/1024 "GB" FROM INFORMATION_SCHEMA.INNODB_TABLESPACES WHERE NAME in 
(SELECT concat(TABLE_SCHEMA,"/",TABLE_NAME) FROM INFORMATION_SCHEMA.TABLES WHERE CREATE_OPTIONS LIKE '%COMPRESSION=%');

3.磁盘扩容(200GB)

pvcreate /dev/sdd
vgextend vg_data /dev/sdd
lvextend -l +100%FREE /dev/mapper/vg_data-lv_mysqldata
blkid /dev/mapper/vg_data-lv_mysqldata
xfs_growfs /dev/mapper/vg_data-lv_mysqldata

4.生成存在表压缩属性的表名

SELECT concat(TABLE_SCHEMA,'.',TABLE_NAME) FROM INFORMATION_SCHEMA.TABLES WHERE CREATE_OPTIONS LIKE '%COMPRESSION=%' order by DATA_LENGTH desc;
vi /mysqldata/dbtmpfile/tmp/table_name.sql
cjc.t1                     
cjc.t2                   
cjc.t3
...313张表...

5.去掉取消表压缩的脚本:

vi /mysqldata/dbtmpfile/tmp/alert_nocompression.sh
#!/bin/bash
# MySQL连接信息
MYSQL_USER="root"
MYSQL_PASSWORD="******"
MYSQL_DATABASE="cjc"

# 表名文件
TABLE_FILE="table_name.sql"

# 日志文件
LOG_FILE="/mysqldata/dbtmpfile/tmp/alert_nocompression.log"

# 记录日志函数
log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> $LOG_FILE
}

# 检查表名文件是否存在
if [ ! -f "$TABLE_FILE" ]; then
    echo "错误: 表名文件 $TABLE_FILE 不存在"
    exit 1
fi

# 记录开始时间
start_time=$(date '+%Y-%m-%d %H:%M:%S')
log "=== 开始执行表引擎转换 ==="
log "开始时间: $start_time"
log "表名文件: $TABLE_FILE"

# 逐行读取表名并执行ALTER TABLE
success_count=0
failed_count=0

while IFS= read -r table_name; do
    # 跳过空行
    if [[ -z "$table_name" ]]; then
        continue
    fi
    
    # 去除前后空白字符
    table_name=$(echo "$table_name" | xargs)
    
    log "开始转换表: $table_name"
    start_table=$(date '+%Y-%m-%d %H:%M:%S')
    
    # 执行ALTER TABLE语句
    if mysql -u$MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE -e "ALTER TABLE $table_name COMPRESSION='NONE';" 2>> $LOG_FILE; then
        log "成功: 表 $table_name 转换完成"
        ((success_count++))
    else
        log "失败: 表 $table_name 转换失败"
        ((failed_count++))
    fi
    
    end_table=$(date '+%Y-%m-%d %H:%M:%S')
    log "表 $table_name 执行时间: $start_table - $end_table"
    echo "" >> $LOG_FILE
    
done < "$TABLE_FILE"

# 记录结束时间
end_time=$(date '+%Y-%m-%d %H:%M:%S')
log "结束时间: $end_time"
log "执行结果: 成功 $success_count 个表,失败 $failed_count 个表"
log "=== 执行完成 ==="

echo "执行完成,成功 $success_count 个表,失败 $failed_count 个表"
echo "详情请查看日志: $LOG_FILE"

后台运行:

nohup /mysqldata/dbtmpfile/tmp/alert_nocompression.sh 2>err_inno.log &

检查执行结果:

root@CJC-DB-01:/mysqldata/dbtmpfile/tmp#cat alert_nocompression.log |grep -v "password"
2025-11-11 18:28:23 === 开始执行表引擎转换 ===
2025-11-11 18:28:23 开始时间: 2025-11-11 18:28:23
2025-11-11 18:28:23 表名文件: table_name.sql
2025-11-11 18:28:23 开始转换表: cjc.t2
2025-11-11 18:28:23 成功: 表 cjc.t2 转换完成
2025-11-11 18:28:23 表 cjc.t2 执行时间: 2025-11-11 18:28:23 - 2025-11-11 18:28:23

2025-11-11 18:28:23 开始转换表: cjc.t3
2025-11-11 18:28:23 成功: 表 cjc.t3转换完成
2025-11-11 18:28:23 表 cjc.t3执行时间: 2025-11-11 18:28:23 - 2025-11-11 18:28:23
......
2025-11-11 18:28:33 结束时间: 2025-11-11 18:28:33
2025-11-11 18:28:33 执行结果: 成功 312 个表,失败 0 个表
2025-11-11 18:28:33 === 执行完成 ===

6.重组表

取消表压缩属性后,只对新数据生效,原数据压缩压缩状态,需要执行重组表
通过脚本进行重组。

vi /mysqldata/dbtmpfile/tmp/alert_innodb.sh
#!/bin/bash

# MySQL连接信息
MYSQL_USER="root"
MYSQL_PASSWORD="******"
MYSQL_DATABASE="cjc"

# 表名文件
TABLE_FILE="table_name.sql"

# 日志文件
LOG_FILE="/mysqldata/dbtmpfile/tmp/alert_innodb.log"

# 记录日志函数
log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> $LOG_FILE
}

# 检查表名文件是否存在
if [ ! -f "$TABLE_FILE" ]; then
    echo "错误: 表名文件 $TABLE_FILE 不存在"
    exit 1
fi

# 记录开始时间
start_time=$(date '+%Y-%m-%d %H:%M:%S')
log "=== 开始执行表引擎转换 ==="
log "开始时间: $start_time"
log "表名文件: $TABLE_FILE"

# 逐行读取表名并执行ALTER TABLE
success_count=0
failed_count=0

while IFS= read -r table_name; do
    # 跳过空行
    if [[ -z "$table_name" ]]; then
        continue
    fi
    
    # 去除前后空白字符
    table_name=$(echo "$table_name" | xargs)
    
    log "开始转换表: $table_name"
    start_table=$(date '+%Y-%m-%d %H:%M:%S')
    
    # 执行ALTER TABLE语句
    if mysql -u$MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE -e "ALTER TABLE $table_name ENGINE=InnoDB;" 2>> $LOG_FILE; then
        log "成功: 表 $table_name 转换完成"
        ((success_count++))
    else
        log "失败: 表 $table_name 转换失败"
        ((failed_count++))
    fi
    
    end_table=$(date '+%Y-%m-%d %H:%M:%S')
    log "表 $table_name 执行时间: $start_table - $end_table"
    echo "" >> $LOG_FILE
    
done < "$TABLE_FILE"

# 记录结束时间
end_time=$(date '+%Y-%m-%d %H:%M:%S')
log "结束时间: $end_time"
log "执行结果: 成功 $success_count 个表,失败 $failed_count 个表"
log "=== 执行完成 ==="

echo "执行完成,成功 $success_count 个表,失败 $failed_count 个表"
echo "详情请查看日志: $LOG_FILE"

执行表重组:

nohup /mysqldata/dbtmpfile/tmp/alert_innodb.sh 2>err_inno.log &

检查执行日志:

root@CJC-DB-01:/mysqldata/dbtmpfile/tmp#cat alert_innodb.log |grep -v "password"
2025-11-11 18:30:39 === 开始执行表引擎转换 ===
2025-11-11 18:30:39 开始时间: 2025-11-11 18:30:39
2025-11-11 18:30:39 表名文件: table_name.sql
2025-11-11 18:30:39 开始转换表: cjc.t2
2025-11-11 18:45:49 成功: 表 cjc.t2 转换完成
2025-11-11 18:45:49 表 cjc.t2 执行时间: 2025-11-11 18:30:39 - 2025-11-11 18:45:49
.....
2025-11-11 20:16:21 结束时间: 2025-11-11 20:16:21
2025-11-11 20:16:21 执行结果: 成功 312 个表,失败 0 个表
2025-11-11 20:16:21 === 执行完成 ===

变更效果:

变更前:

   from      to extents  blocks    pct
      1       1   78243   78243   0.42
      2       3 6133014 17900717  95.15
      4       7    1720    7753   0.04
      8      15     982   10669   0.06
     16      31     999   21393   0.11
     32      63     962   42595   0.23
     64     127     974   85896   0.46
    128     255     905  164594   0.87
    256     511     238   90835   0.48
    512    1023     264  188506   1.00
   1024    2047      74  119030   0.63
   2048    4095      47  103383   0.55

执行碎片整理后:

xfs_db -r -c freesp /dev/mapper/vg_data-lv_mysqldata
   from      to extents  blocks    pct
      1       1 1469638 1469638   7.85
      2       3  710299 1970369  10.52
      4       7   13175   65655   0.35
      8      15    4198   45183   0.24
     16      31    1753   39029   0.21
     32      63    1170   53812   0.29
     64     127     968   89391   0.48
    128     255    1292  254951   1.36
    256     511    1236  434760   2.32
    512    1023    1195  908070   4.85
   1024    2047    1341 1741724   9.30
   2048    4095     559 1523051   8.13
   4096    8191     333 1944766  10.39
   8192   16383     223 2440221  13.03
  16384   32767      69 1572975   8.40
  32768   65535      44 1880745  10.04
  65536  131071      11  984776   5.26
 131072  262143       5 1015372   5.42
 262144  524287       1  289538   1.55

扩容、取消表压缩后:

   from      to extents  blocks    pct
      1       1  309523  309523   0.57
      2       3    6578   17596   0.03
      4       7   11368   56522   0.10
      8      15    5130   55333   0.10
     16      31    2239   49754   0.09
     32      63    1579   72482   0.13
     64     127    1440  133795   0.24
    128     255    2586  521127   0.95
    256     511    2232  825685   1.51
    512    1023    2743 2110568   3.86
   1024    2047     548  736744   1.35
   2048    4095     230  622375   1.14
   4096    8191      84  521528   0.95
   8192   16383      36  414925   0.76
  16384   32767      21  509656   0.93
  32768   65535      15  654667   1.20
  65536  131071       6  585718   1.07
 131072  262143       1  155918   0.29
 262144  524287       1  358666   0.66
1048576 2097151       2 3260465   5.97
2097152 4194303       1 4122288   7.55
4194304 6422528       6 38535132  70.54

监控预防:

1.检查MySQL是否查看压缩表;

mysql> SELECT TABLE_NAME, TABLE_SCHEMA, CREATE_OPTIONS FROM INFORMATION_SCHEMA.TABLES WHERE CREATE_OPTIONS LIKE '%COMPRESSION=%';
+----------------------------------------+--------------+---------------------------------------------------+
| TABLE_NAME                             | TABLE_SCHEMA | CREATE_OPTIONS                                    |
+----------------------------------------+--------------+---------------------------------------------------+
|cjc_t1                             | cjc        | row_format=DYNAMIC COMPRESSION="zlib"             |
|cjc_t2                             | cjc        | row_format=DYNAMIC COMPRESSION="zlib"             |
......

2.检查磁盘空闲分布情况:

XFS文件系统:

[root@cjc-db-01 ~]# xfs_db -r -c freesp /dev/mapper/vg_data01-lv_mysql01
   from      to extents  blocks    pct
      1       1      64      64   0.01
      2       3  172909  374832  42.86
    512    1023       2    1474   0.17
  16384   32767       1   29531   3.38
  65536  131071       1  130771  14.95
 262144  524287       1  337943  38.64

EXT4文件系统:

[root@cjc-db-01 mysql04]# e2freefrag /dev/mapper/vg_data04-lv_mysql04
Device: /dev/mapper/vg_data04-lv_mysql04
Blocksize: 4096 bytes
Total blocks: 2620416
Free blocks: 2537279 (96.8%)

Min. free extent: 4 KB 
Max. free extent: 258036 KB
Avg. free extent: 20 KB
Num. free extent: 153238

HISTOGRAM OF FREE EXTENT SIZES:
Extent Size Range :  Free extents   Free Blocks  Percent
    4K...    8K-  :           169           169    0.01%
    8K...   16K-  :        153016        332007   13.09%
   16K...   32K-  :             2             8    0.00%
   32K...   64K-  :             1             8    0.00%
   64K...  128K-  :             2            59    0.00%
  128K...  256K-  :             1            32    0.00%
  512K... 1024K-  :             5          1168    0.05%
    1M...    2M-  :             5          1767    0.07%
    2M...    4M-  :            15         14063    0.55%
    4M...    8M-  :             2          2784    0.11%
    8M...   16M-  :             3          9218    0.36%
   32M...   64M-  :             5         65284    2.57%
   64M...  128M-  :             9        218777    8.62%
  128M...  256M-  :             3        156413    6.16%

定期检查或监控:

1.XFS 文件系统:[2,3]区间空闲百分比;
2.EXT4文件系统:8k-16k区间空闲百分比;

(1)添加MySQL监控:对表压缩的监控
存在启用透明压缩属性的表时告警。

(2)添加对文件系统的监控:检查是否存在[2,3]区间的碎片

root@CJC-DB-01:/mysqldata/dbtmpfile/tmp#xfs_db -r -c freesp /dev/mapper/vg_data-lv_mysqldata|grep "2       3"
      2       3    6580   17597   0.03

(3)如果存在,继续检查[2,3]区间碎片的占比,超过70%,告3级警

root@CJC-DB-01:/mysqldata/dbtmpfile/tmp#xfs_db -r -c freesp /dev/mapper/vg_data-lv_mysqldata|grep "2       3"|awk -F " " '{print $5}'
0.03

欢迎关注我的公众号《IT小Chen

<think>根据用户需求,用户希望了解通过表名批量清理数据碎片的方法。结合之前的引用内容,特别是引用[3]中提到使用optimize命令和存储过程进行批量清理,我们可以设计一个基于MySQL的存储过程来实现这个功能。同时,引用[1]中提到了避免碎片产生的方法,引用[2]介绍了直接路径加载方法,这些可以作为补充建议。 ### 解决方案设计: 1. **核心命令**:使用`OPTIMIZE TABLE`命令释放表空间(适用于InnoDB和MyISAM引擎)[^3]。 2. **批量处理**:通过存储过程循环处理指定表名列表。 3. **安全机制**:避免系统表被意外优化,增加表名过滤条件。 ### MySQL存储过程示例代码: ```sql DELIMITER $$ CREATE PROCEDURE batch_optimize_tables(IN table_names TEXT) BEGIN DECLARE v_table_name VARCHAR(255); DECLARE v_done INT DEFAULT 0; DECLARE v_cursor CURSOR FOR SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() AND FIND_IN_SET(table_name, table_names); DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = 1; OPEN v_cursor; optimize_loop: LOOP FETCH v_cursor INTO v_table_name; IF v_done THEN LEAVE optimize_loop; END IF; -- 执行OPTIMIZE TABLE命令 SET @sql = CONCAT('OPTIMIZE TABLE `', v_table_name, '`'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END LOOP; CLOSE v_cursor; END$$ DELIMITER ; ``` ### 使用方法: 1. **调用存储过程**:传入逗号分隔的表名字符串 ```sql CALL batch_optimize_tables('table1,table2,orders,customers'); ``` ### 注意事项: 1. **锁表影响**:`OPTIMIZE TABLE`会锁表,建议在业务低峰期执行[^3] 2. **InnoDB引擎**:实际上会重建表并释放未使用空间(等价于`ALTER TABLE ... FORCE`) 3. **MyISAM引擎**:会整理数据文件碎片并重建索引 4. **过滤系统表**:通过`information_schema.tables`自动过滤当前数据库 ### 替代方案(直接路径加载)[^2]: 对于数据导入场景,采用直接路径加载(如MySQL的`LOAD DATA INFILE`使用`DIRECT`选项)可避免碎片产生: ```sql LOAD DATA INFILE '/path/to/data.csv' INTO TABLE orders FIELDS TERMINATED BY ',' DIRECT; -- 跳过缓冲区直接写入 ``` ### 碎片预防建议[^1]: 1. **设计阶段**: - 使用自增主键保持物理存储顺序 - 大字段分离存储(避免频繁更新导致的行迁移) - 合理设置`innodb_page_size`(默认16KB) 2. **操作规范**: ```sql -- 用TRUNCATE替代DELETE清空表 TRUNCATE TABLE access_log; -- 批量删除后立即优化 DELETE FROM orders WHERE order_date < '2020-01-01'; OPTIMIZE TABLE orders; ``` ### 相关问题 1. 如何监控MySQL表的碎片化程度?[^3] 2. 除了`OPTIMIZE TABLE`,还有哪些在线碎片整理方法? 3. 分区表碎片整理有什么特殊注意事项?[^1] </think>以下是针对**批量清理数据库数据碎片**的完整解决方案,结合表名批量操作与碎片清理技术,包含具体方法和优化建议: --- ### 一、核心方法:通过表名批量清理碎片(MySQL示例) #### 1. 使用存储过程批量执行 `OPTIMIZE TABLE` ```sql DELIMITER $$ CREATE PROCEDURE batch_optimize_tables(IN schema_name VARCHAR(64)) BEGIN DECLARE done INT DEFAULT FALSE; DECLARE table_name VARCHAR(64); -- 获取指定模式下所有表名 DECLARE cur CURSOR FOR SELECT table_name FROM information_schema.tables WHERE table_schema = schema_name AND table_type = 'BASE TABLE'; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; OPEN cur; optimize_loop: LOOP FETCH cur INTO table_name; IF done THEN LEAVE optimize_loop; END IF; -- 动态生成OPTIMIZE命令 SET @sql = CONCAT('OPTIMIZE TABLE `', schema_name, '`.`', table_name, '`'); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END LOOP; CLOSE cur; END$$ DELIMITER ; -- 调用示例:清理"public"模式下的所有表 CALL batch_optimize_tables('public'); ``` #### 2. Shell脚本批量操作(跨平台) ```bash #!/bin/bash DB_NAME="your_database" SCHEMA="public" # 获取所有表名并循环优化 mysql -u root -p$DB_PASSWORD -Nse "SHOW TABLES IN $DB_NAME" | while read table; do echo "优化表: $table" mysql -u root -p$DB_PASSWORD -e "OPTIMIZE TABLE $SCHEMA.$table" $DB_NAME done ``` --- ### 二、关键原理与技术说明 1. **`OPTIMIZE TABLE` 的作用**: - 重建表数据文件,消除 `DELETE` 操作产生的空洞[^3] - 重置自增计数器,压缩索引空间 - 等效命令:`ALTER TABLE tablename FORCE` (InnoDB引擎) 2. **碎片产生根源**: $$ \text{碎片率} = \frac{\text{实际数据大小}}{\text{磁盘占用大小}} \times 100\% $$ 当该值低于 $70\\%$ 时需清理[^1] 3. **直接路径写入优化**: - 使用 `LOAD DATA INFILE` 替代 `INSERT` 避免中间碎片[^2] - 批量操作减少事务日志开销 --- ### 三、级优化建议 #### 1. 设计阶段预防碎片[^1] | 优化方向 | 具体措施 | |------------------|--------------------------------------------------------------------------| | **表结构设计** | 使用自增主键、拆分大字段字段、合理设置 `innodb_page_size` | | **写入模式** | 用 `TRUNCATE` 替代 `DELETE` 清空表,批量删除后立即优化 | | **分区管理** | 按时间分区(例:`PARTITION BY RANGE (YEAR(created_at))`),局部优化分区 | #### 2. 自动化运维方案 ```mermaid graph TD A[每日监控] -->|碎片率>30%| B(加入清理队列) B --> C{是否为业务峰期?} C -->|否| D[执行OPTIMIZE TABLE] C -->|是| E[延迟到低峰期] D --> F[记录优化日志] ``` --- ### 四、注意事项 1. **锁表风险**: - InnoDB表优化时产生 `MDL` 锁,建议在 $23\text{:}00 - 05\text{:}00$ 执行 - 使用 `pt-online-schema-change` 工具实现在线优化 2. **空间要求**: - 需预留 $1.5 \times \text{表大小}$ 的磁盘空间 3. **云数据库限制**: - AWS RDS 需通过 `rdsadmin.rdsadmin_optimize_table` 调用 - 阿里云建议使用 `ALTER TABLE ENGINE=InnoDB` --- ### 相关问题 1. 如何在不锁表的情况下在线优化大表? 2. 分区表碎片整理与普通表有何差异?[^1] 3. 如何监控数据库的实时碎片率?[^3] 4. MyISAM 和 InnoDB 的碎片清理机制有什么区别? 5. 数据归档如何影响碎片生成速度?[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值