触发器无法直接获取文件大小,必须由应用层显式传入file_size_bytes字段并设为not null,再通过before insert触发器校验;若该字段为空或超限则报错拦截。

触发器里没法直接获取文件大小
SQL 触发器运行在数据库层面,它只能看到 INSERT 或 UPDATE 语句提交的字段值,看不到客户端上传的原始文件、更无法调用操作系统 API 或读取二进制流。所以 附件大小 必须作为普通字段(比如 file_size_bytes)显式传入,否则触发器无从校验。
常见错误现象:INSERT INTO attachments (file_name, file_data) VALUES ('a.pdf', ?) 却没带 file_size_bytes 字段,触发器读到 NULL 或默认值,校验失效。
实操建议:
- 应用层上传时必须计算并显式写入
file_size_bytes(例如 Python 用len(file.read()),Node.js 用buffer.length) - 数据库表结构中该字段设为
NOT NULL,避免空值绕过检查 - 若历史数据可能为空,触发器需先
IF NEW.file_size_bytes IS NULL THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'file_size_bytes is required';
BEFORE INSERT 触发器做大小拦截最可靠
必须用 BEFORE INSERT(或 BEFORE UPDATE),不能用 AFTER——后者已写入数据,报错也来不及回滚,还可能触发下游逻辑。
示例(MySQL):
CREATE TRIGGER check_attachment_size_before_insert
BEFORE INSERT ON attachments
FOR EACH ROW
BEGIN
IF NEW.file_size_bytes > 10485760 THEN -- 10MB
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'File size exceeds 10MB limit';
END IF;
END;
注意点:
-
SIGNAL是 MySQL 5.5+ 的标准报错方式,PostgreSQL 用RAISE EXCEPTION,SQL Server 用THROW - 硬编码阈值不灵活,可改查配置表:
SELECT max_size FROM system_config WHERE key = 'max_attachment_size',但要注意性能和事务可见性 - 别用
SELECT ... INTO @var再判断,容易因变量作用域出错;直接IF (SELECT ...)更稳妥
元数据表字段设计影响触发器健壮性
如果元数据表(如 attachments)只存路径(file_path),而文件实际存在磁盘或对象存储,那触发器根本没法验证真实大小——它只能校验你“声称”的大小。
所以字段设计要明确责任边界:
- 推荐方案:表中必须含
file_size_bytes(应用保证准确),触发器只校验该字段数值 - 不推荐方案:只存
file_path,触发器试图用LOAD_FILE()读取并LENGTH(),这在多数生产环境被禁用(安全限制 + 权限不足 + 路径不可达) - 若必须校验真实文件,应由应用层上传时计算并校验,数据库只做二次确认(即仍依赖
file_size_bytes字段)
触发器不解决前端绕过或权限滥用问题
触发器只是最后一道数据库防线,它拦不住以下情况:
- 应用层故意伪造
file_size_bytes值(比如传 100 但实际上传 1GB 文件) - DBA 或高权限账号直连数据库执行
INSERT绕过校验 - 通过
mysqldump/pg_restore批量导入跳过触发器
所以真正落地时,file_size_bytes 的可信度取决于应用层是否严格生成、网络传输是否防篡改(如签名校验)、以及数据库账号权限是否最小化(普通应用账号不应有 TRIGGER 或 ALTER 权限)。
最容易被忽略的是:触发器对批量插入(INSERT ... SELECT 或 LOAD DATA)同样生效,但如果应用把多个小文件拼成一个大 BLOB 存进单字段,而元数据表没按文件粒度拆分,那整个校验逻辑就失效了——得先确认你的“附件”在数据库里是不是真的一行一条。










