本帖最后由 mark35 于 2012-12-3 21:03 编辑
技术是用来解决问题,不是用来堆砌的
Discuz X2.5的新构架似乎是为了构架而构架,为了技术而技术。本来一个很简单功能非要整些“高科技”实现,却得不偿失
先看说明:
• 点击数优化
问题:
频繁的写表操作导致锁表
方法:
增加forum_threadaddviews表,记录每一个TID的增量点击数;
查看帖子时,如果增量点击数到100,则使用进程锁将数据更新到thread表并更新增量点击数为0;
回贴时将增量点击数和回复数一起更新到thread表,并更新增量点击数为0;
执行计划任务:每天3点,5分钟一次,一次取500条数据更新到thread表, 并删除此500条数据,以减少forum_threadaddviews表的大小;
http://dev.discuz.org/wiki/index ... 0.E4.BC.98.E5.8C.96
代码:
- function viewthread_updateviews($tableid) {
- global $_G;
- if(!$_G['setting']['preventrefresh'] || $_G['cookie']['viewid'] != 'tid_'.$_G['tid']) {
- if(!$tableid && $_G['setting']['optimizeviews']) {
- if($_G['forum_thread']['addviews']) {
- if($_G['forum_thread']['addviews'] < 100) {
- C::t('forum_threadaddviews')->update_by_tid($_G['tid']);
- } else {
- if(!discuz_process::islocked('update_thread_view')) {
- $row = C::t('forum_threadaddviews')->fetch($_G['tid']);
- C::t('forum_threadaddviews')->update($_G['tid'], array('addviews' => 0)); // <------------------ 归零
- C::t('forum_thread')->increase($_G['tid'], array('views' => $row['addviews']+1), true);
- discuz_process::unlock('update_thread_view');
- }
- }
- ......
- }
复制代码 /source/module/forum/forum_viewthread.php line=909
为了解决一个在并发时addviews值在两个表同时正确更新的问题,竟然用上了进程锁。原因有2:
1、myisam不能行锁,如果用表锁就会导致对一个tid操作时所有tid的操作都被阻塞。结果是所谓点击优化反而导致性能恶化。
2、因为既不能表锁,也不可能行锁,所以在并发时会出现两个线程同时对同一个tid的点击数进行更新情况,如果不能锁住在临时表中的行值,那么就会出现点击数翻倍更新的情况(见上面PHP代码,是先取出值更新到主表,再临时表该行归零)。所以就使用memcache功能实现了数据库本来该有的行锁功能。
可是,难道就没有简单的解决办法了么?
先看看库表结构吧- CREATE TABLE pre_forum_threadaddviews (
- tid mediumint(8) unsigned NOT NULL DEFAULT '0',
- addviews int(10) UNSIGNED NOT NULL DEFAULT '0', -- <-------- UNSIGNED!
- PRIMARY KEY (tid)
- ) TYPE=MyISAM;
复制代码 /install/data/install.sql
好像mysqler 最喜欢或者只会使用 unsigned 类型,你觉得代码中归零就不用考虑负值溢出啦。可你不懂财会基础知识,不知道负值在这儿可是真正的银弹。
好吧,废话少说,以下是省时省力低碳的解决办法:
1、表结构改为 addviews int(10) NOT NULL DEFAULT '0'
2、在viewthread_updateviews函数中抛弃进程锁,读出多少减去多少
- } else {
- // if(!discuz_process::islocked('update_thread_view')) {
- $row = C::t('forum_threadaddviews')->fetch($_G['tid']);
- C::t('forum_threadaddviews')->decrease($_G['tid'], array('addviews' => $row['addviews'])); // <------------
- C::t('forum_thread')->increase($_G['tid'], array('views' => $row['addviews']+1), true);
- // discuz_process::unlock('update_thread_view');
- }
- }
复制代码 这保证了即便不使用锁机制,也不用担心并发操作造成错误数据——主表那儿加多了?不要紧,临时表那儿就是相同差值的负数,这就是有借有贷,借贷平衡!
当然,上面的代码不能直接这么修改。因为只是屏蔽了进程锁的检测、解锁代码,并未屏蔽加锁。当前这种类方法调用格式无法通过传递参数的方式决定是否加锁,除非使用单独的全局变量或者类属性来设置是否加锁,或者修改C类静态方法让其接受第二个参数决定是否使用进程锁。
把一个简单事情做复杂,很简单,
把一个复杂事情做简单,很难。
|