一、数值类型
1. 整数类型(INTEGER)
| 类型 | 存储空间 | 有符号范围 | 无符号范围(UNSIGNED) | 用途 |
|---|---|---|---|---|
| TINYINT | 1字节 | -128 ~ 127 | 0 ~ 255 | 年龄、状态、性别、小枚举 |
| SMALLINT | 2字节 | -32768 ~ 32767 | 0 ~ 65535 | 评分、库存量、小范围数值 |
| MEDIUMINT | 3字节 | -8388608 ~ 8388607 | 0 ~ 16777215 | 中等范围数值(如粉丝数) |
| INT / INTEGER | 4字节 | -2147483648 ~ 2147483647 | 0 ~ 4294967295 | 主键id、数量(最常用) |
| BIGINT | 8字节 | -9223372036854775808 ~ 9223372036854775807 | 0 ~ 18446744073709551615 | 超大id、订单号、时间戳(毫秒) |
属性说明:
sql
-- UNSIGNED:无符号,不能存负数(范围翻倍)
age TINYINT UNSIGNED -- 0~255
-- ZEROFILL:不足位数补0(自动加UNSIGNED)
id INT(5) ZEROFILL -- 存1显示00001
-- 括号内数字(如 INT(11)):不影响范围,只影响显示宽度(配合ZEROFILL)
2. 浮点类型(非精确数值,有精度丢失风险)
| 类型 | 存储空间 | 有符号范围 | 无符号范围 | 精度 | 用途 |
|---|---|---|---|---|---|
| FLOAT | 4字节 | ±1.175494351E-38 ~ ±3.402823466E+38 | 0 ~ ±3.402823466E+38 | 约7位小数 | 科学计算、温度、百分比(不需要精确的场景) |
| FLOAT(M,D) | 4字节 | 取决于M,D | 取决于M,D | 约7位小数 | 指定小数位数的浮点数(不推荐) |
| DOUBLE | 8字节 | ±2.2250738585072014E-308 ~ ±1.7976931348623157E+308 | 0 ~ ±1.7976931348623157E+308 | 约15位小数 | 科学计算、高精度浮点运算 |
| DOUBLE(M,D) | 8字节 | 取决于M,D | 取决于M,D | 约15位小数 | 指定小数位数的双精度(不推荐) |
| REAL | 8字节 | 同DOUBLE | 同DOUBLE | 约15位小数 | DOUBLE的同义词(取决于MySQL设置) |
| FLOAT(p) | 变长 | 取决于p | 取决于p | 取决于p | p=0~24用FLOAT,p=25~53用DOUBLE |
参数说明:
- M:总位数(最大255)
- D:小数位数(最大30)
- p:精度位数(0~53)
⚠️ 核心问题:精度丢失
浮点数采用二进制存储,某些十进制小数无法精确表示:
sql
-- ❌ 浮点数计算会出错
SELECT 0.1 + 0.2;
-- 结果:0.30000000000000004(不是 0.3!)
-- ❌ 比较也会出问题
SELECT 0.1 = 0.1; -- 看起来没问题
SELECT 0.1 + 0.2 = 0.3; -- 结果是 0(false)!
-- ✅ 用DECIMAL就精确
SELECT CAST(0.1 AS DECIMAL(10,2)) + CAST(0.2 AS DECIMAL(10,2));
-- 结果:0.30
二、字符串类型
1. 字符类型(最常用)
| 类型 | 最大长度 | 存储空间 | 特点 | 用途 |
|---|---|---|---|---|
| CHAR(N) | 255字符 | 固定N字节 | 定长、性能略好、浪费空间 | 手机号(11)、身份证(18)、MD5(32)、固定编码 |
| VARCHAR(N) | 65535字符 | 实际长度 + 1~2字节 | 变长、最常用、节省空间 | 姓名、标题、地址、描述、邮箱 |
| TINYTEXT | 255字符 | 实际长度 + 1字节 | 短文本、有长度前缀 | 极短内容(很少用,已被VARCHAR替代) |
| TEXT | 65535字符 | 实际长度 + 2字节 | 长文本、有长度前缀 | 文章内容、备注、评论、富文本 |
| MEDIUMTEXT | 16777215字符(约16MB) | 实际长度 + 3字节 | 中等长文本 | 日志、JSON数据、序列化数据 |
| LONGTEXT | 4294967295字符(约4GB) | 实际长度 + 4字节 | 超长文本 | 全文、大数据JSON、大型文档 |
存储空间计算(UTF-8编码):
| 字符类型 | 存储字节数 |
|---|---|
| 英文字母/数字 | 1字节 |
| 中文字符 | 3字节(UTF-8) |
| Emoji表情 | 4字节 |
| 特殊符号 | 1~4字节 |
sql
-- 示例:VARCHAR(50) 在 UTF-8 下
-- 存 'hello' → 5字节 + 1字节前缀 = 6字节
-- 存 '你好' → 6字节 + 1字节前缀 = 7字节
-- 存 '😊' → 4字节 + 1字节前缀 = 5字节
三、日期时间类型
1. 完整类型列表
| 类型 | 格式 | 范围 | 存储空间 | 零值 | 用途 |
|---|---|---|---|---|---|
| DATE | 'YYYY-MM-DD' | '1000-01-01' ~ '9999-12-31' | 3字节 | '0000-00-00' | 生日、入职日期、合同开始日 |
| TIME | 'HH:MM:SS' | '-838:59:59' ~ '838:59:59' | 3字节 | '00:00:00' | 上班时间、时长、倒计时 |
| DATETIME | 'YYYY-MM-DD HH:MM:SS' | '1000-01-01 00:00:00' ~ '9999-12-31 23:59:59' | 8字节 | '0000-00-00 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字节 | '0000-00-00 00:00:00' | 跨时区系统、日志时间 |
| YEAR | 'YYYY' | 1901 ~ 2155 | 1字节 | 0000 | 毕业年份、成立年份 |
| TIMESTAMP(6) | 带微秒 | 同上 | 7字节 | '0000-00-00 00:00:00.000000' | 高精度时间(需微秒级) |
| DATETIME(6) | 带微秒 | 同上 | 8字节 | '0000-00-00 00:00:00.000000' | 高精度时间(带微秒) |
2. DATE 类型(只有日期)
sql
-- 常见格式
birthday DATE COMMENT '生日'
-- 插入数据(多种格式)
INSERT INTO users (birthday) VALUES ('1990-05-15');
INSERT INTO users (birthday) VALUES ('1990/05/15');
INSERT INTO users (birthday) VALUES ('19900515');
INSERT INTO users (birthday) VALUES (NOW()); -- 当前日期
-- 常用函数
SELECT YEAR(birthday) FROM users; -- 提取年份
SELECT MONTH(birthday) FROM users; -- 提取月份
SELECT DAY(birthday) FROM users; -- 提取日
SELECT WEEKDAY(birthday) FROM users; -- 星期几(0=周一)
SELECT DATE_ADD(birthday, INTERVAL 1 YEAR); -- 加1年
SELECT DATEDIFF(NOW(), birthday) / 365 AS age; -- 计算年龄
四、表基本命令
- 查看表数据库表
sql
SHOW TABLES
- 创建表
sql
CREATE TABLE users (
`id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`name` VARCHAR(50) NOT NULL COMMENT '姓名',
`age` TINYINT UNSIGNED DEFAULT 22 COMMENT '年龄',
`salary` DECIMAL(10,2) COMMENT '工资(10位总长,2位小数)',
`created_time` DATETIME DEFAULT NOW() COMMENT '创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
- 删除表
sql
DROP TABLE users
- 查看建表语句
sql
SHOW CREATE TABLE users
- 增、删、改、查
sql
-- 插入
INSERT INTO users (name, age, salary) VALUES ('张三', 25, 8000.00);
-- 查询(最常用)
SELECT * FROM users WHERE age > 20 ORDER BY id DESC LIMIT 10;
-- 更新(务必加WHERE)
UPDATE users SET salary = 9000.00 WHERE id = 1;
-- 删除(务必加WHERE)
DELETE FROM users WHERE id = 1;
- join链接以及外键
sql
-- 用户表(users)
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
name VARCHAR(50) NOT NULL COMMENT '姓名',
age TINYINT UNSIGNED DEFAULT 0 COMMENT '年龄',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
-- 订单表(orders)
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
order_id VARCHAR(32) NOT NULL COMMENT '订单号(业务唯一)',
user_id INT NOT NULL COMMENT '用户ID',
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间(毫秒精度)',
order_json JSON DEFAULT NULL COMMENT '订单扩展信息(JSON格式)',
-- 索引
UNIQUE KEY uk_order_id (order_id),
KEY idx_user_id (user_id),
KEY idx_create_time_desc (create_time DESC),
-- 外键约束
CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单表';
-- 1. 插入用户
INSERT INTO users (name, age) VALUES ('张三', 25), ('李四', 30), ('王五', 28);
-- 2. 插入订单(user_id 必须存在于 users 表)
INSERT INTO orders (order_id, user_id, order_json) VALUES
('ORD20260724001', 1, '{"product":"手机","price":2999.00}'),
('ORD20260724002', 1, '{"product":"耳机","price":399.00}'),
('ORD20260724003', 2, '{"product":"电脑","price":5999.00}');
-- 3. 查询订单及用户信息(连表查询)
-- 内链接
INNER JOIN
-- 左链接
LEFT JOIN
-- 右链接
RIGHT JOIN
SELECT
o.order_id,
o.create_time,
u.name AS user_name,
u.age AS user_age,
o.order_json
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
ORDER BY o.create_time DESC;
五、索引
索引是帮助 MySQL 高效获取数据的数据结构,类似于书的目录。
核心作用:
- ✅ 大幅提升查询速度(从全表扫描 O(n) 变成 B+Tree 查询 O(log n))
- ❌ 占用额外存储空间
- ❌ 降低插入/更新/删除的速度(需要同步维护索引)
1. 按功能分类
| 索引类型 | 关键字 | 允许重复值 | 允许NULL | 每表数量 | 用途 |
|---|---|---|---|---|---|
| 普通索引 | INDEX / KEY | ✅ 允许 | ✅ 允许 | 多个 | 单纯加速查询 |
| 唯一索引 | UNIQUE INDEX | ❌ 不允许 | ✅ 允许(只能1个NULL) | 多个 | 加速 + 唯一约束 |
| 主键索引 | PRIMARY KEY | ❌ 不允许 | ❌ 不允许 | 1个 | 加速 + 唯一 + 非空 |
| 全文索引 | FULLTEXT INDEX | ✅ 允许 | ✅ 允许 | 多个 | 全文搜索(MyISAM/InnoDB 5.6+) |
| 空间索引 | SPATIAL INDEX | ❌ 不允许 | ❌ 不允许 | 多个 | GIS地理信息查询 |
2. 按字段数量分类
| 索引类型 | 说明 | 最左匹配 |
|---|---|---|
| 单列索引 | 只建在1个字段上 | 不存在 |
| 联合索引(复合索引) | 建在多个字段上,如 (age, salary) | ✅ 必须遵守 |
| 覆盖索引 | 查询的所有字段都在索引中,无需回表 | 不涉及 |
3. 按存储结构分类
| 索引类型 | 底层结构 | 特点 |
|---|---|---|
| B-Tree索引(默认) | B+Tree | 等值查询、范围查询(>、<、BETWEEN)都很快 |
| Hash索引(极少用) | 哈希表 | 等值查询极快,但不支持范围查询(Memory引擎专用) |
| R-Tree索引 | R-Tree | 空间索引(GIS地理信息) |
4.索引操作命令
sql
-- 建表时创建
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
age TINYINT,
salary DECIMAL(10,2),
email VARCHAR(100),
-- 普通索引
INDEX idx_name (name),
-- 唯一索引
UNIQUE INDEX idx_email (email),
-- 联合索引
INDEX idx_age_salary (age, salary)
);
-- 建表后添加(方式1)
CREATE INDEX idx_name ON users(name);
-- 建表后添加(方式2,更灵活)
ALTER TABLE users ADD INDEX idx_name (name);
-- 添加唯一索引
CREATE UNIQUE INDEX idx_email ON users(email);
ALTER TABLE users ADD UNIQUE INDEX idx_email (email);
-- 添加全文索引(MySQL 5.6+ InnoDB支持)
CREATE FULLTEXT INDEX idx_content ON articles(content);
ALTER TABLE articles ADD FULLTEXT INDEX idx_content (content);
-- 添加联合索引
CREATE INDEX idx_age_salary ON users(age, salary);
-- 添加前缀索引(针对长字符串,节省空间)
CREATE INDEX idx_name_prefix ON users(name(10)); -- 只取前10个字符
5.索引失效
索引失效 = MySQL 执行查询时,没有使用你创建的索引 ,而是进行全表扫描(type = ALL)。
场景1:LIKE 以 % 开头
错误写法(索引失效):
sql
SELECT * FROM users WHERE name LIKE '%张三%';
SELECT * FROM users WHERE name LIKE '%张三';
正确写法(索引生效):
sql
SELECT * FROM users WHERE name LIKE '张三%';
原因: B+Tree 索引从左到右匹配,% 开头无法确定起始位置,只能全表扫描。
场景2:类型隐式转换
错误写法(索引失效):
sql
-- phone 是 VARCHAR 类型
SELECT * FROM users WHERE phone = 13800138000;
正确写法(索引生效):
sql
SELECT * FROM users WHERE phone = '13800138000';
原因: 等价于 CAST(phone AS INT) = 13800138000,对索引列用了函数。
场景3:对索引列使用函数
错误写法(索引失效):
sql
SELECT * FROM users WHERE DATE(created_time) = '2026-07-24';
SELECT * FROM users WHERE YEAR(created_time) = 2026;
SELECT * FROM users WHERE UPPER(name) = 'ZHANG';
正确写法(索引生效):
sql
SELECT * FROM users WHERE created_time >= '2026-07-24' AND created_time < '2026-07-25';
SELECT * FROM users WHERE name = 'ZHANG';
原因: 对索引列使用函数,MySQL 无法利用索引的有序性。
场景4:OR 连接(必须所有字段都有索引)
错误写法(索引失效):
sql
-- age 有索引,但 name 没有索引
SELECT * FROM users WHERE age = 25 OR name = '张三';
正确写法(索引生效):
sql
-- 方案1:用 UNION
SELECT * FROM users WHERE age = 25 UNION SELECT * FROM users WHERE name = '张三';
-- 方案2:给 name 也建索引
CREATE INDEX idx_name ON users(name);
SELECT * FROM users WHERE age = 25 OR name = '张三';
原因: OR 条件中只要有 1 个字段没有索引,整个查询就走全表扫描。
场景5:联合索引违反最左匹配
错误写法(索引失效):
sql
-- 假设联合索引是 (age, salary, name)
-- 跳过了 age,索引失效
SELECT * FROM users WHERE salary = 8000;
SELECT * FROM users WHERE name = '张三';
SELECT * FROM users WHERE salary = 8000 AND name = '张三';
正确写法(索引生效):
sql
-- 从最左列 age 开始匹配
SELECT * FROM users WHERE age = 25;
SELECT * FROM users WHERE age = 25 AND salary = 8000;
SELECT * FROM users WHERE age = 25 AND salary = 8000 AND name = '张三';
原因: 联合索引必须从索引的最左列开始匹配,不能跳过。
场景6:范围查询右侧截断(联合索引)
错误写法(索引失效):
sql
-- 假设联合索引是 (age, salary, name)
-- age 等值 → salary 范围 → name 失效(范围查询右侧截断)
SELECT * FROM users WHERE age = 25 AND salary > 8000 AND name = '张三';
正确写法(索引生效):
sql
-- 调整索引顺序:等值查询放前面,范围查询放后面
CREATE INDEX idx_age_name_salary ON users(age, name, salary);
-- 三个字段都能用上
SELECT * FROM users WHERE age = 25 AND name = '张三' AND salary > 8000;
原因: 联合索引遇到 >、<、BETWEEN、LIKE 后,右侧的索引列会失效。
场景7:使用 != 或 <>
错误写法(索引失效):
sql
SELECT * FROM users WHERE age != 25;
SELECT * FROM users WHERE age <> 25;
正确写法(索引生效):
sql
SELECT * FROM users WHERE age < 25 OR age > 25;
原因: != 和 <> 无法利用索引的有序性,大多数情况全表扫描。
场景8:使用 NOT IN
错误写法(索引失效):
sql
SELECT * FROM users WHERE age NOT IN (25, 30, 35);
正确写法(索引生效):
sql
-- 方案1:用 NOT EXISTS
SELECT * FROM users u1
WHERE NOT EXISTS (
SELECT 1 FROM users u2
WHERE u2.age IN (25, 30, 35) AND u2.id = u1.id
);
-- 方案2:用 LEFT JOIN + IS NULL
SELECT u1.* FROM users u1
LEFT JOIN users u2 ON u1.id = u2.id AND u2.age IN (25, 30, 35)
WHERE u2.id IS NULL;
原因: NOT IN 无法走索引,改为 NOT EXISTS 或 LEFT JOIN。
场景9:使用 IS NULL 或 IS NOT NULL(部分情况失效)
错误写法(可能失效):
sql
SELECT * FROM users WHERE age IS NULL; SELECT * FROM users WHERE age IS NOT NULL;
正确写法(索引生效):
sql
-- 优化方案:字段设置 NOT NULL + DEFAULT 默认值
ALTER TABLE users MODIFY age TINYINT NOT NULL DEFAULT 0;
-- 然后查询默认值替代 IS NULL
SELECT * FROM users WHERE age = 0;
原因: NULL 值在索引中存储方式特殊,大范围 NULL 查询效率低。
场景10:隐式计算
错误写法(索引失效):
sql
SELECT * FROM users WHERE id + 1 = 10;
SELECT * FROM users WHERE age * 2 > 50;
正确写法(索引生效):
sql
SELECT * FROM users WHERE id = 9;
SELECT * FROM users WHERE age > 25;
原因: 对索引列做计算,MySQL 无法使用索引。
场景11:ORDER BY 顺序与索引不一致
错误写法(索引失效):
sql
-- 假设索引是 (age, salary)
-- 排序顺序不对
SELECT * FROM users ORDER BY salary, age;
-- 升降序不一致
SELECT * FROM users ORDER BY age ASC, salary DESC;
正确写法(索引生效):
sql
-- 顺序一致
SELECT * FROM users ORDER BY age, salary;
-- 升降序一致
SELECT * FROM users ORDER BY age ASC, salary ASC;
原因: ORDER BY 必须和索引顺序、升降序完全一致才能走索引。
场景12:数据量太小
错误写法(索引失效):
sql
-- 表只有 10 条数据,MySQL 认为全表扫描更快
SELECT * FROM users WHERE age = 25;
正确写法(索引生效):
sql
-- 数据量小不需要优化,MySQL 优化器会自动选择最优方案 -- 数据量变大后自然会走索引
原因: 小表全表扫描比走索引更快(索引需要额外 IO 成本)。