SQL数据类型
哪吒 2024/1/1
# SQL数据类型
点击勘误issues (opens new window),哪吒感谢大家的阅读
# 数据类型概述
SQL数据类型定义了列中可以存储的数据种类。不同的数据库管理系统支持的数据类型可能略有差异,但大多数都遵循SQL标准。正确选择数据类型对于数据库性能、存储效率和数据完整性至关重要。
# 数据类型分类
- 数值类型 - 存储数字
- 字符串类型 - 存储文本
- 日期时间类型 - 存储日期和时间
- 二进制类型 - 存储二进制数据
- 布尔类型 - 存储真/假值
- JSON类型 - 存储JSON格式数据
- 空间数据类型 - 存储地理信息
# 数值类型
# 整数类型
# TINYINT
- 范围: -128 到 127(有符号),0 到 255(无符号)
- 存储: 1字节
- 用途: 存储小整数,如年龄、状态码
CREATE TABLE user_info (
id INT PRIMARY KEY,
age TINYINT UNSIGNED, -- 0-255,适合存储年龄
status TINYINT DEFAULT 1 -- 状态标识
);
INSERT INTO user_info (id, age, status) VALUES
(1, 25, 1),
(2, 30, 0),
(3, 255, 1); -- 无符号最大值
# SMALLINT
- 范围: -32,768 到 32,767(有符号),0 到 65,535(无符号)
- 存储: 2字节
- 用途: 存储中等大小的整数
CREATE TABLE product (
id INT PRIMARY KEY,
stock_quantity SMALLINT UNSIGNED, -- 库存数量
year_produced SMALLINT -- 生产年份
);
INSERT INTO product (id, stock_quantity, year_produced) VALUES
(1, 1500, 2023),
(2, 65535, 2024), -- 无符号最大值
(3, 0, 2022);
# MEDIUMINT
- 范围: -8,388,608 到 8,388,607(有符号),0 到 16,777,215(无符号)
- 存储: 3字节
- 用途: MySQL特有,介于SMALLINT和INT之间
CREATE TABLE statistics (
id INT PRIMARY KEY,
page_views MEDIUMINT UNSIGNED, -- 页面访问量
user_count MEDIUMINT
);
# INT/INTEGER
- 范围: -2,147,483,648 到 2,147,483,647(有符号),0 到 4,294,967,295(无符号)
- 存储: 4字节
- 用途: 最常用的整数类型
CREATE TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED,
total_amount INT, -- 以分为单位存储金额
created_timestamp INT UNSIGNED -- Unix时间戳
);
INSERT INTO orders (user_id, total_amount, created_timestamp) VALUES
(1001, 299900, 1640995200), -- 2999.00元
(1002, 150000, 1640995300); -- 1500.00元
# BIGINT
- 范围: -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
- 存储: 8字节
- 用途: 存储大整数,如ID、时间戳
CREATE TABLE big_data (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT,
timestamp_ms BIGINT, -- 毫秒级时间戳
file_size BIGINT UNSIGNED -- 文件大小(字节)
);
INSERT INTO big_data (user_id, timestamp_ms, file_size) VALUES
(1000000001, 1640995200000, 2147483648); -- 2GB文件
# 浮点数类型
# FLOAT
- 精度: 单精度浮点数
- 存储: 4字节
- 精度: 约7位有效数字
CREATE TABLE measurements (
id INT PRIMARY KEY,
temperature FLOAT(5,2), -- 总共5位,小数点后2位
humidity FLOAT
);
INSERT INTO measurements (id, temperature, humidity) VALUES
(1, 23.45, 65.7),
(2, -10.50, 45.123456), -- 注意精度限制
(3, 999.99, 100.0);
SELECT * FROM measurements;
-- 结果可能因精度问题略有差异
# DOUBLE
- 精度: 双精度浮点数
- 存储: 8字节
- 精度: 约15位有效数字
CREATE TABLE scientific_data (
id INT PRIMARY KEY,
latitude DOUBLE(10,8), -- 纬度,高精度
longitude DOUBLE(11,8), -- 经度,高精度
measurement_value DOUBLE
);
INSERT INTO scientific_data (id, latitude, longitude, measurement_value) VALUES
(1, 39.90419989, 116.40739999, 3.141592653589793),
(2, -33.86785, 151.20732, 2.718281828459045);
# 定点数类型
# DECIMAL/NUMERIC
- 精度: 精确的定点数
- 存储: 可变,取决于精度
- 用途: 金融计算,要求精确的小数
CREATE TABLE financial (
id INT PRIMARY KEY,
account_balance DECIMAL(15,2), -- 最大13位整数,2位小数
interest_rate DECIMAL(5,4), -- 利率,如0.0525表示5.25%
transaction_amount DECIMAL(10,2)
);
INSERT INTO financial (id, account_balance, interest_rate, transaction_amount) VALUES
(1, 1234567890.50, 0.0525, -1500.75),
(2, 999999999.99, 0.0350, 2000.00),
(3, 0.01, 0.0000, 0.01);
-- 精确计算
SELECT
account_balance,
account_balance * interest_rate as annual_interest,
account_balance + transaction_amount as new_balance
FROM financial;
# 字符串类型
# 定长字符串
# CHAR
- 长度: 固定长度,0-255字符
- 存储: 固定空间,不足部分用空格填充
- 用途: 存储固定长度的字符串
CREATE TABLE codes (
id INT PRIMARY KEY,
country_code CHAR(2), -- 国家代码,如CN、US
product_code CHAR(10), -- 产品代码
status_flag CHAR(1) -- 状态标志
);
INSERT INTO codes (id, country_code, product_code, status_flag) VALUES
(1, 'CN', 'PROD001', 'A'),
(2, 'US', 'PROD002', 'I'),
(3, 'JP', 'PROD003', 'A');
-- 查询时自动去除尾部空格
SELECT
country_code,
LENGTH(country_code) as length,
CONCAT('[', country_code, ']') as with_brackets
FROM codes;
# 变长字符串
# VARCHAR
- 长度: 可变长度,0-65,535字符(MySQL)
- 存储: 实际长度+1-2字节长度信息
- 用途: 最常用的字符串类型
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE,
first_name VARCHAR(30),
last_name VARCHAR(30),
bio VARCHAR(500)
);
INSERT INTO users (id, username, email, first_name, last_name, bio) VALUES
(1, 'john_doe', 'john@example.com', 'John', 'Doe', '软件工程师,热爱编程'),
(2, 'jane_smith', 'jane@example.com', 'Jane', 'Smith', NULL),
(3, 'user123', 'user123@test.com', '张', '三', '这是一个很长的个人简介...');
-- 字符串函数示例
SELECT
username,
LENGTH(username) as username_length,
CONCAT(first_name, ' ', last_name) as full_name,
UPPER(email) as email_upper
FROM users;
# 大文本类型
# TEXT类型家族
CREATE TABLE articles (
id INT PRIMARY KEY,
title VARCHAR(200),
summary TINYTEXT, -- 最大255字符
content TEXT, -- 最大65,535字符
full_content MEDIUMTEXT, -- 最大16,777,215字符
archive_content LONGTEXT -- 最大4,294,967,295字符
);
INSERT INTO articles (id, title, summary, content) VALUES
(1, 'SQL数据类型详解', '本文详细介绍了SQL中的各种数据类型',
'SQL数据类型是数据库设计的基础...');
-- 文本搜索
SELECT title, summary
FROM articles
WHERE content LIKE '%数据类型%'
OR summary LIKE '%数据类型%';
# 日期时间类型
# DATE
- 格式: YYYY-MM-DD
- 范围: 1000-01-01 到 9999-12-31
- 存储: 3字节
CREATE TABLE events (
id INT PRIMARY KEY,
event_name VARCHAR(100),
event_date DATE,
created_date DATE DEFAULT (CURRENT_DATE)
);
INSERT INTO events (id, event_name, event_date) VALUES
(1, '新年庆典', '2024-01-01'),
(2, '春节假期', '2024-02-10'),
(3, '国庆节', '2024-10-01');
-- 日期计算
SELECT
event_name,
event_date,
DATEDIFF(event_date, CURRENT_DATE) as days_until_event,
DATE_ADD(event_date, INTERVAL 1 YEAR) as next_year_date
FROM events
WHERE event_date >= CURRENT_DATE;
# TIME
- 格式: HH:MM:SS
- 范围: -838:59:59 到 838:59:59
- 存储: 3字节
CREATE TABLE schedules (
id INT PRIMARY KEY,
task_name VARCHAR(100),
start_time TIME,
end_time TIME,
duration TIME -- 可以存储时间间隔
);
INSERT INTO schedules (id, task_name, start_time, end_time, duration) VALUES
(1, '晨会', '09:00:00', '09:30:00', '00:30:00'),
(2, '午休', '12:00:00', '13:00:00', '01:00:00'),
(3, '加班', '18:00:00', '22:00:00', '04:00:00');
-- 时间计算
SELECT
task_name,
start_time,
end_time,
TIMEDIFF(end_time, start_time) as calculated_duration,
TIME_FORMAT(start_time, '%H时%i分') as formatted_time
FROM schedules;
# DATETIME
- 格式: YYYY-MM-DD HH:MM:SS
- 范围: 1000-01-01 00:00:00 到 9999-12-31 23:59:59
- 存储: 8字节
CREATE TABLE logs (
id INT PRIMARY KEY,
user_id INT,
action VARCHAR(50),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
INSERT INTO logs (id, user_id, action) VALUES
(1, 1001, '登录'),
(2, 1001, '查看商品'),
(3, 1002, '下单');
-- 等待一会儿后更新
UPDATE logs SET action = '登录成功' WHERE id = 1;
-- 日期时间查询
SELECT
user_id,
action,
created_at,
updated_at,
DATE(created_at) as date_only,
TIME(created_at) as time_only,
YEAR(created_at) as year_part
FROM logs
WHERE created_at >= '2024-01-01 00:00:00';
# TIMESTAMP
- 格式: YYYY-MM-DD HH:MM:SS
- 范围: 1970-01-01 00:00:01 UTC 到 2038-01-19 03:14:07 UTC
- 存储: 4字节
- 特点: 自动转换时区
CREATE TABLE user_sessions (
id INT PRIMARY KEY,
user_id INT,
login_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
logout_time TIMESTAMP NULL
);
INSERT INTO user_sessions (id, user_id) VALUES
(1, 1001),
(2, 1002);
-- 模拟用户活动
UPDATE user_sessions SET logout_time = CURRENT_TIMESTAMP WHERE id = 1;
-- 时间戳查询
SELECT
user_id,
login_time,
last_activity,
logout_time,
UNIX_TIMESTAMP(login_time) as unix_timestamp,
FROM_UNIXTIME(UNIX_TIMESTAMP(login_time)) as converted_back
FROM user_sessions;
# YEAR
- 格式: YYYY
- 范围: 1901-2155
- 存储: 1字节
CREATE TABLE vehicles (
id INT PRIMARY KEY,
brand VARCHAR(50),
model VARCHAR(50),
manufacture_year YEAR,
purchase_year YEAR
);
INSERT INTO vehicles (id, brand, model, manufacture_year, purchase_year) VALUES
(1, '丰田', '卡罗拉', 2020, 2021),
(2, '本田', '雅阁', 2019, 2020),
(3, '大众', '帕萨特', 2022, 2022);
-- 年份计算
SELECT
brand,
model,
manufacture_year,
purchase_year,
purchase_year - manufacture_year as years_before_purchase,
YEAR(CURRENT_DATE) - manufacture_year as vehicle_age
FROM vehicles;
# 二进制类型
# BINARY和VARBINARY
CREATE TABLE binary_data (
id INT PRIMARY KEY,
fixed_binary BINARY(16), -- 固定16字节
var_binary VARBINARY(255), -- 可变长度
file_hash BINARY(32) -- SHA-256哈希值
);
-- 插入二进制数据
INSERT INTO binary_data (id, fixed_binary, var_binary, file_hash) VALUES
(1, UNHEX('48656C6C6F20576F726C64'), -- "Hello World"的十六进制
UNHEX('54657374'), -- "Test"
UNHEX('E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855'));
-- 查询二进制数据
SELECT
id,
HEX(fixed_binary) as hex_fixed,
HEX(var_binary) as hex_var,
LENGTH(fixed_binary) as fixed_length,
LENGTH(var_binary) as var_length
FROM binary_data;
# BLOB类型家族
CREATE TABLE file_storage (
id INT PRIMARY KEY,
filename VARCHAR(255),
file_type VARCHAR(50),
small_file TINYBLOB, -- 最大255字节
medium_file BLOB, -- 最大65KB
large_file MEDIUMBLOB, -- 最大16MB
huge_file LONGBLOB -- 最大4GB
);
-- 注意:实际应用中通常不在数据库中存储大文件
-- 而是存储文件路径或使用专门的文件存储服务
# 布尔类型
CREATE TABLE user_preferences (
id INT PRIMARY KEY,
user_id INT,
email_notifications BOOLEAN DEFAULT TRUE,
sms_notifications BOOL DEFAULT FALSE, -- BOOL是BOOLEAN的同义词
is_premium BOOLEAN DEFAULT FALSE,
is_active BOOLEAN DEFAULT TRUE
);
INSERT INTO user_preferences (id, user_id, email_notifications, sms_notifications, is_premium) VALUES
(1, 1001, TRUE, FALSE, TRUE),
(2, 1002, 1, 0, 1), -- 1表示TRUE,0表示FALSE
(3, 1003, FALSE, TRUE, FALSE);
-- 布尔查询
SELECT
user_id,
email_notifications,
sms_notifications,
is_premium,
CASE
WHEN is_premium THEN '高级用户'
ELSE '普通用户'
END as user_type
FROM user_preferences
WHERE is_active = TRUE;
# JSON类型(MySQL 5.7+)
CREATE TABLE user_profiles (
id INT PRIMARY KEY,
user_id INT,
profile_data JSON,
settings JSON
);
INSERT INTO user_profiles (id, user_id, profile_data, settings) VALUES
(1, 1001,
JSON_OBJECT(
'name', '张三',
'age', 25,
'hobbies', JSON_ARRAY('编程', '阅读', '旅行'),
'address', JSON_OBJECT('city', '北京', 'district', '朝阳区')
),
JSON_OBJECT(
'theme', 'dark',
'language', 'zh-CN',
'notifications', JSON_OBJECT('email', true, 'sms', false)
)
);
-- JSON查询
SELECT
user_id,
JSON_EXTRACT(profile_data, '$.name') as name,
JSON_EXTRACT(profile_data, '$.age') as age,
JSON_EXTRACT(profile_data, '$.address.city') as city,
JSON_EXTRACT(settings, '$.theme') as theme
FROM user_profiles;
-- 使用->操作符(MySQL 5.7+)
SELECT
user_id,
profile_data->'$.name' as name,
profile_data->'$.hobbies[0]' as first_hobby,
settings->'$.notifications.email' as email_notifications
FROM user_profiles;
-- JSON数组查询
SELECT user_id, hobby
FROM user_profiles,
JSON_TABLE(profile_data, '$.hobbies[*]' COLUMNS (hobby VARCHAR(50) PATH '$')) as jt;
# 枚举类型
CREATE TABLE orders (
id INT PRIMARY KEY,
order_number VARCHAR(50),
status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending',
priority ENUM('low', 'medium', 'high') DEFAULT 'medium',
payment_method ENUM('credit_card', 'debit_card', 'paypal', 'cash') NOT NULL
);
INSERT INTO orders (id, order_number, status, priority, payment_method) VALUES
(1, 'ORD001', 'pending', 'high', 'credit_card'),
(2, 'ORD002', 'processing', 'medium', 'paypal'),
(3, 'ORD003', 'shipped', 'low', 'cash');
-- 枚举查询
SELECT
order_number,
status,
priority,
payment_method,
CASE status
WHEN 'pending' THEN '待处理'
WHEN 'processing' THEN '处理中'
WHEN 'shipped' THEN '已发货'
WHEN 'delivered' THEN '已送达'
WHEN 'cancelled' THEN '已取消'
END as status_cn
FROM orders
WHERE status IN ('pending', 'processing')
ORDER BY FIELD(priority, 'high', 'medium', 'low');
# 集合类型
CREATE TABLE user_permissions (
id INT PRIMARY KEY,
user_id INT,
username VARCHAR(50),
permissions SET('read', 'write', 'delete', 'admin', 'export', 'import') DEFAULT 'read'
);
INSERT INTO user_permissions (id, user_id, username, permissions) VALUES
(1, 1001, 'admin_user', 'read,write,delete,admin'),
(2, 1002, 'editor_user', 'read,write'),
(3, 1003, 'viewer_user', 'read'),
(4, 1004, 'power_user', 'read,write,export,import');
-- SET查询
SELECT
username,
permissions,
FIND_IN_SET('admin', permissions) > 0 as is_admin,
FIND_IN_SET('write', permissions) > 0 as can_write
FROM user_permissions;
-- 查找具有特定权限的用户
SELECT username, permissions
FROM user_permissions
WHERE FIND_IN_SET('admin', permissions) > 0;
-- 查找具有写权限但没有删除权限的用户
SELECT username, permissions
FROM user_permissions
WHERE FIND_IN_SET('write', permissions) > 0
AND FIND_IN_SET('delete', permissions) = 0;
# 数据类型选择指南
# 性能考虑
-- 示例:用户表设计
CREATE TABLE users_optimized (
-- 主键:使用AUTO_INCREMENT的INT,而不是UUID
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
-- 用户名:固定长度范围,使用VARCHAR
username VARCHAR(30) NOT NULL UNIQUE,
-- 邮箱:较长但变长,使用VARCHAR
email VARCHAR(100) NOT NULL UNIQUE,
-- 密码哈希:固定长度,使用CHAR
password_hash CHAR(60) NOT NULL, -- bcrypt哈希固定60字符
-- 年龄:小数值,使用TINYINT
age TINYINT UNSIGNED,
-- 性别:枚举值,使用ENUM
gender ENUM('M', 'F', 'O') DEFAULT 'O',
-- 状态:布尔值
is_active BOOLEAN DEFAULT TRUE,
-- 注册时间:使用TIMESTAMP自动管理
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- 最后登录:可为空的DATETIME
last_login DATETIME NULL,
-- 余额:金融数据使用DECIMAL
balance DECIMAL(10,2) DEFAULT 0.00,
-- 个人简介:可变长文本
bio TEXT NULL,
-- 设置:JSON格式
settings JSON NULL,
-- 索引优化
INDEX idx_username (username),
INDEX idx_email (email),
INDEX idx_created_at (created_at),
INDEX idx_active_users (is_active, created_at)
);
# 存储空间对比
-- 创建测试表比较不同数据类型的存储效率
CREATE TABLE storage_comparison (
id INT PRIMARY KEY,
-- 整数类型比较
tiny_int TINYINT, -- 1字节
small_int SMALLINT, -- 2字节
medium_int MEDIUMINT, -- 3字节
normal_int INT, -- 4字节
big_int BIGINT, -- 8字节
-- 字符串类型比较
char_10 CHAR(10), -- 固定10字节
varchar_10 VARCHAR(10), -- 变长,最多10字节+长度信息
-- 数值类型比较
float_num FLOAT, -- 4字节,精度较低
double_num DOUBLE, -- 8字节,精度较高
decimal_num DECIMAL(10,2) -- 变长,精确
);
-- 插入测试数据
INSERT INTO storage_comparison VALUES
(1, 100, 100, 100, 100, 100, 'test', 'test', 3.14, 3.14, 3.14);
-- 查看表的存储信息
SHOW TABLE STATUS LIKE 'storage_comparison';
# 数据类型转换
-- 隐式转换示例
SELECT
'123' + 456 as implicit_conversion, -- 字符串转数字
CONCAT(123, '456') as string_concat, -- 数字转字符串
'2024-01-01' + INTERVAL 1 DAY as date_calc;
-- 显式转换
SELECT
CAST('123.45' AS DECIMAL(10,2)) as cast_decimal,
CONVERT('2024-01-01', DATE) as convert_date,
CAST(NOW() AS CHAR) as datetime_to_string;
-- 安全转换(避免错误)
SELECT
CASE
WHEN '123abc' REGEXP '^[0-9]+$' THEN CAST('123abc' AS UNSIGNED)
ELSE 0
END as safe_conversion;
# 常见错误和最佳实践
# 1. 数据类型选择错误
-- 错误:使用VARCHAR存储数值
CREATE TABLE bad_design (
price VARCHAR(10), -- 应该使用DECIMAL
quantity VARCHAR(5) -- 应该使用INT
);
-- 正确:使用合适的数值类型
CREATE TABLE good_design (
price DECIMAL(10,2),
quantity INT UNSIGNED
);
# 2. 长度设置不当
-- 错误:长度过大浪费空间
CREATE TABLE wasteful (
name VARCHAR(1000), -- 通常50-100就够了
status VARCHAR(255) -- 应该使用ENUM
);
-- 正确:合理设置长度
CREATE TABLE efficient (
name VARCHAR(100),
status ENUM('active', 'inactive', 'pending')
);
# 3. 时区问题
-- 查看当前时区设置
SELECT @@global.time_zone, @@session.time_zone;
-- 设置时区
SET time_zone = '+08:00';
-- 时区安全的时间戳存储
CREATE TABLE timezone_safe (
id INT PRIMARY KEY,
event_time_utc TIMESTAMP, -- 存储UTC时间
event_time_local DATETIME, -- 存储本地时间
timezone_offset VARCHAR(6) -- 存储时区偏移
);
# 4. NULL值处理
-- 明确NULL的含义
CREATE TABLE null_handling (
id INT PRIMARY KEY,
required_field VARCHAR(50) NOT NULL, -- 必填字段
optional_field VARCHAR(50) NULL, -- 可选字段
default_field VARCHAR(50) DEFAULT 'unknown', -- 有默认值
calculated_field DECIMAL(10,2) NULL -- 计算字段,可能为空
);
-- NULL值查询
SELECT
required_field,
COALESCE(optional_field, '未填写') as optional_display,
IFNULL(calculated_field, 0) as calculated_display
FROM null_handling
WHERE optional_field IS NULL OR optional_field IS NOT NULL;
# 总结
# 数据类型选择原则
- 精确性优先 - 金融数据使用DECIMAL,不用FLOAT
- 空间效率 - 选择能满足需求的最小数据类型
- 查询性能 - 考虑索引和查询模式
- 业务语义 - 数据类型应该反映业务含义
- 扩展性 - 预留适当的增长空间
# 常用数据类型推荐
- 主键: INT UNSIGNED AUTO_INCREMENT
- 外键: INT UNSIGNED
- 金额: DECIMAL(10,2)
- 用户名: VARCHAR(50)
- 邮箱: VARCHAR(100)
- 密码哈希: CHAR(60)
- 状态: ENUM或TINYINT
- 时间戳: TIMESTAMP
- 日期: DATE
- 布尔值: BOOLEAN
- 大文本: TEXT
- 配置数据: JSON
# 性能优化建议
- 为经常查询的列选择合适的数据类型
- 避免过度使用TEXT和BLOB类型
- 合理使用索引,特别是复合索引
- 定期分析表结构和查询性能
- 考虑分区和分表策略