MySQL 数据类型、约束与数据库对象校招面试题
MySQL 数据类型、约束与数据库对象校招面试题
类型决定数据如何表达,约束决定什么数据允许进入系统。先把这些规则设计正确,再讨论索引与性能,否则快速查到错误数据仍然没有意义。
本文示例数据
本章以 MySQL 8.4、InnoDB 和 utf8mb4 为主要环境。以下 SQL 面向新建的独立练习库,不连接或修改你的现有业务数据库。若 mysql_chapter2_demo 已存在,请停止初始化并换一个新库名,不能忽略创建失败后继续插入。以下结果均为根据样本推演的预期结果,未在数据库中实测。
CREATE DATABASE mysql_chapter2_demo CHARACTER SET utf8mb4;
USE mysql_chapter2_demo;
CREATE TABLE users (
id BIGINT PRIMARY KEY,
name VARCHAR(50) NOT NULL
) ENGINE=InnoDB;
CREATE TABLE products (
id BIGINT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
price DECIMAL(10,2) NOT NULL,
stock INT NOT NULL
) ENGINE=InnoDB;
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
status VARCHAR(20) NOT NULL,
amount DECIMAL(12,2) NOT NULL,
created_at DATETIME NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB;
CREATE TABLE order_items (
order_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
quantity INT NOT NULL,
PRIMARY KEY (order_id, product_id),
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (product_id) REFERENCES products(id)
) ENGINE=InnoDB;
CREATE TABLE accounts (
id BIGINT PRIMARY KEY,
balance DECIMAL(12,2) NOT NULL
) ENGINE=InnoDB;
INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob'), (3, 'Carol');
INSERT INTO products VALUES (1, 'Book', 19.90, 10), (2, 'Pen', 2.00, 10);
INSERT INTO orders VALUES
(101, 1, 'paid', 100.00, '2026-01-01 10:00:00'),
(102, 1, 'pending', 200.00, '2026-01-02 10:00:00'),
(103, 1, 'paid', 50.00, '2026-01-03 10:00:00'),
(104, 2, 'paid', 80.00, '2026-01-03 10:00:00');
INSERT INTO order_items VALUES (101, 1, 2), (101, 2, 1), (104, 2, 1);
INSERT INTO accounts VALUES (1, 1000.00), (2, 1000.00);
订单明细只用于关系教学,并非订单总金额的完整结算依据。独立 demo_* 表由对应题目创建,不重复执行同名建表。涉及状态改变的实验在结束后恢复;不要把前一题的修改作为后一题初始值。所有 DDL 都只作用于此练习库,不能依赖普通回滚撤销。
1. MySQL 有哪些字符串类型?CHAR 和 VARCHAR 怎么选?
相关问法:字符串类型有哪些?定长与变长有什么区别?|示例依赖:本题独立 demo_strings 表。
一句话理解与用途
字符串类型首先分为“有字符集语义的文字”和“按字节处理的数据”,再按长度和用途细分。CHAR 适合长度相对稳定的文字,VARCHAR 适合长度变化的文字,选择需要结合真实数据而不是只背定长和变长。
| 类别 | 类型 | 例子 |
|---|---|---|
| 字符字符串 | CHAR、VARCHAR | 国家代码、姓名 |
| 二进制字符串 | BINARY、VARBINARY | 二进制摘要、协议字段 |
| 大对象 | TEXT 系列、BLOB 系列 | 长正文、二进制内容 |
| 枚举和集合 | ENUM、SET | 取值集合受控的字段 |
ENUM 表示从定义列表中选一个值,SET 表示选择若干成员。它们不是“所有字符串只有六种”的依据,业务状态还要考虑扩展、迁移和约束维护成本。
字符数不等于字节数
VARCHAR(20) 的 20 指最多 20 个字符,而不是 20 字节。utf8mb4 中不同字符占用的字节不同;一个中文字符通常占 3 字节,某些字符占 4 字节。VARCHAR 还需要长度信息,整行长度、索引键长都会进一步限制可用定义。
CREATE TABLE demo_strings (
code CHAR(2),
nickname VARCHAR(20)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO demo_strings VALUES ('CN', '小明');
SELECT code, nickname,
CHAR_LENGTH(nickname) AS chars,
OCTET_LENGTH(nickname) AS bytes
FROM demo_strings;
结果中的 chars 为 2、bytes 为 6。这比背“VARCHAR 比 CHAR 省空间”更重要:存储空间取决于编码、实际值和行格式。
把“小明”换成 Alice,同样查询会得到 5 个字符、5 个字节。原因是本例两个汉字各用 3 字节,而这些英文字母各用 1 字节。CHAR_LENGTH 回答“有几个字符”,LENGTH/OCTET_LENGTH 回答“编码后有多少字节”;这两个问题在 utf8mb4 下不必得到同一数字。
定义长度还不是整行占用空间。例如 utf8mb4 的 VARCHAR(20) 最多允许 20 个字符,但需要为编码字节和长度信息留空间。若把说明文字、多个长字段都放入同一行,还要受总行长约束;若建立索引,则还要看索引键的字节限制。因此类型长度应从业务允许的内容出发,而不是认为“20 就占 20 字节”。
配图:字符个数不等于字节数

对照字符格和字节格,区分声明长度与编码后的实际长度。
定长与变长究竟意味着什么
CHAR 在 SQL 类型语义上按定义宽度处理并涉及空格补齐,读取时通常去掉尾随填充空格;具体行为还与版本和模式有关。VARCHAR 保存可变长度内容,保留存入的尾随空格。二者比较时是否忽略尾随空格,又受排序规则的 PAD SPACE/NO PAD 属性影响,不能从“取出显示几个空格”直接推导相等规则。
InnoDB 的实际物理编码会受字符集、行格式影响,不应把 CHAR(10) 说成在磁盘上永远固定占 10 字节。BINARY 的固定长度填充规则也不同于 CHAR,它处理的是字节。
怎样选择
国家代码固定两个字母,可用 CHAR(2);用户名长度差异较大,可用 VARCHAR。长文章通常用 TEXT。不会因为使用 CHAR 就天然更快,也不应为了“节省空间”把所有列换成 VARCHAR。字段上限应来自业务需求,还要给索引、网络传输和约束留出合理设计空间。官方:CHAR 与 VARCHAR
面试回答
MySQL 字符串包括 CHAR/VARCHAR、BINARY/VARBINARY、TEXT/BLOB 以及 ENUM/SET 等。CHAR 与 VARCHAR 分别表达定长和变长文字,但定义长度通常是字符数,物理字节还受字符集和行格式影响。固定代码适合 CHAR,变化较大的姓名适合 VARCHAR;尾随空格的保存、读取和比较要区分,不应绝对认为 CHAR 更快或 VARCHAR 永远更省。
2. FLOAT、DOUBLE 和 DECIMAL 有什么区别?金额用什么存?
相关问法:浮点数精度、金额类型。|示例依赖:products;计算例子不依赖表。
概念与问题
FLOAT、DOUBLE 保存二进制近似值,DECIMAL 保存有界精度的十进制精确值。测量温度允许小误差,结算金额则需要明确的分位和舍入规则,两类需求不应使用同一种选型理由。
FLOAT 通常占 4 字节,有效十进制数字大约 7 位;DOUBLE 通常占 8 字节,大约 15~16 位。有效数字不是小数位数,也不是“FLOAT 固定保留八位小数”。许多十进制小数不能用有限二进制位准确表示,连续计算和比较可能暴露误差。
SELECT CAST('0.10' AS DECIMAL(10,2))
+ CAST('0.20' AS DECIMAL(10,2)) AS total;
SELECT id, price FROM products ORDER BY id;
第一条得到十进制 0.30。DECIMAL(10,2) 总共最多 10 位,其中小数 2 位,整数部分最多 8 位;这是范围与精度约束,不是无限精度计算。
二进制浮点的问题不在于“它不会加法”,而在于输入已经可能是近似值。类似十进制无法用有限位写完 1/3,二进制也无法用有限位准确写完某些十进制小数;DOUBLE 提供更多有效位,但没有消除这种表达方式的限制。MySQL 的普通 0.1 字面量属于精确值语义,不能拿未声明类型的 SELECT 0.1 + 0.2 去证明 FLOAT 的误差。官方:精确值与近似值
精确保存不等于结算规则自动正确。 以单价 19.90、两件商品、假设税率 6% 为例:先算整笔税后金额,再保留两位是 ROUND(19.90 * 2 * 1.06, 2),预期 42.19;若逐件先舍入再相加,则 ROUND(19.90 * 1.06, 2) * 2 预期 42.18。两者都能使用精确十进制,却回答了不同的业务规则。需要明确在哪一步舍入,而不是只换列类型。官方:DECIMAL
配图:金额为什么常用 DECIMAL

对照近似表示与精确十进制,检查精度、范围和舍入边界。
适用边界
金额常用 DECIMAL,或在明确币种最小单位后使用整数。两者都要考虑税率计算、中间精度、溢出和舍入位置。客户端若先把金额转成二进制浮点再传入,误差可能已经发生,所以应用与数据库都应一致使用十进制金额语义。超出精度的输入处理还与 SQL 模式相关。
面试回答
FLOAT 和 DOUBLE 都是近似数值,通常分别占 4、8 字节,DOUBLE 精度和范围更大;DECIMAL 按指定十进制位数保存精确值。金额一般使用 DECIMAL 或明确最小单位的整数,同时规定舍入和范围。有效数字不等于小数位数,DECIMAL 也不是无限精度,客户端计算方式同样需要匹配。
3. BLOB 和 TEXT 有什么区别?图片应如何保存?
相关问法:大对象、图片入库。|示例依赖:本题独立 demo_content 表。
概念与用途
BLOB 表示二进制大对象,按字节处理;TEXT 表示文本大对象,带有字符集和排序规则。二者都有 TINY、普通、MEDIUM、LONG 等容量层级,容量应按字节和具体类型理解。
CREATE TABLE demo_content (
id INT PRIMARY KEY,
body TEXT,
payload BLOB
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO demo_content VALUES (1, '商品说明', X'010203');
SELECT body, HEX(payload) FROM demo_content WHERE id = 1;
查询返回商品说明和 010203。正文需要正确解码和字符比较;payload 的三个字节不要求能解释成文字。
可再查询 OCTET_LENGTH(payload) 与 CHAR_LENGTH(HEX(payload)):预期分别为 3 和 6,因为每个原始字节用两个十六进制字符显示。显示形式不是内容本身;将文件转换为 Base64 再存入 TEXT,是另一种文本编码方案,会改变体积和访问方式,不能直接称为原始 BLOB。
普通 TEXT/BLOB 的容量上限为 65,535 字节;TINY、MEDIUM、LONG 系列提供不同上限。TEXT 能放多少字符还取决于编码,不能把字节上限等同汉字数量。大字段还可能增加传输和处理成本,即使内容可放在页外,也不等于查询 SELECT * 时无需获取它。官方:BLOB 与 TEXT
图片保存的两种方式
小体量、希望与业务记录统一事务管理的二进制内容,可以存入 BLOB。它使备份和访问路径集中,但会增加数据库体积、复制流量和读取压力。
大量图片通常放对象存储,数据库保存对象标识、大小和校验信息,便于使用独立带宽或 CDN。代价是上传对象与写数据库不能天然形成一个本地事务,需要处理上传成功但记录失败、删除后的孤立对象等情况。
具体可以先上传对象,取得稳定 object_key,再在数据库事务内保存业务记录与元数据。如果后一步失败,对象不会随 MySQL 的 ROLLBACK 消失,应设计清理任务或补偿。读取时应用通过记录中的 key 构造授权访问方式,不一定保存会过期的签名 URL。这样明确了两系统各自负责什么,也说明为什么“把链接存进去”并没有自动解决一致性。
配图:文本、字节与图片存放

区分文本语义、原始字节和外部图片对象的存储边界。
容易混淆的地方
TEXT 不是“不区分大小写的 BLOB”:文字是否区分大小写取决于排序规则,二进制则没有同样的字符大小写语义。即使使用二进制排序规则的 TEXT,仍然属于带字符集语义的文本,不能据此把它直接等同于任意字节容器。
面试回答
BLOB 保存字节,TEXT 保存有字符集与排序规则语义的文本,二者都有不同容量级别。图片可以存 BLOB,也可以存对象存储、数据库只保存元数据。前者便于统一事务管理,后者便于分担容量与访问负载,但需要处理跨系统一致性;不能仅凭大小写行为定义二者,也不能一律禁止图片入库。
4. DATE、DATETIME 和 TIMESTAMP 怎么选?如何写入日期?
相关问法:插入日期、自动时间字段、TIMESTAMP 是什么?|示例依赖:本题独立 demo_times 表。
一句话理解与用途
DATE 表示日期,DATETIME 表示年月日时分秒的日历值,TIMESTAMP 表示会按照会话时区转换的时间点。生日、门店约定营业时刻、服务器事件发生时刻表达的含义不同,不能只按字段名称选择。
MySQL 8.0/8.4 中,DATE 的正常范围为 1000-01-01~9999-12-31;DATETIME 正常范围覆盖这些年份的时间;TIMESTAMP 以 UTC 表示的范围约为 1970-01-01 00:00:01~2038-01-19 03:14:07。DATETIME、TIMESTAMP 可指定 0~6 位小数秒。特殊零日期是否合法还受 SQL 模式影响,业务示例不用零日期。官方:日期时间类型
用时区变化理解区别
CREATE TABLE demo_times (
id INT PRIMARY KEY,
birthday DATE,
local_time DATETIME,
event_time TIMESTAMP
) ENGINE=InnoDB;
SET @demo_previous_time_zone = @@session.time_zone;
SET time_zone = '+00:00';
INSERT INTO demo_times VALUES
(1, '2000-01-02', '2026-01-01 10:00:00', '2026-01-01 10:00:00');
SET time_zone = '+08:00';
SELECT birthday, local_time, event_time FROM demo_times;
SET time_zone = @demo_previous_time_zone;
在 +08:00 查询,local_time 仍为 10:00,event_time 显示 18:00。发生时刻没变,只是 TIMESTAMP 在入库时从会话时区转为 UTC、读取时再转为会话时区;DATETIME 不自动做同样转换。固定偏移演示不需要安装命名时区表。
| 列 | 在 +00:00 写入 | 在 +08:00 读取 |
|---|---|---|
| birthday | 2000-01-02 | 2000-01-02 |
| local_time | 2026-01-01 10:00:00 | 2026-01-01 10:00:00 |
| event_time | 2026-01-01 10:00:00 | 2026-01-01 18:00:00 |
因此“把 DATETIME 当作 UTC 存储”是一项应用约定,数据库不会因为列名带 time 就自动帮你转换;而 TIMESTAMP 的会话显示也不是把事件真实发生时刻推迟了八小时。示例末尾恢复原会话时区,避免后续 SQL 受实验设置影响。
配图:DATE、DATETIME、TIMESTAMP 与时区

同一输入换会话时区读取,只有 TIMESTAMP 的显示进行转换。
自动初始化和更新要明确声明
可定义 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,或为更新时间增加 ON UPDATE CURRENT_TIMESTAMP。自动更新在其他列实际改变等满足规则的场景发生,不等于“任意执行 UPDATE 就一定刷新”,也不等于所有 TIMESTAMP 都自动有该能力。显式声明能避免依赖旧配置行为。
怎样选择
生日用 DATE;需要宽时间范围或按约定时区保存日历值时可选 DATETIME;需要会话时区转换且范围满足需求时可选 TIMESTAMP。使用 DATETIME 保存 UTC 也是可行约定,但转换责任在应用或 SQL 中,要明确记录这一约定。
TIMESTAMP 类型不是整数 Unix 时间戳,若 API 接收秒数要先明确单位、时区及转换函数。日期写入优先使用参数绑定和标准格式;参数占位符用于客户端驱动,不把未知字符串拼进 SQL。
生日只关心日期,不应因为浏览者时区不同变成前一天;跨时区交易事件则需要保持同一发生时刻。预约未来某个地区的日历时间还可能受夏令时规则影响,通常需要同时记录业务时区,不能只保存不带说明的字符串。
面试回答
DATE 保存日期,DATETIME 保存不自动转换时区的日期时间,TIMESTAMP 在存取时按会话时区与 UTC 转换,且在 MySQL 8.0/8.4 中范围受 2038 年边界限制。自动创建或更新时间应通过 DEFAULT、ON UPDATE 明确声明。选型要先确定业务表达的是日历值还是时间点,以及范围和精度;TIMESTAMP 不是普通整数时间戳。
5. 主键、候选键和唯一约束有什么区别?如何修改主键?
相关问法:主键是什么、候选键、删除主键。|示例依赖:users;结构实验使用独立 demo_keys。
一句话理解与用途
候选键是能唯一标识一行的最小属性集合,主键是从候选键中选作主要标识的一组列,唯一约束则让数据库检查某些列组合是否重复。它们把“不会混淆两个业务对象”的要求变成可检查的规则。
用户可以同时有内部 id 和业务 user_no。如果二者都非空且唯一,都可以用于区分用户,属于候选键;我们选择 id 作为主键。所谓“最小”指去掉集合中的任何一列就不能再保证唯一,不是指字符串字数最短。
一列和一组列都可以是主键
订单明细可以用 (order_id, product_id) 标识某订单中的一个商品。它是一个由两列组成的主键,不是两个主键。一个表最多一个 PRIMARY KEY,但可以有多个 UNIQUE 约束。
MySQL 主键列不能为 NULL。NULL 表示缺失或未知,空字符串则是一个确定的字符串值,两者不能混称“空”。MySQL 唯一索引允许多个 NULL,因此单独 UNIQUE(email) 不足以表达严格非空候选键,需要结合业务是否允许缺失决定是否加 NOT NULL。
CREATE TABLE demo_nullable_emails (
id BIGINT PRIMARY KEY,
email VARCHAR(100) NULL,
UNIQUE KEY uk_email (email)
) ENGINE=InnoDB;
INSERT INTO demo_nullable_emails VALUES (1, NULL), (2, NULL);
SELECT COUNT(*) AS rows_with_missing_email
FROM demo_nullable_emails WHERE email IS NULL;
预期为 2,而不是第二条插入被“重复空值”拒绝。若要求所有用户都用 email 唯一识别,必须另外要求它非空并符合业务语义。同理,复合主键 (order_id, product_id) 拒绝第二条 (101,1),但允许 (102,1):比较的是整组值,不是禁止商品 1 出现在不同订单里。官方:唯一索引
配图:候选键、复合主键与 UNIQUE

对照候选键、复合主键和可空唯一列,检查约束作用于什么值。
修改主键的最小实验
CREATE TABLE demo_keys (
id BIGINT NOT NULL,
external_no VARCHAR(20) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_external_no (external_no)
) ENGINE=InnoDB;
INSERT INTO demo_keys VALUES (1, 'U001'), (2, 'U002');
ALTER TABLE demo_keys DROP PRIMARY KEY;
ALTER TABLE demo_keys ADD PRIMARY KEY (id);
实验选择没有外键、没有 AUTO_INCREMENT 的小表,避免把额外依赖隐藏起来。删除主键不会删除两条记录,也不会让 external_no 的唯一约束消失。重新建立主键要求列中数据满足非空唯一性。
为什么修改主键要关注成本
InnoDB 使用聚簇组织,主键通常就是聚簇键,二级索引又携带主键定位行。因此主键变更可能重建表并影响二级索引,不是简单改一行元数据。若主键被外键引用,需要先处理引用依赖;若列有自增属性,也必须满足引擎对索引的要求。启用了要求主键等配置时,本题无主键中间状态也可能被拒绝。
唯一约束只能保证对应的业务键不重复,并不自动让所有业务操作幂等。重复扣款仍需要事务与请求编号,见第 4 题。
面试回答
候选键是能够唯一标识记录的最小列集合,主键是被选中的候选键,一表只有一个主键但可由多列组成。唯一约束可以有多个,在 MySQL 中通常允许多个 NULL,因此严格候选键还需非空条件。删除主键使用 ALTER TABLE ... DROP PRIMARY KEY,但 InnoDB 的聚簇结构、外键和自增列可能使它受限或需要表重建。
6. 一对一、一对多、多对多关系如何设计?
相关问法:MySQL 中表关系有哪些?|示例依赖:users、orders、order_items、products。
概念与用途
表关系描述业务对象之间的对应数量,帮助减少重复数据并维护关联。一对多不是 JOIN 语法名称,而是用户可以拥有多笔订单这样的业务事实。
- 一对一:用户与扩展资料。资料表的 user_id 既是外键,又是主键或唯一列,限制一个用户最多对应一条资料。
- 一对多:用户与订单。orders.user_id 引用 users.id,但不加单列唯一,否则一个用户只能下一单。
- 多对多:订单与商品。一笔订单有多个商品,一个商品出现在多笔订单,用 order_items 连接。
CREATE TABLE demo_profiles (
user_id BIGINT PRIMARY KEY,
bio VARCHAR(200),
FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB;
INSERT INTO demo_profiles VALUES (1, 'Alice profile');
SELECT o.id, p.name, i.quantity
FROM orders AS o
JOIN order_items AS i ON i.order_id = o.id
JOIN products AS p ON p.id = i.product_id
ORDER BY o.id, p.id;
初始明细是订单 101 的商品 1、数量 2;订单 101 的商品 2、数量 1;订单 104 的商品 2、数量 1。上述 JOIN 的预期结果为:
| 订单 id | 商品 name | quantity |
|---|---|---|
| 101 | Book | 2 |
| 101 | Pen | 1 |
| 104 | Pen | 1 |
一笔订单 101 有两种商品,而 Pen 又属于两笔订单,这才直观地显示了多对多。若把商品列表写成订单表中的逗号字符串,查询数量、检查商品存在、更新单项都会变得困难;中间表把每条关系变成可以约束和查询的记录。联合主键约束本例同一商品在订单中出现一次;若允许不同批次拆成多行,应重新设计明细身份。
配图:三种表关系如何落地

通过三条明细展示真实多对多,并把关联线连到实际外键列。
边界
外键检查引用对象是否存在,JOIN 负责查询时组合数据,两者不互相替代。资料表的唯一外键只保证“最多一条”,并不强制每位用户一定已经创建资料;若业务要求必有资料,还需要创建流程或进一步设计。
本章确实在建表时定义了外键,因此不存在的用户不能直接成为订单的 user_id。不指定级联规则时,删除仍被订单引用的父用户可能被拒绝;这保护的是引用完整性,不代表数据库自动懂得“用户注销应保留什么”。是否级联、限制删除或改为软删除,需要按业务生命周期选择。
面试回答
一对一常用外键加唯一约束,一对多把外键放在多的一侧,多对多通过中间表保存关联及数量等属性。外键维护引用完整性,JOIN 在查询时组合数据。关系设计要反映业务允许的数量,再决定唯一约束和删除规则,不能仅凭列名相同认为关系已经被数据库保护。
7. 什么是视图?它会保存查询结果吗?
相关问法:创建视图、视图与表区别。|示例依赖:orders。
概念与用途
普通视图是保存下来的查询定义,可以像表一样被查询。它用于复用查询逻辑或提供有限的数据接口,不会自动保存一份长期缓存的结果。
CREATE VIEW paid_order_summary AS
SELECT user_id, COUNT(*) AS order_count, SUM(amount) AS total
FROM orders WHERE status = 'paid'
GROUP BY user_id;
SELECT * FROM paid_order_summary ORDER BY user_id;
初始结果是用户 1:2 单、150;用户 2:1 单、80。后续底层已支付订单改变,新的查询按其事务可见性重新得到相应结果,不需要手工同步一张结果表。
定义没改,为什么结果会变? 视图相当于为上述查询起了名字;读取它时仍要计算当前读取规则下的底层记录,而不是拿创建那一刻的截图。下面在本练习库中将 102 改为 paid 并提交,随后新读取视图,再恢复初始状态:
START TRANSACTION;
UPDATE orders SET status = 'paid' WHERE id = 102;
COMMIT;
SELECT * FROM paid_order_summary ORDER BY user_id;
START TRANSACTION;
UPDATE orders SET status = 'pending' WHERE id = 102;
COMMIT;
中间查询的预期结果是用户 1:3 单、350;用户 2:1 单、80,因为新增参与聚合的金额是 200。这里特意使用提交后的新读取;若在 RR 的旧读视图中观察,还要按其事务可见性分析,不能把任何查询时点都写成“立即看到全局最新”。
原理与限制
MySQL 执行时可以合并视图查询或物化内部临时结果,具体由定义和优化决定;这不意味着视图本身是长期物化视图。上面的聚合视图不能当作普通表逐行更新。部分简单视图可更新,是否允许还需检查查询结构。
比如对汇总行 total=350 执行更新,数据库无法仅凭这个数字知道应该改哪笔订单、改多少,所以本例聚合视图不可更新。普通视图主要减少重复查询逻辑,而不是替查询免去扫描和聚合成本。官方:视图可更新条件
视图能只暴露需要的列,但权限还取决于 SQL SECURITY、定义者或调用者的权限和授权方式,不能仅创建视图就声称底层表已经隔离。
配图:普通视图保存的是查询定义

对照两次查询的结果,说明保存定义不等于保存结果截图。
面试回答
视图保存查询定义,用来复用逻辑和构造受控的数据接口,普通视图不单独长期存储查询结果。查询时仍会访问底层对象或执行相应计划,所以不保证更快。简单视图可能可更新,聚合、分组等视图通常有限制,权限行为也要结合 SQL SECURITY 与授权配置。
8. 什么是触发器?它适合解决什么问题?
相关问法:六种触发器、自动审计。|示例依赖:accounts;另建 demo_balance_audit。
概念与用途
触发器是在指定表发生 INSERT、UPDATE、DELETE 时自动执行的数据库逻辑。它能让多个写入入口遵守某些共同规则,代价是修改一行还会隐式执行额外操作。
BEFORE/AFTER 与三类事件组成六种时机组合。MySQL 按行触发:UPDATE 影响一百行,相应行触发逻辑也会执行多次。
CREATE TABLE demo_balance_audit (
account_id BIGINT,
old_balance DECIMAL(12,2),
new_balance DECIMAL(12,2)
) ENGINE=InnoDB;
CREATE TRIGGER demo_account_after_update
AFTER UPDATE ON accounts FOR EACH ROW
INSERT INTO demo_balance_audit
VALUES (OLD.id, OLD.balance, NEW.balance);
START TRANSACTION;
UPDATE accounts SET balance = balance - 10 WHERE id = 1;
SELECT * FROM demo_balance_audit;
ROLLBACK;
DROP TRIGGER demo_account_after_update;
初始余额 1000 时,事务内看到审计行 1、1000、990。两个表都用 InnoDB,因此回滚后余额及该审计写入均撤销。本例触发器只有一条语句,不需要客户端 DELIMITER 指令。
动作顺序是:客户端执行 UPDATE;引擎修改该行;AFTER UPDATE 触发器取得 OLD/NEW 并写审计;语句成功返回,但显式事务还没有提交。此时本事务能看到余额 990 和自己的审计写入,并不代表其他事务能读到这些未提交值。ROLLBACK 后重新查询 accounts 应为 1000,本次审计行不存在。若改为 COMMIT,两者一起保留,不能把 AFTER 理解成“提交成功后才做审计”。
配图:触发器与审计同在事务中

将余额修改和审计写入放在同一事务内,比较提交和回滚。
边界
INSERT 有 NEW,DELETE 有 OLD,UPDATE 同时有两者。六种组合不等于每表最多只能有六个触发器;同一时机事件可以定义多个。BEFORE 可以在允许的操作中校验或调整新值,AFTER 面对的是该行操作完成后的状态,但不代表整个事务已经提交。
若触发器写审计表失败,原语句也可能失败;排查一条 UPDATE 的性能和错误时,就不能只查看应用发来的 SQL。要保留“失败尝试也不可撤销”的审计,需要另行设计,不能依赖本例会一起回滚的记录。
在本例事务型表条件下,触发器错误会使触发它的整个语句失败,其语句内修改撤销;但该事务之前其他成功语句是否撤销,不能直接类推。应用仍需明确处理失败并决定是否结束整个事务。触发器内不能用 COMMIT 把审计变成独立提交;它也不适合承担耗时的外部调用。官方:触发器执行和错误处理
面试回答
触发器是在表发生指定增删改时逐行执行的数据库逻辑,常用于短小的规则补充和审计。BEFORE/AFTER 与三类事件形成六种组合,同一组合可有多个触发器。它会增加隐式执行、锁和排错成本,不是独立后台任务;使用 InnoDB 表时,审计写入也可能随业务事务回滚。
9. MEMORY 表是什么?MyISAM 静态表与动态表有什么区别?
相关问法:堆表、HEAP、旧引擎行格式。|示例依赖:概念示例,不运行额外表。
概念与用途
MEMORY 是把表数据放在内存中的引擎,HEAP 是历史别名;重启后用户 MEMORY 表定义保留,但行数据丢失。它适合可以重新构建的临时数据,不应保存不可丢失的订单。
用户 MEMORY 表支持 HASH 和 BTREE 索引,支持 AUTO_INCREMENT 和可空索引列,但不支持 BLOB/TEXT。不要把 Hash 不支持范围定位误解成表不能执行大小比较。MySQL 内部临时表还可能使用 TempTable 等机制,不能与用户 MEMORY 表混称。
MyISAM 静态行格式侧重定长记录,定位和恢复相对简单但可能浪费空间;动态格式处理变长内容更节省空间,更新后更容易产生碎片。动态表不是“所有列都必须变长”。
不要把两个问题混成“内存里的动态表”。 MEMORY 回答数据放在哪里,MyISAM 的静态/动态格式回答该引擎如何组织一条记录。MEMORY 本身使用固定长度行存储,VARCHAR 等逻辑变长类型也按其行格式处理;不能因为某列叫 VARCHAR 就把 MEMORY 画成 MyISAM 的动态记录。官方:MEMORY
对于 MyISAM 静态记录,固定跨度使定位方式较直接;动态记录的长度变化能减少部分空闲,但变长更新可能需要重用空位或拆分记录,增加维护成本。这是空间、访问与更新模式的取舍,不能用“动态一定更快”概括。用户 MEMORY 表在重启后还保留定义,只是行要重建,因此也不等同于连接结束即消失的 TEMPORARY 表。本题只讲机制,不要求重启服务器做实验。
配图:MEMORY 与 MyISAM 行格式:不是同一维度

分别说明数据存放位置和记录组织方式,避免混淆分类维度。
面试回答
MEMORY/HEAP 是 MySQL 内存表引擎,重启丢数据但保留定义,可用于可重建数据,支持 Hash/BTREE 而不支持 BLOB/TEXT。MyISAM 静态与动态格式主要是定长和变长记录的取舍。这些属于低频引擎知识,不能与 InnoDB 或其他数据库中的堆表概念混淆。
阅读导航




