Think怎么现阅读计数 Think文章浏览量统计操作案例|Duuu笔记
在生产环境中优化PHP,本文分析
阅读数未更新主因是缓存干扰或条件不匹配:ThinkPHP默认开启查询缓存,若误用cache(true)或全站缓存中间件,读取仍返回旧值;需检查ID类型是否一致、SQL条件是否命中,并确保setInc在控制器中执行而非模板内。
为什么
update
之后阅读数没变?
常见现象是:文章页每次刷新都调用
Db::name('article')->where('id', $id)->setInc('view_count')
,但数据库里数字不动。原因多半是缓存没关或条件没命中——ThinkPHP 默认开启查询缓存,
setInc
属于写操作,但若上层用了
cache(true)
或开启了全站缓存中间件,读取时可能仍返回旧值。
确认是否在控制器/模型中误加了
->cache(true)
链式调用
检查数据库连接配置里的
'resultset_type' => 'collection'
是否影响了写后立即读(一般不影响,但搭配某些缓存驱动会出问题)
用
Db::getLastSql()
打印实际执行的 SQL,看 WHERE 条件是否真能匹配到记录(比如
$id
是字符串而数据库字段是 int,隐式转换失败)
并发下
setInc
会不会丢计数?
会,但概率低。ThinkPHP 的
setInc
底层是
UPDATE ... SET view_count = view_count + 1
,MySQL 在行级锁支持下基本能保证原子性。但如果业务量大(比如秒杀级文章爆火),仍可能出现多个请求同时读到同一旧值、再各自+1写回的情况——这不是 ThinkPHP 的锅,是 SQL 层面未加锁导致。
高并发场景建议改用原生 SQL 加
FOR UPDATE
(需事务包裹):
Db::startTrans();
Db::query('SELECT view_count FROM think_article WHERE id = ? FOR UPDATE', [$id]);
Db::name('article')->where('id', $id)->setInc('view_count');
Db::commit();
更轻量的解法是用 Redis 计数器:先
INCR article:views:$id
,再定时同步回 MySQL,避免 DB 成为瓶颈
注意:Redis 方案要处理“页面首次加载即触发计数”的时机——不能等 JS 异步上报,得服务端直出时就 incr
怎么区分真实用户和爬虫?
直接对所有
GET /article/123
请求加
setInc
,会导致搜索引擎、健康检查、监控探针把阅读数刷爆。必须过滤。
独响
一个轻笔记+角色扮演的app
下载
基础判断:检查
$_SERVER['HTTP_USER_AGENT']
是否含
bot
、
spider
、
crawler
(不区分大小写),但容易误伤 RSS 阅读器
更稳的做法是结合
$_SERVER['HTTP_X_FORWARDED_FOR']
和 IP 频率限制——比如同一 IP 5 分钟内只记 1 次
推荐组合策略:
if (!is_bot() && !is_repeated_view($ip, $article_id)) {
Db::name('article')->where('id', $id)->setInc('view_count');
}
其中
is_repeated_view
可用 Redis 的
SETNX article:seen:$ip:$id
+ 过期时间实现
setInc
和手动
update
哪个更安全?
用
setInc
更安全。手动写
update(['view_count' => $old + 1])
要先
select
再
update
,中间有竞态窗口;而
setInc
是单条 SQL,MySQL 自己保证原子性。
“
PHP免费学习笔记(深入)
”;
但注意:如果字段允许 NULL,
setInc
对 NULL 值会变成
NULL + 1 → NULL
,结果仍是 NULL。务必初始化为 0:
Db::name('article')->where('id', $id)->update(['view_count' => 0]);
如果需要带条件更新(比如仅登录用户才计数),
setInc
不支持 where 外的额外条件,得换
update
+ 事务兜底
性能上无差别,底层都是 UPDATE;但
setInc
少一次 PHP 层变量计算,代码更干净
真实项目里最常被忽略的,是「计数时机」——很多人在模板里用
{:Db::name('article')->where('id', $id)->value('view_count')}
直接查,却忘了这行代码本身不会触发
setInc
。阅读数增加和展示必须拆成两个明确动作,且增加操作得放在控制器逻辑里,不能依赖模板渲染时顺手做。
