【MySQL基础】详解MySQL数据类型:底层原理、越界测试与最佳实践

🔥个人主页:Cx330🌸

❄️个人专栏:《C语言》《LeetCode刷题集》《数据结构-初阶》《C++知识分享》

《优选算法指南-必刷经典100题》《Linux操作系统》:从入门到入魔

《Git深度解析》:版本管理实战全解 《Qt 极境架构》MySQL 核心技术与实战

🌟心向往之行必能


🎥Cx330🌸的简介:


目录

前言:

[一. MySQL 数据类型分类](#一. MySQL 数据类型分类)

[二. 数值类型](#二. 数值类型)

[2.1 整数类型(BIT/TINYINT/INT/BIGINT)](#2.1 整数类型(BIT/TINYINT/INT/BIGINT))

[2.1.1 TINYINT 越界测试与 Unsigned 机制](#2.1.1 TINYINT 越界测试与 Unsigned 机制)

[2.2.2 BIT 类型](#2.2.2 BIT 类型)

[奇妙的 ASCII 显示现象](#奇妙的 ASCII 显示现象)

[2.1.3 INT/BIGINT 对比测试](#2.1.3 INT/BIGINT 对比测试)

[2.2 小数类型(FLOAT/DOUBLE/DECIMAL)](#2.2 小数类型(FLOAT/DOUBLE/DECIMAL))

[2.2.1 FLOAT 类型](#2.2.1 FLOAT 类型)

[2.2.2 DECIMAL 类型与精度大PK](#2.2.2 DECIMAL 类型与精度大PK)

[三. 字符串类型](#三. 字符串类型)

[3.3.1 CHAR(L)](#3.3.1 CHAR(L))

[3.3.2 VARCHAR(L)](#3.3.2 VARCHAR(L))

[3.3.3 CHAR 与 VARCHAR 空间效率大比拼](#3.3.3 CHAR 与 VARCHAR 空间效率大比拼)

[🛠 经典选型建议(如何抉择?)](#🛠 经典选型建议(如何抉择?))

[四. 日期时间类型](#四. 日期时间类型)

[4.1 TIMESTAMP 自动更新测试](#4.1 TIMESTAMP 自动更新测试)

[五. ENUM 与 SET 类型](#五. ENUM 与 SET 类型)

[5.1 特点](#5.1 特点)

[5.2 案例实战:投票调查表](#5.2 案例实战:投票调查表)

[5.3 数据插入与基础查询](#5.3 数据插入与基础查询)

[🚨 核心避坑:如何查询集合中包含某一项的人?](#🚨 核心避坑:如何查询集合中包含某一项的人?)

[正确姿势:使用 find_in_set 函数](#正确姿势:使用 find_in_set 函数)

结语


前言:

在C++开发中,我们对基本数据类型的边界(如 intfloatdouble的内存占用与精度)以及位运算(Bitwise operations)了如指掌。然而,当我们把视角转向数据库,尤其是 MySQL 时,数据类型的选用和底层表现同样至关重要。

如果选型不当,不仅会浪费珍贵的磁盘和内存空间,更可能导致高并发下的高精度计算失真(如金融场景) ,或者因超出范围而发生插入异常。今天,我将结合底层设计与越界测试,带大家彻底搞懂 MySQL 的数据类型系统。


一. MySQL 数据类型分类

MySQL 的数据类型丰富多样,大体上可以分为以下四大核心版块:

分类 核心类型 适用场景
数值类型 BIT、TINYINT、INT、BIGINT、FLOAT、DECIMAL 存储数字(年龄、金额、计数等)
字符串类型 CHAR、VARCHAR、TEXT、BLOB 存储文本(姓名、地址、大文本、二进制数据)
日期时间类型 DATE、DATETIME、TIMESTAMP 存储时间(生日、创建时间、时间戳)
特殊字符串 ENUM(枚举)、SET(集合) 固定选项(性别、爱好、状态等)
二进制类型 BLOB 存储图片、文件等二进制数据

二. 数值类型

数值类型是最常用的类型,核心关注范围和精度,避免数据溢出或精度丢失。

2.1 整数类型(BIT/TINYINT/INT/BIGINT)

整数类型按占用字节和范围分为 5 类,支持UNSIGNED(无符号)修饰(默认有符号):

类型 字节 最小值 (带符号 / 无符号) 最大值 (带符号 / 无符号)
TINYINT 1 -128 / 0 127 / 255
SMALLINT 2 -32768 / 0 32767 / 65535
MEDIUMINT 3 -8388608 / 0 8388607 / 16777215
INT 4 -2147483648 / 0 2147483647 / 4294967295
BIGINT 8 -9223372036854775808 / 0 9223372036854775807 / 18446744073709551615

关键实战要点

  • 避免无符号类型(UNSIGNED) :虽然无符号类型能扩大正数范围,但可能导致溢出时报错(如TINYINT UNSIGNED插入 - 1 直接报错),且与有符号类型计算时容易出现逻辑问题。建议直接用更大的整数类型(如用INT替代TINYINT UNSIGNED)。
2.1.1 TINYINT 越界测试与 Unsigned 机制

TINYINT 占用 1 字节,有符号范围 - 128~127,无符号范围 0~255:

💡 C++博主避坑建议: > 尽可能少用 UNSIGNED。对于一个容易溢出的 INTINT UNSIGNED 的物理极限虽然翻倍,但面对海量业务同样可能面临溢出。不如在设计之初,直接将字段类型升级为 BIGINT,避免后期在线改表的痛苦。

2.2.2 BIT 类型

语法: BIT[(M)],M 表示每个值的位数(Bit),范围 1-64。如果忽略 M,默认值为 1。

奇妙的 ASCII 显示现象

当我们在终端中查询 BIT类型时,可能会发现值"神秘消失"或显示怪异:

原因分析: BIT 字段在终端显示时,是按照其对应的 ASCII 码字符进行渲染的。查询时按 ASCII 码显示

复制代码
mysql> insert into tt4 values (65, 65); -- 65 对应的 ASCII 字符是 'A'
mysql> select * from tt4;
+------+------+
| id   | a    |
+------+------+
|   10 |      |
|   65 | A    | -- 此时显示出了字符 'A'
+------+------+
2.1.3 INT/BIGINT 对比测试
复制代码
-- 1. INT存储手机号(越界测试)
CREATE TABLE test_int(phone INT);
INSERT INTO test_int VALUES(13800138000); 
-- 报错:Out of range value for column 'phone' at row 1(INT最大值2147483647 < 13800138000)

-- 2. BIGINT存储手机号(成功)
CREATE TABLE test_bigint(phone BIGINT);
INSERT INTO test_bigint VALUES(13800138000); -- 成功
SELECT * FROM test_bigint;
+-------------+
| phone       |
+-------------+
| 13800138000 |
+-------------+

2.2 小数类型(FLOAT/DOUBLE/DECIMAL)

2.2.1 FLOAT 类型
  • 语法: FLOAT[(M, D)] [UNSIGNED],M 指定显示长度(含小数点和负号),D 指定小数位数。占用 4 字节。

  • 截断四舍五入测试:

    -- 定义 float(4,2),表示范围在 -99.99 到 99.99 之间
    mysql> create table tt6(id int, salary float(4,2));

    mysql> insert into tt6 values (100, -99.99); -- 边界插入
    mysql> insert into tt6 values (101, 99.991); -- 多出的一位小数四舍五入被拿掉

    mysql> select * from tt6;
    +------+--------+
    | id | salary |
    +------+--------+
    | 100 | -99.99 |
    | 101 | 99.99 |
    +------+--------+

*思考:如果定义为 **FLOAT(6,3)*有符号,它的数值范围是多少? 答案是:-999.999 - 999.999。如果是无符号 FLOAT(4,2) UNSIGNED,其范围则是 0 - 99.99(对负数报错拦截)。

2.2.2 DECIMAL 类型与精度大PK

对于高精度的商业计算,我们通常会选用 DECIMAL

  • 语法: DECIMAL(M, D) [UNSIGNED]

  • 限制: 整数最大位数 M 为 65;支持的小数最大位数 D 是 30。如果 D 省略默认为 0,M 省略默认为 10。

接下来我们看一波 FLOAT vs DECIMAL 的真实精度对比:

复制代码
mysql> create table tt8 (id int, salary float(10,8), salary2 decimal (10,8));
mysql> insert into tt8 values (100, 23.12345612, 23.12345612);
mysql> select * from tt8;
+------+-------------+-------------+
| id   | salary      | salary2     |
+------+-------------+-------------+
|  100 | 23.12345695 | 23.12345612 | 
+------+-------------+-------------+
-- 瞧!float(10,8) 存入的值被失真变成了 23.12345695!而 decimal(10,8) 依然保持绝对精确。

原因: FLOAT的单精度浮点数有效精度大约只有 7 位(C++ 同理)。凡是涉及金钱、科学计数、高精度的场景,一律强推使用 DECIMAL


三. 字符串类型

在字符串处理上,MySQL 分为定长和变长两种核心策略:

3.3.1 CHAR(L)

  • 特点: 固定长度字符串,单位为字符(注意:不是字节!汉字和英文字符同等待遇),L 最大可取 255。

    mysql> create table tt9(id int, name char(2));
    mysql> insert into tt9 values (100, 'ab'); -- 成功
    mysql> insert into tt9 values (101, '中国'); -- 成功(存放了两个汉字字符)

    -- 如果超过 255
    mysql> create table tt10(id int, name char(256));
    ERROR 1074 (42000): Column length too big for column 'name' (max=255); use BLOB or TEXT instead

3.3.2 VARCHAR(L)

  • 特点: 可变长度字符串,L 表示最大字符长度。

  • 底层上限原理(重点): MySQL 的单行(Row)最大限制为 65535 字节VARCHAR 的实际有效存储字节上限为 65532 字节(因为有 1~3 个字节用于记录实际数据的大小)。

    这意味着它的最大字符长度限制与其字符集编码紧密结合:

    • utf8 编码:每个字符最多占 3 字节。Max L = 65532 / 3 = 21844。

    • gbk 编码:每个字符最多占 2 字节。Max L = 65532 / 2 = 32766。

      -- 验证 utf8 下 L=21845 越界:
      mysql> create table tt11(name varchar(21845)) charset=utf8;
      ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBS, is 65535.

      -- 验证 L=21844 成功创建:
      mysql> create table tt11(name varchar(21844)) charset=utf8;
      Query OK, 0 rows affected (0.01 sec)

主流编码下的字节数

编码 一个汉字的字节数 说明
UTF-8 3 字节 最通用的编码,网页、大多数数据库默认
GBK / GB2312 2 字节 简体中文环境常用,Windows 系统常见
UTF-16 (LE/BE) 2~4 字节 多数常用汉字占 2 字节,生僻字可能占 4 字节

3.3.3 CHAR 与 VARCHAR 空间效率大比拼

在底层存储中,我们假设在 utf8编码下:

实际存储内容 CHAR(4) 占用空间 VARCHAR(4) 占用空间
abcd 4 * 3 = 12 bytes 4 * 3 + 1 (长度信息) = 13 bytes
A 4 * 3 = 12 bytes (浪费空间) 1 * 3 + 1 (长度信息) = 4 bytes (按需开辟)
Abcde 数据超长,拦截报错 数据超长,拦截报错

🛠 经典选型建议(如何抉择?)

  1. 选 CHAR(定长): 适合数据长度变化极小、甚至固定 的列。例如:身份证号(18位)、手机号(11位)、MD5 密文、UUID。虽然浪费磁盘空间,但是由于是物理连续的一片固定内存,读写效率极高

  2. 选 VARCHAR(变长): 适合数据长度存在明显差异的列。例如:姓名、家庭地址、个人简介。在最大自定义范围内,"用多少,开辟多少",充分节约磁盘空间。


四. 日期时间类型

MySQL 日常开发中常用的日期时间类型主要有三种:

类型 格式 占用字节 特点
DATE yyyy-mm-dd 3 字节 只存储日期
DATETIME yyyy-mm-dd HH:mm:ss 8 字节 表示范围从 1000 到 9999 年
TIMESTAMP yyyy-mm-dd HH:mm:ss 4 字节 时间戳。插入/更新数据时会自动刷新为当前时间

4.1 TIMESTAMP 自动更新测试

复制代码
-- 1. 创建表
mysql> create table birthday (t1 date, t2 datetime, t3 timestamp);

-- 2. 仅插入 t1 和 t2
mysql> insert into birthday (t1, t2) values('1997-7-1', '2008-8-8 12:1:1');

mysql> select * from birthday;
+------------+---------------------+---------------------+
| t1         | t2                  | t3                  |
+------------+---------------------+---------------------+
| 1997-07-01 | 2008-08-08 12:01:01 | 2017-11-12 18:28:55 | -- t3(时间戳) 自动补上当前时间
+------------+---------------------+---------------------+

-- 3. 更新 t1 的数值
mysql> update birthday set t1='2000-1-1';

mysql> select * from birthday;
+------------+---------------------+---------------------+
| t1         | t2                  | t3                  |
+------------+---------------------+---------------------+
| 2000-01-01 | 2008-08-08 12:01:01 | 2017-11-12 18:32:09 | -- 更新操作会同步刷新时间戳!
+------------+---------------------+---------------------+

五. ENUM 与 SET 类型

这两个属于限制型、带有约束属性的字符串类型。

5.1 特点

  • ENUM(单选): 只能在候选集合中选一个值写入。为了节省底层开销,内部是以数字编号(1, 2, 3...)进行存储的,最多可存储 65535 个选项。

  • SET(多选): 可以选择候选集合中任意多个 组合值。各成员间以 , 隔开。内部同样以数字存储,但和 Linux 文件权限一样,采用比特位(1, 2, 4, 8, 16...)映射的方式来判定集合内的爱好组合。最多支持 64 个选项。

⚠️ 避坑指南: 在实际业务中,不推荐INSERT时使用数字标号代替文本,因为可读性较差,维护成本高。

5.2 案例实战:投票调查表

我们创建一个问卷投票表:

复制代码
mysql> create table votes (
    username varchar(30),
    hobby set('登山','游泳','篮球','武术'),
    gender enum('男','女')
);

5.3 数据插入与基础查询

复制代码
-- 使用文本插入
mysql> insert into votes values('雷锋','登山,武术','男');

-- 混用数字代号插入('女' 对应的 enum 下标为 2)
mysql> insert into votes values('Juse','登山,武术', 2);

mysql> select * from votes where gender=2;
+----------+---------------+--------+
| username | hobby         | gender |
+----------+---------------+--------+
| Juse     | 登山,武术     | 女     |
+----------+---------------+--------+
🚨 核心避坑:如何查询集合中包含某一项的人?

假设我们现有数据如下:

复制代码
+------------+---------------+--------+
| username   | hobby         | gender |
+------------+---------------+--------+
| 雷锋       | 登山,武术     | 男     |
| Juse       | 登山,武术     | 女     |
| LiLei      | 登山          | 男     |
| HanMeiMei  | 游泳          | 女     |
+------------+---------------+--------+

如果你尝试用常规的 **=**去匹配查询喜欢"登山"的人:

复制代码
mysql> select * from votes where hobby='登山';
+----------+-------+--------+
| username | hobby | gender |
+----------+-------+--------+
| LiLei    | 登山  | 男     |
+----------+-------+--------+

问题: 结果只查出了李雷。因为等号 = 做的是完整字符串精确匹配!像雷锋、Juse 这种除了登山还喜欢武术的人,直接被过滤掉了。

正确姿势:使用 find_in_set 函数

在 MySQL 中,查找集合中是否包含某项,必须依靠内置函数 find_in_set(sub, str_list)

  • 原理: 如果元素 sub存在于以逗号隔开的字符串 str_list中,返回对应的元素下标(从1开始);否则返回 0。

    mysql> select find_in_set('a', 'a,b,c'); -- 返回 1
    mysql> select find_in_set('d', 'a,b,c'); -- 返回 0

    -- 完美查询出所有爱好包含 "登山" 的人:
    mysql> select * from votes where find_in_set('登山', hobby);
    +----------+---------------+--------+
    | username | hobby | gender |
    +----------+---------------+--------+
    | 雷锋 | 登山,武术 | 男 |
    | Juse | 登山,武术 | 女 |
    | LiLei | 登山 | 男 |
    +----------+---------------+--------+


结语

从底层的位运算(BITSET)到高精度的 DECIMAL处理,我们可以看出,MySQL 的数据类型设计和 C/C++ 的底层内存对齐、结构体映射有着极高的相通之处。合理地规划字段类型,不仅能为你的系统省下海量存储,更能免除后续的各种越界、精度异常 Bug。

相关推荐
摇滚侠1 小时前
VMvare 安装 Linux CentOS 7
linux·运维·centos
星恒随风1 小时前
C++ string 入门(一)
开发语言·c++·笔记·学习
旖-旎1 小时前
《LeetCode 200 FloodFill 岛屿数量DFS解法》
c++·算法·深度优先·力扣·floodfill
CingSyuan1 小时前
服务器现场排障:在 Windows 下使用 Linux reader 直接查看 Linux 系统 U 盘中的日志文件与文件结构
linux·运维·服务器·网络·windows
Upsy-Daisy1 小时前
Hermes Agent 学习笔记 07:Messaging Gateway,让 Agent 从终端走向多平台入口
运维·服务器·数据库
程序员晨曦1 小时前
数据库写轮眼:看透 MVCC 版本链、快照、隔离级别。
数据库·oracle
skywalk81631 小时前
继续推进心语项目6.15 @CodeArts
开发语言·算法·编程
Leon-Ning Liu1 小时前
MySQL数据恢复实践:binlog2sql数据追加
数据库·mysql
前进吧-程序员1 小时前
反转链表完全指南:辅助容器、三指针、头插法
数据结构·c++·链表