大家好,今天要和大家聊聊MySQL中最容易被误用的两种字符串类型——CHAR和VARCHAR。很多开发者在建表时随手就写,却不知这小小选择可能让你的数据库性能天差地别!
🔥 惊!一个字段类型让查询速度差10倍?
上周排查一个慢查询问题时,发现某核心表的status字段被定义成了VARCHAR(10),而实际上这个字段永远只存储1-2个字符的固定状态码。改成CHAR(2)后,查询速度直接提升了8倍!
为什么? 因为CHAR是固定长度,MySQL可以像数组一样直接定位数据;而VARCHAR需要先读取长度前缀才能找到数据位置。
📊 CHAR vs VARCHAR 终极对决
🅰️ CHAR:速度王者
固定长度:声明CHAR(10)就永远占10个字符空间
闪电读取:像数组一样直接定位,无需计算
适用场景:
性别(M/F)
国家代码(CN/US)
固定长度的编码
🅱️ VARCHAR:空间大师
动态长度:只用存储实际内容+1-2字节长度标记
空间高效:不会浪费一个字节
适用场景:
用户名
用户地址
产品描述
💣 三大致命误区
"VARCHAR更省空间所以无脑用"错!对于CHAR(2)就能搞定的状态码,用VARCHAR反而多出1字节长度标记
"CHAR会自动补空格很危险"其实检索时MySQL会自动去除CHAR的尾部空格,完全不影响WHERE条件判断
"UTF-8下CHAR和VARCHAR没区别"大错!CHAR(10)在UTF8下预留30字节空间,而VARCHAR只存实际字符
🛠️ 实战优化案例
优化前(错误示范):
CREATE TABLE user (id INT PRIMARY KEY,gender VARCHAR(10), -- 实际上只存'M'/'F'phone VARCHAR(100), -- 但业务限制最长20位status VARCHAR(20) -- 实际只有'active','disabled'两种);
优化后(性能起飞):
CREATE TABLE user (id INT PRIMARY KEY,gender CHAR(1), -- 固定1字符phone VARCHAR(20), -- 按业务最大长度设置status CHAR(8) -- 按最长值设置);
📈 性能实测数据
我们对10万条数据进行了测试:
测试机配置:
CPU: 13th Gen Intel® Core™ i5-13500H
内存: 16GB DDR4
存储: SSD
MySQL版本: 5.7.26 (InnoDB引擎)
字符集: utf8
建表
-- 测试用CHAR表CREATE TABLE char_test (id INT AUTO_INCREMENT PRIMARY KEY,fixed_code CHAR(6), -- 模拟固定长度编码short_desc CHAR(20), -- 模拟短描述dynamic_data CHAR(100) -- 故意用CHAR存变长数据(反面案例));-- 测试用VARCHAR表CREATE TABLE varchar_test (id INT AUTO_INCREMENT PRIMARY KEY,fixed_code VARCHAR(6), -- 模拟固定长度编码short_desc VARCHAR(20), -- 模拟短描述dynamic_data VARCHAR(100) -- 正确用VARCHAR存变长数据);
编写存储过程
CREATE DEFINER=`root`@`localhost` PROCEDURE `insert_test_data`()BEGINDECLARE i INT DEFAULT 0;DECLARE str_len INT;DECLARE rand_str TEXT;WHILE i < 100000 DO-- 生成6字符固定编码SET @fixed_code = CONCAT(CHAR(65+FLOOR(RAND()*26)),CHAR(65+FLOOR(RAND()*26)),CHAR(65+FLOOR(RAND()*26)),CHAR(65+FLOOR(RAND()*26)),CHAR(65+FLOOR(RAND()*26)),CHAR(65+FLOOR(RAND()*26)));-- 生成10-20字符短描述SET str_len = 10 + FLOOR(RAND()*11);SET @short_desc = '';WHILE str_len > 0 DOSET @short_desc = CONCAT(@short_desc, CHAR(65+FLOOR(RAND()*26)));SET str_len = str_len - 1;END WHILE;-- 生成20-100字符动态数据SET str_len = 20 + FLOOR(RAND()*81);SET @dynamic_data = '';WHILE str_len > 0 DOSET @dynamic_data = CONCAT(@dynamic_data, CHAR(65+FLOOR(RAND()*26)));SET str_len = str_len - 1;END WHILE;-- 插入数据INSERT INTO char_test VALUES (NULL, @fixed_code, @short_desc, @dynamic_data);INSERT INTO varchar_test VALUES (NULL, @fixed_code, @short_desc, @dynamic_data);SET i = i + 1;IF i % 1000 = 0 THENCOMMIT;END IF;END WHILE;END
测试
-- 开始计时SET @start_insert = NOW();-- 调用存储过程插入数据CALL insert_test_data();-- 结束计时SET @end_insert = NOW();-- 计算总耗时(秒)SET @total_seconds = TIMESTAMPDIFF(SECOND, @start_insert, @end_insert);SELECT @total_seconds AS total_insert_seconds;-- 计算平均每千条耗时(毫秒)SELECT (@total_seconds * 1000) / (COUNT(*)/1000) AS avg_ms_per_thousandFROM char_test;-- 查看表数据文件大小SELECTtable_name AS '表名',round(data_length/1024/1024, 2) AS '数据大小(MB)',round(index_length/1024/1024, 2) AS '索引大小(MB)'FROM information_schema.TABLESWHERE table_schema = 'new_mall'AND table_name = 'char_test';
测试结论:短固定长度字段用CHAR,长变长字段用VARCHAR!
💡 极光总结的黄金法则
≤4字符的字段:无脑用CHAR
5-20字符的固定长度:优先CHAR
>20字符或长度变化大:用VARCHAR
UTF-8多字节字符:VARCHAR通常更合适
记住:数据库设计是门艺术,小小类型选择,大大性能影响!你们数据库里有该优化的情况吗?欢迎在评论区分享你的发现~





