行格式概述

行格式(Row Format) 定义了一行数据在磁盘上的存储方式。

-- 查看表的行格式
SHOW TABLE STATUS LIKE 'users'\G
-- Row_format: Dynamic

-- 创建表时指定行格式
CREATE TABLE test (
    id INT PRIMARY KEY,
    name VARCHAR(100)
) ROW_FORMAT=COMPACT;

-- 修改行格式
ALTER TABLE test ROW_FORMAT=DYNAMIC;

四种行格式

行格式MySQL版本特点适用场景
Redundant5.0前默认旧格式,占用空间大兼容性
Compact5.0-5.6默认紧凑格式,节省20%空间通用(已过时)
Dynamic5.7+默认动态格式,处理行溢出推荐(默认)
Compressed5.5+压缩格式,节省50%+空间只读表、归档数据

1. Compact行格式(紧凑格式)

结构

┌──────────────────────────────────────────────────┐
│ 变长字段长度列表(逆序)                              │  VARCHAR、TEXT字段的长度
├──────────────────────────────────────────────────┤
│ NULL值列表(位图,逆序)                             │  标记哪些字段为NULL
├──────────────────────────────────────────────────┤
│ 记录头信息(5字节)                                  │  deleted_flag、min_rec_flag等
├──────────────────────────────────────────────────┤
│ 隐藏列                                           │  DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID
├──────────────────────────────────────────────────┤
│ 列1数据                                          │  实际数据
├──────────────────────────────────────────────────┤
│ 列2数据                                          │
├──────────────────────────────────────────────────┤
│ ...                                              │
└──────────────────────────────────────────────────┘

示例

CREATE TABLE user_compact (
    id INT PRIMARY KEY,
    name VARCHAR(20),
    age INT,
    email VARCHAR(50)
) ROW_FORMAT=COMPACT;

INSERT INTO user_compact VALUES (1, 'Alice', 25, 'alice@example.com');

存储布局

变长字段长度列表:[17, 5](逆序,email长度17,name长度5)
NULL值列表:0x00(无NULL值)
记录头信息:5字节
隐藏列:
  - DB_TRX_ID:6字节(事务ID)
  - DB_ROLL_PTR:7字节(回滚指针)
列数据:
  - id:4字节(1)
  - name:5字节(Alice)
  - age:4字节(25)
  - email:17字节(alice@example.com)

特点

  • 节省空间:比Redundant节省约20%
  • 变长字段优化:VARCHAR实际长度存储
  • NULL值优化:用位图标记,不占用实际空间

2. Dynamic行格式(动态格式)

结构

与Compact几乎相同,主要区别在行溢出处理

行溢出(Row Overflow)

问题:当一行数据超过页大小(16KB)怎么办?

Compact处理

  • 前768字节存储在数据页(行内)
  • 剩余部分存储在溢出页(Overflow Page)
  • 数据页保留768字节 + 20字节指针

Dynamic处理

  • 完全溢出:整个字段存储在溢出页
  • 数据页只保留20字节指针
  • 节省数据页空间,提高缓存命中率

示例

CREATE TABLE test_overflow (
    id INT PRIMARY KEY,
    content TEXT  -- 假设存储100KB数据
) ROW_FORMAT=DYNAMIC;

INSERT INTO test_overflow VALUES (1, REPEAT('A', 100000));

Compact存储

数据页:
  - id:4字节
  - content前768字节
  - 指针(20字节)→ 溢出页1

溢出页1:
  - 剩余99232字节(100000 - 768)

Dynamic存储

数据页:
  - id:4字节
  - 指针(20字节)→ 溢出页1

溢出页1:
  - 完整的100000字节

优点

  • 数据页更紧凑,缓存命中率高
  • 减少回表次数

3. Compressed行格式(压缩格式)

特点

在Dynamic基础上增加压缩,使用zlib算法压缩数据页。

配置

-- 创建压缩表
CREATE TABLE test_compressed (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    content TEXT
) ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;  -- 压缩页大小8KB

-- KEY_BLOCK_SIZE可选值:1、2、4、8、16(默认16)

压缩流程

写入流程:
1. 数据写入Buffer Pool(16KB页)
2. 压缩为8KB页
3. 写入磁盘(8KB)

读取流程:
1. 从磁盘读取8KB压缩页
2. 解压缩为16KB页
3. 缓存到Buffer Pool

性能影响

操作压缩格式普通格式
存储空间节省50-70%100%
写入性能慢30-50%(压缩开销)100%
读取性能慢10-20%(解压开销)100%
CPU占用高(压缩/解压)

适用场景

-- ✅ 适合:只读表、归档数据
CREATE TABLE log_archive (
    id BIGINT PRIMARY KEY,
    log_time TIMESTAMP,
    log_content TEXT
) ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;

-- ❌ 不适合:频繁更新的表
-- 压缩/解压开销大,性能下降明显

4. Redundant行格式(冗余格式)

特点

MySQL 5.0前的默认格式,已过时,占用空间大。

与Compact的区别

特性RedundantCompact
空间占用大(多占用20%)
变长字段存储固定长度(浪费空间)存储实际长度
NULL值占用实际空间(如INT 4字节)位图标记(不占空间)

不推荐使用,仅用于兼容旧版本。


行格式选择

1. 默认使用Dynamic(推荐)

-- MySQL 5.7+默认
-- 大字段处理好,性能好

-- 查看默认行格式
SHOW VARIABLES LIKE 'innodb_default_row_format';
-- dynamic

2. 归档表使用Compressed

-- 归档日志表
CREATE TABLE log_2024 (
    id BIGINT PRIMARY KEY,
    log_time TIMESTAMP,
    log_content TEXT
) ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;

-- 优势:
- 节省50%+存储空间
- 减少备份时间和成本

3. 避免使用Redundant

-- ❌ 不推荐
ROW_FORMAT=REDUNDANT
-- 浪费空间,性能差

行溢出阈值

何时触发行溢出?

-- 阈值计算
一行数据大小 > (页大小 - 页头 - 页尾) / 2
即:行大小 > (16384 - 200) / 2  8KB

-- 触发行溢出的场景
1. TEXTBLOB大字段
2. VARCHAR(10000)超长字段
3. 多个VARCHAR累计超8KB

查看行溢出

-- 查看表的数据和溢出页大小
SELECT
    table_name,
    data_length/1024/1024 AS data_mb,
    max_data_length/1024/1024 AS overflow_mb
FROM information_schema.TABLES
WHERE table_schema = 'test';

实战建议

1. 默认使用Dynamic

# my.cnf
[mysqld]
innodb_default_row_format = DYNAMIC  # 默认值

2. 避免过长字段

-- ❌ 不好:VARCHAR过长
name VARCHAR(10000)  -- 可能触发行溢出

-- ✅ 好:合理长度
name VARCHAR(200)    -- 根据业务需求

3. 大字段拆分

-- ❌ 不好:大字段和小字段混合
CREATE TABLE article (
    id INT PRIMARY KEY,
    title VARCHAR(200),
    content TEXT  -- 可能很大,触发行溢出
);

-- ✅ 好:大字段单独存储
CREATE TABLE article (
    id INT PRIMARY KEY,
    title VARCHAR(200)
);

CREATE TABLE article_content (
    article_id INT PRIMARY KEY,
    content TEXT,
    FOREIGN KEY (article_id) REFERENCES article(id)
);

4. 监控压缩效果

-- 查看压缩表的压缩率
SELECT
    table_name,
    data_length/1024/1024 AS data_mb,
    data_free/1024/1024 AS free_mb,
    ROUND(data_free/data_length * 100, 2) AS compress_ratio
FROM information_schema.TABLES
WHERE table_schema = 'test' AND row_format = 'COMPRESSED';

常见面试题

Q1: Dynamic和Compact的区别?

  • 主要区别在行溢出处理:Dynamic完全溢出,Compact部分溢出

Q2: 什么时候使用Compressed?

  • 归档表、只读表,节省存储空间

Q3: 行溢出的阈值是多少?

  • 约8KB(页大小16KB的一半)

Q4: 如何避免行溢出?

  • 合理设计字段长度、大字段拆分、使用Dynamic格式

小结

Compact:紧凑格式,节省空间(已过时) ✅ Dynamic:动态格式,完全行溢出(推荐,默认) ✅ Compressed:压缩格式,节省空间,性能差(归档表使用) ✅ Redundant:冗余格式,浪费空间(不推荐)

建议:默认Dynamic,归档表使用Compressed。


📚 相关阅读

  • 下一篇:《Buffer Pool:MySQL的内存管理》
  • 推荐:《InnoDB存储结构:表空间、段、区、页》