一个让人抓狂的“幽灵慢查询”
深夜两点,你刚上线了一个带自定义模型的“二手交易”频道,前台列表页加载从最初的2秒飙升到15秒,更诡异的是,后台编辑文章时,只要涉及自定义字段,页面就卡得像PPT翻页,你打开帝国CMS的性能监视器,看到 phome_ecms_shop_data 和 phome_ecms_shop_index 两张表上出现了大量 filesort 和 Using temporary。
排查思路按部就班:先看数据量——shop_index 才6万行,shop_data 有9万行,这种量级在MySQL里根本不该慢,问题一定出在索引设计上。
帝国CMS数据表索引优化实战,从卡顿到秒开的调优手记
自定义模型创建后前台不显示——索引缺失的“幽灵”
你新建了模型 shop,添加了字段 price(FLOAT)、condition_level(TINYINT),前台用灵动标签调用:
[e:loop={"select * from phome_ecms_shop where checked=1 order by newstime desc limit 10",10,24,0}]
结果空白,查 phome_ecms_shop_index 表发现,checked 字段没有索引,当 checked=0 的记录占多数时,全表扫描加上 filesort 直接拖垮查询。
修复:
ALTER TABLE phome_ecms_shop_index ADD INDEX idx_checked_time (checked, newstime);
关键点:帝国CMS的模型索引表字段通常已经建索引,但 checked、isgood 这类布尔字段容易被忽略,凡在灵动标签或列表页用于 where 条件且区分度低的字段,务必联合排序字段建复合索引。
灵动标签SQL调用——如何避免“选择性索引失效”
你写了一个带参数筛选的灵动标签:
[e:loop={"select * from phome_ecms_shop_data d left join phome_ecms_shop_index i on d.id=i.id where i.classid in (5,6,7) and d.price between 100 and 500 order by i.newstime desc",20,24,0}]
结果执行2.8秒,解释执行计划发现 d 表全表扫描——因为 price 字段存在 phome_ecms_shop_data 表,而 classid 和 newstime 在另一张表。跨表条件导致索引合并失败。
正确姿势:
- 给
data表的price加单列索引:ALTER TABLE phome_ecms_shop_data ADD INDEX idx_price (price);
- 改造SQL,利用
IN子查询先缩小范围:[e:loop={"select * from phome_ecms_shop_index i where i.classid in (5,6,7) and i.id in (select id from phome_ecms_shop_data where price between 100 and 500) order by i.newstime desc",20,24,0}] - 若数据量继续增长,考虑在
shop_index表冗余一个price字段(牺牲规范化换性能),并在模型字段里勾选“复制到主表”。
列表模板和内容模板变量调用——索引对模板变量的隐性影响
列表页你习惯用 $bqr[title]页用 $navinfor[price],实际上帝国CMS在列表页生成时,每次循环都会通过 id 回查 data 表获取附表字段,如果主键索引失效(比如主键被误删除或改成非自增),每次回查就是一个全表扫。
检查你的表结构:
SHOW INDEX FROM phome_ecms_shop_data;
确保 id 为 PRIMARY 索引,且类型为 int(11) unsigned,如果之前升级或迁移导致主键变成普通索引,立即修复:
ALTER TABLE phome_ecms_shop_data DROP PRIMARY KEY, ADD PRIMARY KEY (id);
另一个技巧:列表模板中只显示附表字段时,直接用灵动标签查 data 表,但必须带上主表的主键条件,不要写:
select * from phome_ecms_shop_data where id in (...)
而要这样:
select d.* from phome_ecms_shop_data d force index (PRIMARY) where d.id in (...)
force index 能避免优化器在数据量小的时候误选低效索引。
万能标签与智能标签——索引使用的分水岭
很多用户分不清二者。智能标签(如 [e:loop])会强制走主表索引,适合简单列表;万能标签([e:万能标签])允许原生SQL,但帝国不会帮你优化执行计划。
典型场景:你要调用一个“本周热门二手商品”,包含价格和成交次数排序。
万能标签写法:
[e:万能标签]select i.id, i.title, d.price, i.onclick from phome_ecms_shop_index i inner join phome_ecms_shop_data d on i.id=d.id where i.newstime > unix_timestamp(date_sub(now(), interval 7 day)) and i.checked=1 order by i.onclick desc limit 10[/e:万能标签]
此时必须给 onclick 加索引,否则排序就是全表 filesort:
ALTER TABLE phome_ecms_shop_index ADD INDEX idx_click_time (onclick, newstime);
核心区别:智能标签自动帮你管理“是否显示未审核、是否去重”等逻辑,但会额外执行查询;万能标签完全裸奔,性能可达最优,但索引设计必须自己负责,新手用智能,高峰期用万能。
全站搜索配置与自定义字段索引——搜索慢的元凶
全站搜索开启后,帝国用了 phome_search 临时表,但如果你在搜索表单里支持自定义字段筛选(比如按价格区间搜),系统会直接在同表加 where price between,却不会告诉你索引缺失。
自定义字段索引:进入模型字段管理,找到 price,勾选“搜索”和“索引”,帝国会在 phome_ecms_shop_data 上自动建立 idx_price,但注意:如果该字段是 text 类型,不会建索引,请将需要搜索的字段设为 float、int 或 varchar。
手动检查并补充:
SHOW INDEX FROM phome_ecms_shop_data WHERE Column_name = 'price';
如果没索引,帝国后台重新保存一次字段设置即可自动生成。
搜索时的SQL技巧:让帝国搜索框的 where 条件强制使用索引:
$search .=" and d.price >= '".intval($price_min)."'";
不要用 like 匹配数字字段,那会废掉索引。
模板中调用附表字段的实现技巧——避免每行查询的致命伤
页(show)模板里,你直接写 $navinfor[price] 没问题,因为帝国已经预取数据,但在列表页(list)模板里,你写 $bqr[price] 就会触发逐行二次查询,数据量一大,6万行就是6万次查询。
解决方案:在列表页使用灵动标签一次性联表取数,而非依赖模板变量。
[e:loop={"select i.id, i.title, d.price, d.condition_level from phome_ecms_shop_index i inner join phome_ecms_shop_data d on i.id=d.id where i.classid='$GLOBALS[navclassid]' order by i.newstime desc",20,24,0}][!--title--] 价格:[!--price--] 成色:[!--condition_level--]</li>
[/e:loop]
这样仅执行一次联表查询,模板变量只是输出数组内容,不触库。
终极优化:对于百万级数据,将 shop_data 中高频查询的字段(如价格、成色)冗余到 shop_index 表,然后只查单表。
定期维护:记得每周执行一次:
OPTIMIZE TABLE phome_ecms_shop_index, phome_ecms_shop_data; ANALYZE TABLE phome_ecms_shop_index;
收尾:索引诊断的“三板斧”
- 打开帝国后台性能分析,找到耗时超过0.5秒的SQL,用
EXPLAIN看type是不是ALL或index——那是全表扫描的信号。 - 所有
order by字段必须有索引,且方向(ASC/DESC)要一致,否则MySQL会隐式排序。 - 多表联查时,
where条件里的字段类型必须一致,i.id是int,d.id不能是varchar。
做完这些调整,你的二手频道前台从15秒压到了0.3秒——这才是帝国CMS在数据量面前的正确打开方式。帝国CMS本身不慢,慢的是你对索引的忽视。



发表评论