catalog_product_entity_varchar 保存商品的文本型 EAV 属性值。商品多、Store View 多时表大很正常;真正需要处理的是异常增长、重复 Store View 覆盖、已删除实体残留或集成每次都写入无意义值。直接按行数删除会损坏商品名称、URL key、图片路径等核心数据。

先量化:大在哪里

SELECT table_rows, data_length, index_length,
       ROUND((data_length+index_length)/1024/1024,1) AS total_mb
FROM information_schema.tables
WHERE table_schema=DATABASE()
  AND table_name='catalog_product_entity_varchar';

SELECT attribute_id, store_id, COUNT(*) AS rows_count
FROM catalog_product_entity_varchar
GROUP BY attribute_id, store_id
ORDER BY rows_count DESC
LIMIT 30;

再把 attribute_id 映射成属性代码:

SELECT v.attribute_id, a.attribute_code, v.store_id, COUNT(*) AS rows_count
FROM catalog_product_entity_varchar v
JOIN eav_attribute a ON a.attribute_id=v.attribute_id
GROUP BY v.attribute_id, a.attribute_code, v.store_id
ORDER BY rows_count DESC
LIMIT 50;

如果某个自定义属性占比异常,就从它的导入或保存逻辑入手;如果每个 Store View 都接近完整商品数,可能是导入把 Default 值复制到了所有范围。

检查无效 Store 和无主实体

SELECT v.store_id, COUNT(*)
FROM catalog_product_entity_varchar v
LEFT JOIN store s ON s.store_id=v.store_id
WHERE v.store_id<>0 AND s.store_id IS NULL
GROUP BY v.store_id;

SELECT COUNT(*) AS orphan_rows
FROM catalog_product_entity_varchar v
LEFT JOIN catalog_product_entity p ON p.entity_id=v.entity_id
WHERE p.entity_id IS NULL;

Commerce 某些版本使用 row_id 关联,必须先 DESCRIBE 表并按当前架构改查询。暂存内容也可能让看似无主的 row_id 实际有效,不能照抄删除。

识别与 Default 完全相同的 Store View 覆盖

SELECT s.value_id, s.entity_id, s.attribute_id, s.store_id, s.value
FROM catalog_product_entity_varchar s
JOIN catalog_product_entity_varchar d
  ON d.entity_id=s.entity_id
 AND d.attribute_id=s.attribute_id
 AND d.store_id=0
WHERE s.store_id<>0 AND s.value=d.value
LIMIT 100;

相同值不一定冗余:业务可能故意固定 Store View 值,防止 Default 后续变化。先与内容团队确认继承语义,并按属性、商店和来源系统生成候选清单。

用时间和批次判断增长来源

EAV value 表通常没有 created_at。若需要定位最近增长,可从 binlog、数据库审计、导入报告和商品 updated_at 建立时间关联。先统计一段时间内被大量修改的商品,再对这些 entity_id 的 varchar 属性分布进行抽样:

SELECT DATE(updated_at) AS day, COUNT(*) AS products_changed
FROM catalog_product_entity
WHERE updated_at >= CURRENT_DATE - INTERVAL 14 DAY
GROUP BY DATE(updated_at)
ORDER BY day;

SELECT v.attribute_id, v.store_id, COUNT(*) AS rows_count
FROM catalog_product_entity_varchar v
JOIN catalog_product_entity p ON p.entity_id=v.entity_id
WHERE p.updated_at >= CURRENT_DATE - INTERVAL 1 DAY
GROUP BY v.attribute_id, v.store_id
ORDER BY rows_count DESC;

某次 ERP 全量同步后所有 Store View 同时增长,通常比单个商品编辑更可疑。把增长曲线与任务开始、结束时间对齐,能找到真正写入者。

删除前评估索引和锁的成本

大批删除会产生 undo、redo 和复制流量。先在副本上用相同 WHERE 条件执行 SELECT COUNT(*),查看执行计划是否使用 entity_id/attribute_id/store_id 索引。生产清理应按主键小批次提交,并监控副本延迟:

EXPLAIN SELECT value_id
FROM catalog_product_entity_varchar
WHERE attribute_id=999 AND store_id=3;

SHOW INDEX FROM catalog_product_entity_varchar;
SHOW ENGINE INNODB STATUS\G

如果删除目标是“Store View 值与 Default 相同”,需要在临时表固化 value_id 清单,避免清理过程中 Default 值又发生变化。清理脚本必须可重复执行且不会扩大匹配范围。

找到制造数据的流程

对比表增长时间与商品导入、ERP 同步、批量属性更新日志。检查集成是否每次为所有 Store View 写空字符串或重复值。正确做法是:没有本地覆盖时不写 Store View 行;需要恢复继承时删除特定范围值,而不是写一份 Default 副本。

清理必须可回滚

  1. 在副本数据库执行统计和候选查询。
  2. 把待删除行复制到带日期的备份表,并记录 SQL 与行数。
  3. 抽查名称、URL key、图片、元数据等高风险属性。
  4. 小批量事务删除,检查复制延迟和锁等待。
  5. 重建相关索引并做前台回归。

删除行后表文件未立即变小很正常。OPTIMIZE TABLE 可能重建大表并占用大量磁盘与锁时间,只能在评估空间、复制和维护窗口后执行。目标应是停止异常增长和改善查询,而不是追求立刻缩小文件。