Oracle ACE

MySQL

MySQL 8.4 数据类型详解

MySQL 8.4 LTS 已经发布有一段时间了,很多项目都在准备推进升级。本期内容来带大家复盘一下 MySQL 8.4 中的数据类型。

01. 版本现状:8.4 LTS 数据类型

MySQL 8.4 作为首个长期支持版本(LTS),在数据类型层面延续了8.0的框架,但强化了严格模式(Strict Mode)的默认启用,这对数据迁移、版本升级至关重要。

维度 MySQL 5.7(已EOL) MySQL 8.0 MySQL 8.4.6 LTS(推荐)
JSON支持 基础JSON JSON增强 + 多值索引 JSON Duality Views(技术预览)
字符集 utf8(utf8mb3) utf8mb4 默认 utf8mb4_0900_ai_ci 强制
数值精度 FLOAT 建议 DECIMAL 强化 DECIMAL(65,30) 极限精度
时区处理 TIMESTAMP 转化 显式时区 与OCI云服务时区同步优化
AI向量 不支持 不支持 9.0 预览 / 8.4 建议 JSON 中转

例如,MySQL 8.4 默认启用了STRICT_TRANS_TABLESONLY_FULL_GROUP_BY,这意味着向 UNSIGNED 字段插入负数的操作,在8.4中默认会报错而非仅警告。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mysql> select version()\G
*************************** 1. row ***************************
version(): 8.4.6
1 row in set (0.00 sec)

mysql> select @@sql_mode\G
*************************** 1. row ***************************
@@sql_mode: ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
1 row in set (0.00 sec)

mysql> create table mydb.t1 (id int unsigned);
Query OK, 0 rows affected (0.11 sec)

mysql> insert mydb.t1 select 1;
Query OK, 1 row affected (0.01 sec)
Records: 1 Duplicates: 0 Warnings: 0

mysql> insert mydb.t1 select -1;
ERROR 1264 (22003): Out of range value for column 'id' at row 1

02. 数值类型:DECIMAL的精度问题与FLOAT陷阱

很多同学误以为 DECIMAL与FLOAT/DOUBLE都差不多,可以互换使用。存储金额或AI模型参数时,习惯性地使用FLOAT,但这在金融系统中应该是绝对禁止的。

来看具体例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- 场景:存储以下值,哪个类型不会丢失精度?
-- 12325.1251717337, 6212, 551.124111

-- 错误示范:FLOAT/DOUBLE会丢失精度(二进制浮点误差)
CREATE TABLE bad_precision (
tid BIGINT PRIMARY KEY,
amount FLOAT(10,2), -- 实际存储可能变成12325.13或12325.12
ai_weight DOUBLE -- AI模型权重如果用DOUBLE,累加后偏差可达0.1%
);

-- 正确做法:DECIMAL定点数,精确到小数点后10位以上
CREATE TABLE financial_data (
tid BIGINT PRIMARY KEY,
amount DECIMAL(18,8), -- 支持亿级金额,小数点后8位,满足加密货币精度
ai_confidence DECIMAL(5,4) -- AI置信度:0.0000到9.9999
);

-- 插入测试
INSERT INTO bad_precision VALUES (1, 12325.1251717337, 0.9876);
INSERT INTO financial_data VALUES (1, 12325.1251717337, 0.9876);

输出结果,表 financial_data 精确显示 12325.12517173 ,而 bad_precision 中的 FLOAT 类型会显示为 12325.13:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> SELECT * FROM bad_precision;
+-----+----------+-----------+
| tid | amount | ai_weight |
+-----+----------+-----------+
| 1 | 12325.13 | 0.9876 |
+-----+----------+-----------+
1 row in set (0.00 sec)

mysql> SELECT * FROM financial_data;
+----------------+----------------+---------------+
| transaction_id | amount | ai_confidence |
+----------------+----------------+---------------+
| 1 | 12325.12517173 | 0.9876 |
+----------------+----------------+---------------+
1 row in set (0.00 sec)

03. 日期时间:TIMESTAMP的时区陷阱与DATETIME的AI应用

MySQL 8.4在OCI云环境下默认使用UTC时区存储,但国内一般On-Premises服务器通常设置为CST(China Standard Time),这会导致TIMESTAMP类型出现8小时偏差。

1
2
3
4
5
6
7
8
9
10
11
12
-- 优化会议表的时间存储。start_time用DATETIME,duration用TIME
CREATE TABLE meeting_schedule (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
start_time DATETIME(3), -- 支持毫秒级,适合AI训练时间戳
duration TIME, -- 存储间隔:'01:30:00'表示1.5小时
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB;

-- 插入数据
INSERT INTO meeting_schedule (name, start_time, duration)
VALUES ('AI model review', '2025-11-10 14:30:00.123', '02:30:00');

时间数据类型上的索引。

示例:高效查询2000年出生的用户(索引优化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- 创建测试表
CREATE TABLE employees (
id INT AUTO_INCREMENT PRIMARY KEY,
first_name VARCHAR(50) NOT NULL,
last_name VARCHAR(50) NOT NULL,
date_of_birth DATE NOT NULL,
email VARCHAR(100) UNIQUE,
hire_date DATE DEFAULT (CURRENT_DATE),
INDEX idx_dob (date_of_birth)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT INTO employees (first_name, last_name, date_of_birth, email, hire_date) VALUES
('James', 'Smith', '2000-01-01', 'james.smith@example.com', '2022-04-01'),
('Emma', 'Johnson', '2000-05-15', 'emma.johnson@example.com', '2022-04-01'),
('Robert', 'Williams', '2000-08-20', 'robert.williams@example.com', '2023-01-15'),
('Olivia', 'Brown', '2000-11-30', 'olivia.brown@example.com', '2023-02-01'),
('Michael', 'Jones', '2000-12-31', 'michael.jones@example.com', '2023-04-01'),
('William', 'Garcia', '1999-12-31', 'william.garcia@example.com', '2021-04-01'),
('Sophia', 'Miller', '2001-01-01', 'sophia.miller@example.com', '2023-04-01'),
('David', 'Davis', '1990-06-10', 'david.davis@example.com', '2013-10-01'),
('Isabella', 'Rodriguez', '1985-03-25', 'isabella.rodriguez@example.com', '2008-04-01'),
('John', 'Wilson', '2005-07-07', 'john.wilson@example.com', '2027-04-01');

错误写法:WHERE YEAR(date_of_birth) = 2000(无法使用索引)

1
2
3
4
5
6
7
8
mysql> EXPLAIN ANALYZE
-> SELECT * FROM employees
-> WHERE YEAR(date_of_birth) = 2000\G
*************************** 1. row ***************************
EXPLAIN: -> Filter: (year(employees.date_of_birth) = 2000) (cost=1.25 rows=10) (actual time=0.0291..0.0378 rows=5 loops=1)
-> Table scan on employees (cost=1.25 rows=10) (actual time=0.0266..0.0341 rows=10 loops=1)

1 row in set (0.00 sec)

正确写法:范围查询(利用索引)

1
2
3
4
5
6
7
mysql> EXPLAIN ANALYZE
-> SELECT * FROM employees
-> WHERE date_of_birth BETWEEN '2000-01-01' AND '2000-12-31'\G
*************************** 1. row ***************************
EXPLAIN: -> Index range scan on employees using idx_dob over ('2000-01-01' <= date_of_birth <= '2000-12-31'), with index condition: (employees.date_of_birth between '2000-01-01' and '2000-12-31') (cost=2.51 rows=5) (actual time=0.0375..0.041 rows=5 loops=1)

1 row in set (0.01 sec)

04. JSON类型:从文档存储到AI元数据管理

虽然原生VECTOR类型在MySQL 9.0才引入,但8.4的JSON类型通过虚拟列(Generated Columns)已经可以高效存储AI应用的半结构化元数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 创建带JSON校验的表(数据完整性约束)
CREATE TABLE fshop (
id INT AUTO_INCREMENT PRIMARY KEY,
product JSON,
CHECK (JSON_VALID(product)) -- 确保插入的是合法JSON,不是普通字符串
);

-- 插入AI生成的商品描述(包含多模态特征)
INSERT INTO fshop (product) VALUES (
'{"name": "AI Camera",
"features": ["face", "night"],
"ai_vectors": {"visual": [0.23, 0.56, 0.89], "text": [0.12, 0.34]},
"price": 299.99}'
);

直接查询JSON字段会失败(需要 JSON_UNQUOTE ),MySQL 8.4 推荐方案是创建虚拟列并建立索引。

1
2
3
4
ALTER TABLE fshop 
ADD COLUMN product_name VARCHAR(100)
GENERATED ALWAYS AS (JSON_UNQUOTE(product->'$.name')) STORED,
ADD INDEX idx_name (product_name);

现在可以直接查询,且走索引。

1
2
3
4
5
6
mysql> EXPLAIN ANALYZE
-> SELECT * FROM fshop WHERE product_name = 'AI Camera'\G
*************************** 1. row ***************************
EXPLAIN: -> Index lookup on fshop using idx_name (product_name='AI Camera') (cost=0.35 rows=1) (actual time=0.0201..0.024 rows=1 loops=1)

1 row in set (0.00 sec)

JSON路径查询(-> vs ->>的区别)

1
2
3
4
5
SELECT 
product->'$.name' AS raw_json, -- 返回带引号的字符串:"智能摄像头"
product->>'$.name' AS unquoted_text, -- 返回纯文本:智能摄像头(推荐)
JSON_EXTRACT(product, '$.price') AS price -- 传统写法
FROM fshop;

输出结果:

1
2
3
4
5
6
+-------------+---------------+--------+
| raw_json | unquoted_text | price |
+-------------+---------------+--------+
| "AI Camera" | AI Camera | 299.99 |
+-------------+---------------+--------+
1 row in set (0.00 sec)

AI应用实践:在OCI的HeatWave Lakehouse中,MySQL 8.4的JSON列可以直接被AI算法读取为特征向量,无需ETL转换。建议在本地环境中为JSON列设置COLUMN_FORMAT COMPRESSED,可节省40%存储空间。

1
2
3
4
CREATE TABLE employees (
id INT PRIMARY KEY,
info JSON
) ENGINE=InnoDB COMPRESSION='zlib'; -- or 'lz4'

05. 字符类型:utf8mb4与转义符处理

特殊字符的插入也需要特别注意,特别是姓名中的单引号(如O’Hara)。MySQL 8.4默认使用utf8mb4字符集,支持emoji和中文生僻字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
插入带单引号的姓名(两种正确方式)
-- 方式1:双单引号转义(SQL标准)
INSERT INTO customers (last_name) VALUES ('O''Hara');

-- 方式2:反斜杠转义(仅在NO_BACKSLASH_ESCAPES关闭时可用)
INSERT INTO customers (last_name) VALUES ('O\'Hara');

-- 验证utf8mb4支持(存储AI生成的Unicode标签)
CREATE TABLE ai_tags (
tag_id INT PRIMARY KEY,
tag_name VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci,
-- 支持存储emoji:☕🌻👉
description VARCHAR(255)
);

INSERT INTO ai_tags VALUES (1, '咖啡文化☕', 'AI识别的饮品标签');

06. 生产警示:SQL Mode与隐式类型转换

MySQL 8.4的严格模式设置是数据迁移最容易踩坑的地方。下面展示了在非严格模式下,向UNSIGNED字段插入负数的诡异行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-- sql_mode=''(空模式)下的陷阱。 8.4中已不建议,但部分遗留系统仍使用
SET sql_mode = '';

CREATE TABLE inventory_items (
id INT AUTO_INCREMENT PRIMARY KEY,
item_count INT UNSIGNED -- 无符号,理论上不能存负数
);

-- 在非严格模式下,插入-1不会报错,而是变成0并报警告(数据丢失!)
INSERT INTO inventory_items (item_count) VALUES (-1);

mysql> show warnings;
+---------+------+-----------------------------------------------------+
| Level | Code | Message |
+---------+------+-----------------------------------------------------+
| Warning | 1264 | Out of range value for column 'item_count' at row 1 |
+---------+------+-----------------------------------------------------+
1 row in set (0.00 sec)

-- 结果:item_count = 0
mysql> table inventory_items;
+----+------------+
| id | item_count |
+----+------------+
| 1 | 0 |
+----+------------+
1 row in set (0.00 sec)

CONCAT与NULL的陷阱

1
2
3
4
5
6
7
mysql> SELECT CONCAT('Hello ', NULL, 'World!');
+----------------------------------+
| CONCAT('Hello ', NULL, 'World!') |
+----------------------------------+
| NULL |
+----------------------------------+
1 row in set (0.00 sec)

返回NULL(不是"Hello World!"),这在AI文本拼接中经常导致意外空值。

MySQL 8.4 生产环境推荐配置(/etc/my.cnf)

1
2
[mysqld]
sql_mode = ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

使用COALESCE避免NULL(生产最佳实践),可以正确返回 Hello World!

1
2
3
4
5
6
7
mysql> SELECT CONCAT('Hello ', COALESCE(NULL, ''), 'World!');  --  Hello World!
+------------------------------------------------+
| CONCAT('Hello ', COALESCE(NULL, ''), 'World!') |
+------------------------------------------------+
| Hello World! |
+------------------------------------------------+
1 row in set (0.00 sec)

总结

本文干货较多,这里总结一下几条重点。

  1. 金融与AI模型参数:坚决使用DECIMAL而非FLOAT/DOUBLE,精度损失在AI累加计算中会被放大
  2. 时区敏感业务:8.4中优先使用DATETIME(3)存储毫秒级时间戳,TIMESTAMP仅用于自动更新的created_at字段,避免OCI跨Region时区混乱
  3. AI元数据存储:利用JSON类型+虚拟列+索引的组合,在8.4中实现准向量检索能力,为9.0的VECTOR升级预留空间
  4. 字符集:强制使用utf8mb4_0900_ai_ci,禁用已废弃的utf8mb3,确保使用中文的操作系统上的生僻字与emoji支持
  5. 严格模式:生产环境必须启用STRICT_TRANS_TABLES,杜绝隐式截断,宁可报错不可错存

Have a nice day ~ ☕

🌻 近期内容 ▼

👉 这里有得聊

如果对国产基础软件(操作系统、数据库、中间件)、AI、Vibe Coding、OpenClaw 、Hermes Agent 等感兴趣,可以加群一起聊聊。关注微信公众号:(少安事务所),后台回复[群],即可看到入口。如果这篇文章为你带来了灵感或启发,请帮忙『点赞、推荐、转发』吧,感谢!ღ( ´・ᴗ・` )~