LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

MySql 的 VARCHAR 和 TEXT 怎么选?大厂都在用的文本存储方案

zhenglin
2025年12月4日 10:9 本文热度 108


在数据库设计当中,有一个很常见但又容易被忽视的问题,那就是TEXT类型的使用。 很多开发者在设计数据库时,会很随意的使用TEXT类型来存储文本,但这其实隐藏着很多隐患。


什么是TEXT类型?

在MySQL中,TEXT类型是用来存储大文本数据的数据类型。它主要有以下几种:

  • TINYTEXT:最大长度 255 字符


  • TEXT:最大长度 65,535 字符


  • MEDIUMTEXT:最大长度 16,777,215 字符


  • LONGTEXT:最大长度 4,294,967,295 字符



使用TEXT类型的问题?

1. 性能问题:溢出页存储机制

核心问题在于TEXT类型的存储方式可能触发"溢出页"机制。

首先了解数据库的页大小和行格式设置:

SHOW VARIABLES LIKE 'innodb_page_size';      -- 通常是16384字节(16KB)

SHOW VARIABLES LIKE 'innodb_default_row_format'; -- MySQL 5.7+默认是DYNAMIC


溢出页的精确触发条件:

行格式为DYNAMIC/COMPRESSED时(MySQL 5.7+默认):

  • 当单个TEXT字段长度超过约8000字节

  • 或者整行数据大小超过页大小(16KB)时

  • 字段会被存储在溢出页中


行格式为REDUNDANT/COMPACT时:TEXT/BLOB字段总是存储在溢出页

溢出页的影响:

  • 额外的磁盘I/O:读取一条记录可能需要访问多个数据页

  • 内存效率低:缓存效率下降,因为数据分散在多个页中

  • 查询性能下降:特别是涉及全表扫描或大量数据读取时


让我们通过实际代码来理解这个问题:

-- 创建测试表

CREATE TABLE article (

    id INT PRIMARY KEY AUTO_INCREMENT,

    title VARCHAR(255) NOT NULL,

    content TEXT,           -- 当内容较大时可能使用溢出页

    summary VARCHAR(500),   -- 对于短内容使用VARCHAR

    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

) ENGINE=InnoDB;


-- 插入不同长度的测试数据

INSERT INTO article (title, content, summary) VALUES 

-- 短内容(<1000字节),通常不会使用溢出页

('短文章', '这个内容很短。', '短文章摘要'),


-- 中等内容(~4000字节),可能使用溢出页

('中等文章', REPEAT('这是一个中等长度的内容。', 100), '中等文章摘要'),


-- 长内容(>8000字节),几乎肯定使用溢出页

('长文章', REPEAT('这是一个非常长的文章内容。', 500), '长文章摘要');

查询性能对比:


-- 查询所有数据(对于溢出页记录需要额外I/O)

SELECT * FROM article;


-- 不包含TEXT字段的查询要快得多

SELECT id, title, summary, created_at FROM article;


对于使用了溢出页的记录,查询时需要:

  1. 读取主记录页(包含TEXT字段的指针)
  2. 根据指针读取一个或多个溢出页来获取完整的TEXT内容

这比单页存储需要更多的磁盘I/O操作!


2. 索引限制

TEXT字段的索引使用有很多限制,这会影响查询优化:

代码高亮:

-- 尝试在TEXT字段上创建普通索引(会失败)

-- ERROR 1170 (42000): BLOB/TEXT column 'content' used in key specification without a key length

CREATE INDEX idx_content ON article(content);

TEXT字段索引的限制:

  • 不能创建普通索引,只能创建前缀索引

  • 前缀索引只对字段的前N个字符有效

  • 很多查询无法利用前缀索引

-- 只能在TEXT字段上创建前缀索引

CREATE INDEX idx_content_prefix ON article(content(100));


-- 这个查询可能使用前缀索引(匹配前100个字符)

SELECT id, title FROM article WHERE content LIKE 'MySQL%';


-- 但这个查询无法使用前缀索引进行完整排序

SELECT id, title FROM article ORDER BY content;


对比VARCHAR字段的完整索引能力:

CREATE TABLE optimized_article (

    id INT PRIMARY KEY AUTO_INCREMENT,

    title VARCHAR(255) NOT NULL,

    summary VARCHAR(1000) NOT NULL,  -- 使用VARCHAR可以创建完整索引

    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    INDEX idx_summary (summary)       -- 完整的索引,不是前缀索引

);


-- 在VARCHAR字段上可以高效排序和搜索

SELECT id, title FROM optimized_article ORDER BY summary;

SELECT id, title FROM optimized_article WHERE summary LIKE '%数据库%';


3. 内存与临时表问题

TEXT字段在处理排序、分组等操作时会产生额外的性能开销:

CREATE TABLE user_comments (

    id INT PRIMARY KEY AUTO_INCREMENT,

    user_id INT NOT NULL,

    comment_text TEXT,              -- 不推荐:使用TEXT存储评论

    comment_content VARCHAR(1000),  -- 推荐:使用VARCHAR

    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    INDEX idx_user_id (user_id)

);


内存临时表的限制:

代码高亮:

SHOW VARIABLES LIKE 'tmp_table_size';     -- 内存临时表最大大小

SHOW VARIABLES LIKE 'max_heap_table_size'; -- 内存表最大大小

性能对比测试:


-- 插入测试数据

INSERT INTO user_comments (user_id, comment_text, comment_content) VALUES 

(1, REPEAT('这是一个很长的评论...', 50), '这是一个普通长度的评论');


-- 按照用户ID排序(性能良好,使用内存临时表)

SELECT id, user_id FROM user_comments ORDER BY user_id;


-- 按照TEXT字段排序(性能差!可能使用磁盘临时表)

SELECT id, comment_text FROM user_comments ORDER BY comment_text;


-- 分组操作同样受影响

SELECT comment_text, COUNT(*) FROM user_comments GROUP BY comment_text;

原因分析: MySQL在处理TEXT/BLOB类型排序时,可能无法在内存中完成,必须使用速度更慢的磁盘临时表。



更优的解决方案

方案1:合理使用VARCHAR并分析数据长度

关键步骤:先分析现有数据的长度分布

-- 分析现有数据的长度特征

SELECT 

    MAX(CHAR_LENGTH(comment_text)) as max_length,

    AVG(CHAR_LENGTH(comment_text)) as avg_length,

    COUNT(*) as total_count

FROM user_comments;


基于分析结果优化表结构:

CREATE TABLE optimized_comments (

    id INT PRIMARY KEY AUTO_INCREMENT,

    user_id INT NOT NULL,

    content VARCHAR(1000) NOT NULL,  -- 根据分析设置合理长度

    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    INDEX idx_user_created (user_id, created_at),

    INDEX idx_content_prefix (content(100))

) ENGINE=InnoDB;


-- 插入测试数据

INSERT INTO optimized_comments (user_id, content) VALUES 

(1, '这个产品很好用,推荐大家购买!'),

(2, '产品质量很好,物流速度也很快,非常满意的一次购物体验。');


-- 查询性能大幅提升

SELECT id, content, created_at 

FROM optimized_comments 

WHERE user_id = 1 

ORDER BY created_at DESC 

LIMIT 10;


方案2:内容分表存储(推荐用于真正的大文本)

对于真正需要存储大文本的场景,建议使用分表策略:

主表:存储频繁查询的基本信息

代码高亮:

CREATE TABLE articles_main (

    id INT PRIMARY KEY AUTO_INCREMENT,

    title VARCHAR(255) NOT NULL,

    summary VARCHAR(500),           -- 摘要,用于列表展示

    author_id INT NOT NULL,

    status TINYINT DEFAULT 1,

    view_count INT DEFAULT 0,

    like_count INT DEFAULT 0,

    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

    INDEX idx_author_status (author_id, status),

    INDEX idx_created (created_at)

) ENGINE=InnoDB;

内容表:专门存储大文本内容

CREATE TABLE articles_content (

    id INT PRIMARY KEY AUTO_INCREMENT,

    article_id INT NOT NULL,

    content LONGTEXT NOT NULL,      -- 这里确实需要TEXT类型

    version INT DEFAULT 1,

    content_hash VARCHAR(64),       -- 内容哈希,用于去重

    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    UNIQUE KEY uk_article_version (article_id, version),

    FOREIGN KEY (article_id) REFERENCES articles_main(id) ON DELETE CASCADE

) ENGINE=InnoDB;


分表查询的优势:

-- 场景1:文章列表页(只需要基本信息,性能极佳)

SELECT id, title, summary, author_id, view_count, created_at 

FROM articles_main 

WHERE status = 2 

ORDER BY created_at DESC 

LIMIT 20;


-- 场景2:文章详情页(需要内容时才关联查询)

SELECT m.id, m.title, m.summary, c.content

FROM articles_main m

JOIN articles_content c ON m.id = c.article_id

WHERE m.id = 1;


方案3:文件系统存储 + 数据库元数据

对于超大型文本内容,可以考虑存储在文件系统中:


CREATE TABLE documents (

    id INT PRIMARY KEY AUTO_INCREMENT,

    name VARCHAR(255) NOT NULL,

    file_path VARCHAR(500) NOT NULL,  -- 存储文件路径而不是内容

    file_size INT NOT NULL,

    mime_type VARCHAR(100),

    storage_type ENUM('local', 'oss', 'cos') DEFAULT 'local',

    md5_hash VARCHAR(32),             -- 文件哈希,用于去重

    download_count INT DEFAULT 0,

    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    INDEX idx_name (name),

    UNIQUE INDEX idx_md5_hash (md5_hash)

) ENGINE=InnoDB;


决策指南

基于长度的具体决策标准

肯定使用 VARCHAR 的场景(< 1000字符):

代码高亮:

CREATE TABLE varchar_scenarios (

    username VARCHAR(100),      -- 用户名

    product_name VARCHAR(255),  -- 商品名称

    address VARCHAR(500),       -- 地址信息

    description VARCHAR(1000),  -- 描述

    summary VARCHAR(500),       -- 摘要

    tags VARCHAR(200)          -- 标签

);


肯定使用 TEXT 的场景(> 4000字符):

CREATE TABLE text_scenarios (

    article_content TEXT,       -- 博客文章正文

    product_description TEXT,   -- 产品详细描述

    forum_post TEXT,           -- 论坛帖子

    log_details TEXT,          -- 系统日志详情

    email_template TEXT        -- 邮件模板

);


灰色区域(需要具体分析):

CREATE TABLE gray_area_scenarios (

    user_comment VARCHAR(2000),   -- 用户评论:大多数短,少数长

    product_specs VARCHAR(4000)   -- 产品规格:根据实际长度分析

);



适用场景总结

应该使用 TEXT 的场景

1. 内容长度通常超过4000字符

  • 博客文章、新闻正文

  • 产品详细描述(富文本格式)

  • 论坛长帖子、文档内容


2. 内容长度不确定,但可能很大

  • 用户生成的富文本内容

  • 系统日志的详细上下文

  • 邮件模板和通知内容


3. 内容不参与频繁查询和排序

  • 文章正文(列表页不显示)

  • 产品详细规格(搜索不依赖此字段)

应该使用 VARCHAR 的场景

1. 内容长度通常小于1000字符

  • 用户名、标题、名称

  • 地址、描述、摘要

  • 标签、分类信息


2. 内容需要频繁查询和排序

  • 商品名称(需要搜索和排序)

  • 用户评论(需要显示和分页)


需要具体分析的场景

内容长度在1000-4000字符之间

业务可能快速增长的字段

既有短内容又有长内容的字段



实际案例对比

不推荐的设计:滥用TEXT类型

CREATE TABLE products_bad_design (

    id INT PRIMARY KEY AUTO_INCREMENT,

    name VARCHAR(255),

    short_description TEXT,    -- 不合适:简短描述使用TEXT

    specifications TEXT,       -- 不合适:规格参数使用TEXT

    price DECIMAL(10,2),

    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

);


推荐的设计:合理选择数据类型

代码高亮:

CREATE TABLE products_good_design (

    id INT PRIMARY KEY AUTO_INCREMENT,

    name VARCHAR(255) NOT NULL,

    short_description VARCHAR(500),  -- 合适:简短描述使用VARCHAR

    price DECIMAL(10,2) NOT NULL,

    stock INT DEFAULT 0,

    category_id INT NOT NULL,

    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    INDEX idx_category (category_id)

) ENGINE=InnoDB;


-- 商品详情单独存储

CREATE TABLE product_details (

    id INT PRIMARY KEY AUTO_INCREMENT,

    product_id INT NOT NULL,

    full_description TEXT,           -- 合适:详细描述使用TEXT

    specifications_json JSON,        -- 合适:规格使用JSON格式

    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

    FOREIGN KEY (product_id) REFERENCES products_good_design(id)

);


总结

在设计MySQL数据库时,我们可以参考以下原则:

1. 精确分析数据需求:了解每个字段的实际长度分布 

2. 理解溢出页机制:知道TEXT类型在什么情况下会触发溢出页存储 

3. 优先使用VARCHAR:为字符串字段设置合理的长度限制 

4. 理智使用TEXT类型:只在真正需要存储大文本(>4000字符)时使用 

5. 考虑分表策略:对大数据字段使用分表存储,优化查询性能


数据库设计需要在存储效率、查询性能、开发复杂度之间找到平衡点。合理的数据类型选择会让你的应用运行得更快、更稳定!


参考文章:原文链接





该文章在 2025/12/4 10:09:05 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved