真正未被引用的图片ID需同时排除内容URL、缩略图元数据及附件文件路径匹配:SELECT p.ID FROM wp_posts p WHERE p.post_type='attachment' AND p.post_status='inherit' AND p.ID NOT IN (SELECT DISTINCT CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(pm.meta_value, 'wp-content/uploads/', -1), '"', 1) AS UNSIGNED) FROM wp_postmeta pm WHERE pm.meta_key='_wp_attached_file' AND pm.meta_value REGEXP 'wp-content/uploads/[0-9]{4}/[0-9]{2}/1+.(jpg|jpeg|png|gif)$') AND p.ID NOT IN (SELECT meta_value FROM wp_postmeta WHERE meta_key='_thumbnail_id') AND p.ID NOT IN (SELECT p2.ID FROM wp_posts p2 JOIN wp_posts p3 ON p3.post_content REGEXP CONCAT('https?://2+/wp-content/uploads/[0-9]{4}/[0-9]{2}/1+.', p2.post_mime_type) WHERE p2.post_type='attachment' AND p2.post_mime_type IN ('image/jpeg','image/png','image/gif'));" ↩/ ↩
查出数据库里没被任何文章/页面引用的图片ID
wordpress媒体库里的图片如果没被post_content、post_excerpt或自定义字段(如_thumbnail_id)显式调用,就属于“游离媒体”。但wp_posts表本身不存引用关系,得靠反向扫描内容字段来判断。
直接查wp_posts中post_type = 'attachment'且post_status = 'inherit'的记录只是第一步;真正要筛掉的是那些ID没出现在任何post_content里的wp-content/uploads/路径或wp_get_attachment_url()生成的URL中。
- 别依赖
guid字段匹配——它可能被迁移改写过,已不可信 - 用
REGEXP比LIKE更准:比如匹配/wp-content/uploads/2023/05/abc.jpg,得同时覆盖http://、https://和相对路径 - 注意缩略图:
_thumbnail_id元数据是硬关联,必须单独查wp_postmeta表,漏掉这个会误删大量封面图
用SQL一次性找出未被引用的附件ID(含缩略图校验)
下面这条SQL能避开常见陷阱,返回真正“没人要”的ID:
SELECT p.ID
FROM wp_posts p
WHERE p.post_type = 'attachment'
AND p.post_status = 'inherit'
AND p.ID NOT IN (
-- 被文章内容引用的
SELECT DISTINCT CAST(SUBSTRING_INDEX(SUBSTRING_INDEX(pm.meta_value, 'wp-content/uploads/', -1), '"', 1) AS UNSIGNED)
FROM wp_postmeta pm
WHERE pm.meta_key = '_wp_attached_file'
AND pm.meta_value REGEXP 'wp-content/uploads/[0-9]{4}/[0-9]{2}/[^"]+\.(jpg|jpeg|png|gif)$'
)
AND p.ID NOT IN (
-- 被设为特色图的
SELECT meta_value FROM wp_postmeta WHERE meta_key = '_thumbnail_id'
)
AND p.ID NOT IN (
-- 被post_content里URL引用的(需替换域名)
SELECT p2.ID
FROM wp_posts p2
JOIN wp_posts p3 ON p3.post_content REGEXP CONCAT('https?://[^/]+/wp-content/uploads/[0-9]{4}/[0-9]{2}/[^"]+\.', p2.post_mime_type)
WHERE p2.post_type = 'attachment'
AND p2.post_mime_type IN ('image/jpeg', 'image/png', 'image/gif')
);实际执行前先备份。上面第三层子查询性能较差,大站建议拆成临时表或用PHP分批处理。
删除前务必确认文件物理存在且无插件依赖
查出ID只是开始。WordPress插件(比如WP Smush、Autoptimize)可能把原始图转成WebP并改名,或者用wp_upload_dir()生成非标准路径;有些主题还手动wp_get_attachment_image_src()拼接URL,绕过常规匹配。
- 用
wp_get_attachment_metadata()检查file字段是否指向真实存在的文件路径 - 搜索全站插件目录里有没有
get_posts、WP_Query带post_type=attachment的调用,避免误伤缓存图或后台专用图 - 别直接
DELETE FROM wp_posts——先UPDATE ... SET post_status = 'trash',留出回滚窗口
清理后媒体库仍显示图片?那是缓存或孤立元数据
删了wp_posts记录,但wp_postmeta里残留的_wp_attachment_metadata或_wp_attached_file会让部分管理界面报错或显示异常。这些不是“图片还在”,而是“数据库脏数据”。
- 运行
DELETE FROM wp_postmeta WHERE post_id NOT IN (SELECT ID FROM wp_posts)清空孤儿元数据 - 对象缓存(Redis/Memcached)可能缓存了
wp_count_attachments()结果,记得wp_cache_flush() - 某些主题用
get_children()查附件,该函数默认只查post_status = 'inherit',删完后不用额外处理
最麻烦的其实是CDN上已拉取但本地删了的图——HTTP 404会持续到CDN缓存过期,这个得去CDN后台手动刷新路径,没法靠WordPress解决。


















