A-
A+
WordPress把阅读计数(`wp_postmeta` 存`post_views`元字段)迁移到新建独立表。
今天发现数据库有一个postmate的表已经过万的数据了,仔细一看是post_views占用了2000多条,后期数量更多了以后,就更麻烦了,所以要迁移到独立表里提升数据库性能。
1.首先停用你的插件WP‑PostViews
2.创建数据库和迁移数据库以及删除的SQL语句,建议在phpmyadmin下操作,一定先备份数据库!!
-- 1. 创建计数表(插件激活会自动执行,这里仅备份SQL) CREATE TABLE IF NOT EXISTS `wp_post_views` ( `post_id` bigint(20) UNSIGNED NOT NULL COMMENT '文章ID', `view_count` bigint(20) UNSIGNED NOT NULL DEFAULT '0' COMMENT '阅读数', PRIMARY KEY (`post_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 2. 从postmeta迁移旧阅读数据(旧数据存在 postmeta meta_key='post_views') INSERT INTO `wp_post_views` (`post_id`, `view_count`) SELECT post_id, meta_value FROM `wp_postmeta` WHERE meta_key = 'post_views' AND meta_value > 0 ON DUPLICATE KEY UPDATE view_count = VALUES(view_count); -- 3. 校验迁移结果,查看迁移多少条 SELECT COUNT(*) FROM wp_post_views; -- 可选:迁移完成确认数据无误后,清理旧meta(谨慎,确认数据正确再执行) -- DELETE FROM wp_postmeta WHERE meta_key='post_views';
3.建一个单独文件放入到你的主题目录下,文件名字比如为postviews.php
<?php
/**
* @version PHP static数组 shutdown落库版
* 优缺点备注:
* 1.【优点】计数数据100%可靠,不存在TTL过期、缓存服务重启丢失计数问题;使用MySQL原子自增SQL,高并发不会发生计数覆盖丢失。
* 2.【优点】逻辑简单清晰,调试、排错方便;仅复用缓存做读取加速,不拿缓存承担计数缓冲职责。
* 3.【优点】存储使用独立wp_post_views表,彻底解决原wp_postmeta表膨胀、碎片、行锁问题。
* 4.【缺点】每一个独立访问请求,shutdown阶段都会执行一次MySQL写SQL;PV越高,数据库写压力越大,Memcached只能加速读,无法减少写次数。
* 5.【缺点】static数组仅在当前PHP请求进程内生效,不能跨请求合并多个访客的计数增量。
* 6.【适用场景】优先保证阅读计数准确;日PV中等;追求代码稳定易维护;Memcached存在重启风险的环境。
* 7.【不适用场景】超高访问量,MySQL写入成为明显性能瓶颈。
* 8.【优化方向】可叠加Cookie/IP访问冷却逻辑,过滤短时间重复刷新,减少总写SQL数量。
* 9.【回退方案】换回老插件postmeta方案,但是会存在meta表膨胀、并发计数覆盖bug。
*/
if (!defined('ABSPATH')) {
exit;
}
// ==========【新增开始】解决列表N+1查询问题 ==========
//页面生命周期内存缓存,存放the_posts钩子批量预加载出来的阅读数
static $im2828_local_cache = [];
//WordPress主查询钩子:**仅主查询执行批量IN查询**,自定义WP_Query直接跳过,避免产生多余SQL
add_filter('the_posts', function($posts, $query){
if(!$query->is_main_query()){
return $posts;
}
if(empty($posts)) return $posts;
$ids = [];
foreach ($posts as $p){
$ids[] = (int)$p->ID;
}
$id_str = implode(',', array_map('intval', $ids));
global $wpdb;
$rows = $wpdb->get_results("SELECT post_id,view_count FROM {$wpdb->prefix}post_views WHERE post_id IN({$id_str})");
foreach ($rows as $row){
$GLOBALS['im2828_local_cache'][(int)$row->post_id] = (int)$row->view_count;
}
return $posts;
},10,2);
// ==========【新增结束】 ==========
// 获取文章阅读数
function im2828_get_views($post_id) {
global $wpdb;
$post_id = (int)$post_id;
if ($post_id < 1) return 0;
// ==========【新增开始】优先读取页面批量预加载内存缓存,避免列表循环单条查库 ==========
if(isset($GLOBALS['im2828_local_cache'][$post_id])){
return $GLOBALS['im2828_local_cache'][$post_id];
}
// ==========【新增结束】 ==========
$cache_key = "post_views_{$post_id}";
$num = im2828_cache_get($cache_key);
if ($num !== false) {
return (int)$num;
}
$val = $wpdb->get_var($wpdb->prepare("SELECT view_count FROM {$wpdb->prefix}post_views WHERE post_id = %d", $post_id));
$num = $val ? (int)$val : 0;
im2828_cache_set($cache_key, $num, 600);
// ==========【新增开始】把本次查询结果存入页面本地内存缓存,同页面多次调用不再重复查库 ==========
$GLOBALS['im2828_local_cache'][$post_id] = $num;
// ==========【新增结束】 ==========
return $num;
}
// 内存缓冲计数
static $im2828_view_inc = [];
function im2828_inc_views($post_id) {
if (is_admin() || (int)$post_id < 1) return;
//移除不存在的 wp_is_robot()
$im2828_view_inc[(int)$post_id] = ($im2828_view_inc[(int)$post_id] ?? 0) + 1;
}
// shutdown批量写入数据库
add_action('shutdown', function(){
global $wpdb, $im2828_view_inc;
if(empty($im2828_view_inc)) return;
foreach ($im2828_view_inc as $pid => $add) {
$wpdb->query($wpdb->prepare(
"INSERT INTO {$wpdb->prefix}post_views (post_id, view_count) VALUES (%d, %d)
ON DUPLICATE KEY UPDATE view_count = view_count + %d",
$pid, $add, $add
));
$cache_key = "post_views_{$pid}";
im2828_cache_delete($cache_key);
}
}, 99);
// 文章详情页触发计数
add_action('wp', function(){
if(is_singular('post')){
im2828_inc_views(get_the_ID());
}
});
// 兼容原有模板函数,模板不用修改
if (!function_exists('template_get_view_num')) {
function template_get_view_num()
{
static $_view_cache = [];
$post_id = get_the_ID();
if (!isset($_view_cache[$post_id])) {
$_view_cache[$post_id] = im2828_get_views($post_id);
}
return $_view_cache[$post_id];
}
}
/**
* 【工具函数】自定义WP_Query查询后手动调用,按需批量预加载阅读计数,消除自定义循环N+1
* @param array $posts 查询返回的posts数组 $query->posts
*/
function im2828_preload_views($posts)
{
if(empty($posts)) return;
$ids = [];
foreach ($posts as $p){
$ids[] = (int)$p->ID;
}
$id_str = implode(',', array_map('intval',$ids));
global $wpdb;
$rows = $wpdb->get_results("SELECT post_id,view_count FROM {$wpdb->prefix}post_views WHERE post_id IN({$id_str})");
foreach ($rows as $row){
$GLOBALS['im2828_local_cache'][(int)$row->post_id] = (int)$row->view_count;
}
}
然后在functions.php里加入一行
require_once get_template_directory() . '/postviews.php';
这样一来,就大幅度提升了wp_postmeta的性能,里面会少很多数据。
