当前位置:首页 > 休闲 > mysql把查询结果存入新建一张表 正文

mysql把查询结果存入新建一张表

来源:烟消火灭网   作者:热点   时间:2026-10-17 05:52:42

上周帮同事排查一个报表问题,查询存入他写了条三表 JOIN 的结果 SQL,跑一次要四十多秒,新建每次刷新页面都重新查一遍。张表我让他把结果直接落成一张临时表,查询存入查询时间降到 0.3 秒。结果MySQL 里把查询结果存进新表,新建最直接的张表办法就是 CREATE TABLE ... AS SELECT,但这里面的查询存入坑比想象中多。

先看基本写法:

CREATE TABLE monthly_report AS SELECT user_id,结果 COUNT(*) AS order_cnt, SUM(amount) AS total FROM orders WHERE created_at >= '2024-01-01' GROUP BY user_id;

执行完,一张名为 monthly_report 的新建新表就出来了,字段名和类型由 SELECT 的张表结果集推导。注意是查询存入“推导”,不是结果“继承”。原表 orders.user_id 是新建 BIGINT UNSIGNED,新表里可能变成 BIGINT,符号位丢了。金额字段如果是 DECIMAL(10,2),聚合后的 SUM() 精度也可能变。我见过有人用这种方式建表,后面往里插负数直接报错,查了半天才发现是 UNSIGNED 在作怪。

想控制字段类型和索引,得换个路子。先 CREATE TABLE 定义好结构,再用 INSERT INTO ... SELECT 灌数据:

CREATE TABLE monthly_report ( user_id BIGINT UNSIGNED NOT NULL, order_cnt INT UNSIGNED NOT NULL DEFAULT 0, total DECIMAL(12,2) NOT NULL DEFAULT 0.00, PRIMARY KEY (user_id), KEY idx_total (total) ); INSERT INTO monthly_report (user_id, order_cnt, total) SELECT user_id, COUNT(*), SUM(amount) FROM orders WHERE created_at >= '2024-01-01' GROUP BY user_id;

多写几行,换来的是可控的字段类型、主键和索引。数据量大时,这个差距更明显——CREATE TABLE AS SELECT 建出来的表没有索引,后续查询该慢还是慢。

还有个细节:CREATE TABLE AS SELECT 默认不会复制原表的索引、外键、触发器,连 AUTO_INCREMENT 属性都不带。它就像拍快照,只拿数据,不拿约束。如果 SELECT 里有表达式,比如 COUNT(*) 或 SUM(amount),字段名会变成 COUNT(*) 这种带括号的怪东西,后面引用得加反引号,挺烦。用别名能避开:COUNT(*) AS order_cnt。

临时表场景更推荐 CREATE TEMPORARY TABLE ... AS SELECT。会话断开自动销毁,不污染库。但临时表有个限制:同一会话内不能同名两次,想重复用就得先 DROP TEMPORARY TABLE IF EXISTS。我习惯在存储过程里这么干,省得每次手动清理。

引擎选择也值得说一句。MySQL 8.0 默认 InnoDB,CREATE TABLE AS SELECT 建出来的也是 InnoDB。如果只是中间结果,数据量又大,可以显式指定 ENGINE=MEMORY,速度飞快,但字段类型受限,TEXT/BLOB 用不了。以前有人用 MyISAM 做中间表,现在没必要了,InnoDB 够快,还支持事务。

最后提一个容易忽略的点:CREATE TABLE AS SELECT 执行期间会持有原表的元数据锁,大查询可能阻塞其他 DDL。生产环境跑之前,最好在业务低峰期,或者用 INSERT INTO ... SELECT 配合已有表结构,锁的粒度更可控。

我自己的习惯是:临时分析用 CREATE TEMPORARY TABLE ... AS SELECT,图快;要长期留存或加索引的,老老实实先建表再插数据。多写几行 DDL,省下的是后面排查字段类型和性能问题的时间。

标签:

责任编辑:知识