千萬級(jí)記錄的Discuz論壇導(dǎo)致MySQL CPU 100%的優(yōu)化筆記
前幾天,一位朋友通過這篇文章找到了我,說他就是運(yùn)行最新的 discuz 版本,MySQL 占用 CPU 100%,導(dǎo)致系統(tǒng)假死,每天都要重啟好幾次,花了一個(gè)多月的時(shí)間一直沒有解決,希望我?guī)兔σ幌?。?jīng)過檢查,他的這個(gè)論壇最重要的幾個(gè)表中,目前 cdb_members 表,有記錄 6.2 萬;cdb_threads 表,有記錄 11萬;cdb_posts表,有記錄 1740 萬;所有數(shù)據(jù)表的記錄加起來,超過 2000 萬;數(shù)據(jù)庫的大小超過 1GB。經(jīng)過半天的調(diào)試,總算完成了 discuz 論壇優(yōu)化,于是將其解決經(jīng)過記錄在這篇文章中。
2007年3月我發(fā)現(xiàn) discuz 論壇的數(shù)據(jù)庫結(jié)構(gòu)設(shè)計(jì)有一些疏忽,有許多查詢子句的條件比較,都沒有建立 Index 索引。當(dāng)時(shí)我所檢查的那個(gè)數(shù)據(jù)表,記錄只有幾千條,因此對(duì) CPU 負(fù)荷不大?,F(xiàn)在這個(gè)數(shù)據(jù)庫表,上千萬的記錄檢索,可以想象,如果數(shù)據(jù)表結(jié)構(gòu)設(shè)計(jì)不規(guī)范,沒有提供索引,所耗費(fèi)的時(shí)間是一個(gè)恐怖的數(shù)字。有關(guān) MySQL 建立索引的重要性,可以參見我的這篇文章底部的說明
為了調(diào)試方便,我從 dizcus 的官網(wǎng)下載了其最新的 Dizcus! 5.5.0 論壇程序.
我首先檢查了 my.ini 的參數(shù)配置,一切正常。進(jìn)入 MySQL 的命令行,調(diào)用 show processlist 語句,查找負(fù)荷最重的 SQL 語句,結(jié)合 Discuz 論壇的源碼,發(fā)現(xiàn)有以下語句導(dǎo)致 CPU 上升:
mysql> show processlist;
+-----+------+----------------+---------+---------+------+------------+---------
-----------------------------------------------------------------+
| Id | User | Host | db | Command | Time | State | Info
+-----+------+----------------+---------+---------+------+------------+---------
-----------------------------------------------------------------+
| 363 | root | localhost:1393 | history | Query | 0 | statistics | SELECT C
OUNT(*) FROM cdb_pms WHERE msgfromid=11212 AND folder='outbox' |
+-----+------+----------------+---------+---------+------+------------+---------
檢查 cdb_pms 表的結(jié)構(gòu):
mysql> show columns from cdb_pms;
+-----------+------------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+------------------------+------+-----+---------+----------------+
| pmid | int(10) unsigned | NO | PRI | NULL | auto_increment |
| msgfrom | varchar(15) | NO | | | |
| msgfromid | mediumint(8) unsigned | NO | MUL | 0 | |
| msgtoid | mediumint(8) unsigned | NO | MUL | 0 | |
| folder | enum('inbox','outbox') | NO | | inbox | |
| new | tinyint(1) | NO | | 0 | |
| subject | varchar(75) | NO | | | |
| dateline | int(10) unsigned | NO | | 0 | |
| message | text | NO | | | |
| delstatus | tinyint(1) unsigned | NO | | 0 | |
+-----------+------------------------+------+-----+---------+----------------+
10 rows in set (0.00 sec)
這條語句: WHERE msgfromid=11212 AND folder='outbox',我們看到,在 cdb_pms 表中,msgfromid 字段已經(jīng)建立了索引,但是,folder 字段并沒有。目前這個(gè)表已經(jīng)有記錄 7823 條。顯然,這會(huì)對(duì)查詢?cè)斐梢欢ㄓ绊?。于是為其建立索引?
mysql> ALTER TABLE `cdb_pms` ADD INDEX ( `folder` );
Query OK, 7823 rows affected (1.05 sec)
Records: 7823 Duplicates: 0 Warnings: 0
繼續(xù)檢查:
mysql> show processlist;
+------+------+----------------+---------+---------+------+------------+--------
--------------------------------------------------------------------------------
--------------+
| Id | User | Host | db | Command | Time | State | Info
|
+------+------+----------------+---------+---------+------+------------+--------
--------------------------------------------------------------------------------
--------------+
| 1583 | root | localhost:2616 | history | Query | 0 | statistics | SELECT
t.tid, t.closed, f.*, ff.* , f.fid AS fid
FROM cdb_threads t
INNER JOIN cdb_forums f |
+------+------+----------------+---------+---------+------+------------+--------
--------------------------------------------------------------------------------
--------------+
1 rows in set (0.00 sec)
這條 SQL 語句是針對(duì)最重要的數(shù)據(jù)表 cdb_threads 進(jìn)行操作的,由于 show processlist 沒有將這條 SQL 語句全部顯示完全,經(jīng)對(duì)比 Discuz 論壇的源碼,此SQL語句的原型位于 common.inc.php 的 Line 283,內(nèi)容如下:
$query = $db->query("SELECT t.tid, t.closed,".(defined('SQL_ADD_THREAD') ?
SQL_ADD_THREAD : '')." f.*, ff.* $accessadd1 $modadd1, f.fid AS fid
FROM {$tablepre}threads t
INNER JOIN {$tablepre}forums f ON f.fid=t.fid
LEFT JOIN {$tablepre}forumfields ff ON ff.fid=f.fid $accessadd2 $modadd2
WHERE t.tid='$tid'".($auditstatuson ? '' : " AND t.displayorder>=0")." LIMIT 1");
經(jīng)檢查,數(shù)據(jù)表 cdb_threads, 并沒有針對(duì) displayorder 字段建立索引。在 discuz 論壇中,displayorder字段多次參與了 Where 子句的比較。于是為其建立索引:
mysql> ALTER TABLE `cdb_threads` ADD INDEX ( `displayorder` );
Query OK, 110330 rows affected (2.36 sec)
Records: 110330 Duplicates: 0 Warnings: 0
此時(shí) cpu 已經(jīng)輕微下降了一部分。
繼續(xù)檢查,發(fā)現(xiàn) 下面這條 discuz 的 SQL 語句,也導(dǎo)致負(fù)荷增加,這條語句位于 rss.php 程序中的第 142 行。
$query = $db->query("SELECT t.tid, t.readperm, t.price, t.author, t.dateline, t.subject, p.message
FROM {$tablepre}threads t
LEFT JOIN {$tablepre}posts p ON p.tid=t.tid AND p.first=1
WHERE t.fid='$fid' AND t.displayorder>=0
ORDER BY t.dateline DESC LIMIT $num");
在這個(gè) Order by 子句中,用到了 cdb_threads 表中的 dataline 字段。這個(gè)字段是用來存儲(chǔ) unixtime 的時(shí)間戳,在整個(gè)論壇程序中,大部分時(shí)候數(shù)據(jù)的排序也是基于這個(gè)字段,竟然沒有建立索引。于是加上:
mysql> ALTER TABLE `cdb_threads` ADD INDEX ( `dateline` );
Query OK, 110330 rows affected (12.27 sec)
Records: 110330 Duplicates: 0 Warnings: 0
查找占用 CPU 高負(fù)茶的 SQL 語句,是一件麻煩而又枯燥的事,需要一條一條排除、分析。后面的工作,都是依此類推,經(jīng)過檢查,共查出有八處地方,需要增加索引,如果你也碰到了 discuz 5.5.0 論壇導(dǎo)致 cpu 占用 100% 的情況,可以直接將下列語句復(fù)制過去,在 mysql 的命令行下執(zhí)行即可:
ALTER TABLE `cdb_pms` ADD INDEX ( `folder` );
ALTER TABLE `cdb_threads` ADD INDEX ( `displayorder` );
ALTER TABLE `cdb_threads` ADD INDEX ( `dateline` );
ALTER TABLE `cdb_threads` ADD INDEX ( `closed` );
ALTER TABLE `cdb_threadsmod` ADD INDEX ( `dateline` );
ALTER TABLE `cdb_sessions` ADD INDEX ( `invisible` );
ALTER TABLE `cdb_forums` ADD INDEX ( `type` );
ALTER TABLE `cdb_forums` ADD INDEX ( `displayorder` );
注意:“cdb_” 是 discuz 論壇的默認(rèn)數(shù)據(jù)表前綴。如果你的表名前綴不是 “cdb_”,則應(yīng)該改成你對(duì)應(yīng)的表名。例如:my_threads, my_pms 等等。
完成這些結(jié)構(gòu)的優(yōu)化之后,整個(gè)系統(tǒng)的 CPU 負(fù)荷在 10%~20%左右震蕩,問題解決。
我很奇怪,設(shè)計(jì)數(shù)據(jù)庫結(jié)構(gòu),是一個(gè)數(shù)據(jù)庫開發(fā)人員的基本功,discuz 論壇好歹也是一個(gè)發(fā)展了有六七年的論壇了,為何數(shù)據(jù)庫結(jié)構(gòu)設(shè)計(jì)得如此糟糕?我想也許有如下三個(gè)原因:
- 數(shù)據(jù)庫開發(fā)人員設(shè)計(jì)時(shí)本身的疏忽
- 故意留下的缺陷,當(dāng)普通論壇沒有上數(shù)量級(jí)的記錄時(shí),不會(huì)感覺到這個(gè)問題,當(dāng)數(shù)據(jù)量增大(例如千萬級(jí)),此問題突現(xiàn),以便針對(duì)用戶提供個(gè)性服務(wù)收取服務(wù)費(fèi).呵呵,估且以最大的惡意來猜測(cè)此事,玩笑而已,不必當(dāng)真。:)
- 另一個(gè)可能就是用戶的論壇是從低版本升級(jí)而來,程序升了級(jí),但數(shù)據(jù)結(jié)構(gòu)也許沒有做相應(yīng)的更新
附1: 補(bǔ)充筆記 2007-07-09
今天查看網(wǎng)站日志的 reffer, 發(fā)現(xiàn)在 discuz 的官方論壇上,有人就此文引起了一些爭(zhēng)論: http://www.discuz.net/thread-673887-1-1.html。discuz 的管理員和管理員有如下言論:
引用自 cnteacher:
恰恰相反,discuz 的優(yōu)化措施和數(shù)據(jù)庫的索引是按照大規(guī)模論壇設(shè)計(jì)的。
TO 一樓:數(shù)據(jù)庫結(jié)構(gòu)的設(shè)計(jì)都是按照程序應(yīng)用來進(jìn)行的,使用任何非Discuz! 標(biāo)準(zhǔn)版本以外的代碼和程序,或者變更標(biāo)準(zhǔn)數(shù)據(jù)結(jié)構(gòu),均可能遇到不可預(yù)知的各種問題。
引用自 童虎:
你們可以看看xxxxx, xxxx之類的比較大型的網(wǎng)站,這種網(wǎng)站使用dz論壇都沒有問題,說明dz標(biāo)準(zhǔn)程序是沒有問題,出現(xiàn)樓主說的情況,多半屬于服務(wù)器或者安裝一些插件造成的
顯然將問題推給插件的原因是不正確的.舉個(gè)簡(jiǎn)單的例子:在最新的 discuz 5.5.0 forumdisplay.php 第183 行,有如下語句:
$query = $db->query("SELECT uid, groupid, username, invisible, lastactivity, action FROM {$tablepre}sessions WHERE $guestwhere fid='$fid' AND invisible=0");這里的 invisible 并沒有建立索引。本文中有評(píng)論認(rèn)為 session 表是內(nèi)存表, 速度會(huì)很快。理論是如此。不過我在 show processlist 中,觀察到上面這條語句占用了大量 CPU, 所以也將其一并加上了 index。cdb_threads 中的 closed 等字段, 也多次參與 where 運(yùn)算, 也沒有建立索引。這些運(yùn)算的語句, 是 discuz 自己的程序中的。
附2: 補(bǔ)充筆記 2007-11-11
自從這篇筆記發(fā)表以來,在我的這篇文章的評(píng)論、以及我的聯(lián)系消息中,就經(jīng)常收到許多下面兩種類型的評(píng)論和郵件:一、許多技術(shù)人員批評(píng)我胡說八道、Dizcus 論壇不需要做優(yōu)化或者不能亂建索引的;二、許多使用Dizcus 的站長(zhǎng)找我“冰天雪地裸體跪求”解決他們的 CPU 占用 100% 的問題。一、關(guān)于 MySQL 數(shù)據(jù)庫優(yōu)化技術(shù)上的爭(zhēng)論,我的觀點(diǎn)再次聲明如下:
- 技術(shù)上的爭(zhēng)論是可以放開了討論的。而我的水平也確實(shí)只是半瓶水,對(duì)數(shù)據(jù)庫的理論知識(shí)也只懂這么點(diǎn),牛牛們的批評(píng),我虛心接心,非常感謝。但是,評(píng)論里的批評(píng)不要上升到人身攻擊,否則,我的地盤我作主,直接刪除。
- 數(shù)據(jù)庫的優(yōu)化,要涉及到的方方面面很多。關(guān)說理論是沒有用的,得靠事實(shí)說話。一個(gè)千萬級(jí)數(shù)據(jù)庫的實(shí)例優(yōu)化說明不了問題,兩個(gè)千萬級(jí)的數(shù)據(jù)庫優(yōu)化也許還說明不了問題,但我相信,三個(gè)、四個(gè)、五個(gè)總是可以說明問題的,--截止到 2007.11.09,我已經(jīng)幫助朋友優(yōu)化過五個(gè)記錄數(shù)超過 1000 萬的 discuz 論壇了。我想事實(shí)勝于雄辯:優(yōu)化之前,cpu 都是 100%;優(yōu)化之后,cpu 降到 30%~40% 左右。沒錯(cuò),做 ADD INDEX 會(huì)增加數(shù)據(jù)庫 INSERT/UPDATE 時(shí)的開銷,但別忘了論壇最主要的操作,是 SELECT 查詢。
二、關(guān)于找我?guī)兔?B>解決數(shù)據(jù)庫優(yōu)化的評(píng)論和郵件,答復(fù)如下:
- 數(shù)據(jù)庫的優(yōu)化,不同的版本有不同的實(shí)際情況,優(yōu)化一個(gè) database,短則三兩小時(shí),慢則半天一天。請(qǐng)大家理解這個(gè)中年老男人養(yǎng)家的壓力,我的精力有限,不可能一一幫到。
- 對(duì)于沒有收入的個(gè)人網(wǎng)站,我可以在周六周日的空余時(shí)間內(nèi)幫忙。請(qǐng)事先與我聯(lián)系好。
- 對(duì)于有收入的網(wǎng)站,嗯嗯,自覺點(diǎn),請(qǐng)帶價(jià)格與我聯(lián)系,或者直接安排美女請(qǐng)我吃飯,否則免談。:) 請(qǐng)不要來信問“優(yōu)化我們這個(gè)論壇你要多少費(fèi)用?”這樣沒營(yíng)養(yǎng)的話,而是直接說“幫我們優(yōu)化 XXXX 論壇, XXXX RMB 可以不?”,我覺得合適就做。大家都很忙,我的時(shí)間很值錢,你要我自己報(bào)價(jià),我怕嚇著你。
- 請(qǐng)通過 http://www.xiaohui.com/support/ 與我聯(lián)系。不要在評(píng)論里留個(gè) QQ 號(hào)然后要我加你,我不會(huì)時(shí)時(shí)盯著評(píng)論看。
附3: 補(bǔ)充筆記 2007-11-17: 關(guān)于裝有首頁四格插件的 dz 論壇導(dǎo)致 MySQL 占用 大量CPU 的分析
今天手機(jī)巴士的站長(zhǎng)( http://bbs.sj84.com )找到我,他的基于 Discuz 的論壇,也存在 CPU 占用 100% 的問題,服務(wù)器從 Win 2003 換到 CentOS,內(nèi)存 2G, CPU 1.86G, 數(shù)據(jù):cdb_threads 4 萬,cdb_posts 96 萬,cdb_members 35 萬,已經(jīng)按我上面文章所說的優(yōu)化過索引。按說這個(gè)配置足夠運(yùn)行論壇了,但問題一直得不到解決。經(jīng)過調(diào)試,將慢查詢的結(jié)果 dump 到 /usr/local/mysql/var/localhost-slow.log,運(yùn)行 /usr/local/mysql/bin/mysqldumpslow /usr/local/mysql/var/localhost-slow.log 查看,結(jié)合 show processlist 命令,發(fā)現(xiàn)慢查詢集中在下列語句:
SELECT t.*, f.name FROM cdb_threads t, cdb_forums f WHERE t.fid<>'S' AND f.fid=t.fid AND f.fid NOT IN (N,N,N,N) AND t.closed NOT LIKE 'S' AND t.replies !=N AND t.displayorder>=N ORDER BY t.views DESC LIMIT N, N然而搜索 Dizcus 論壇的源碼,并沒有找到這行代碼。懷疑是插件的原因。經(jīng)查,論壇裝了首頁四格的插件,這行語句位于 include/toplist.php 中: 仔細(xì)檢查這行代碼,發(fā)現(xiàn)存在許多性能或語法規(guī)范上的問題:
- AND t.closed NOT LIKE 'S':t.closed 是數(shù)值字段,不應(yīng)該用 LIKE 'S' 的形式參與比較?!?
- ORDER BY t.views: t.views 在 dizcus 的原始數(shù)據(jù)表中,是沒有做索引的。
- SELECT t.*: 這種寫法,是不被推薦的。如果要選擇某個(gè)表內(nèi)的所有字段,最好是按實(shí)全部寫出來,例如:select t.aa, t.bb, t.cc, t.dd, ...
- WHERE t.fid <> 'S': t.fid 是數(shù)值型字段,不應(yīng)該寫成 字符比較的形式。這個(gè)對(duì)性能影響不大,是個(gè)編程規(guī)范的問題。
- ....
toplist.php 的其他三條 sql 語句,都存在這些問題。如果要針對(duì)他的 sql 語句去優(yōu)化 MySQL 結(jié)構(gòu),會(huì)帶來不良的后果;如果直接改他的 toplist.php 程序,如果站長(zhǎng)以后升級(jí) toplist.php 又怕帶來不兼容問題。于是我建議他干脆關(guān)閉首頁四格插件。
關(guān)閉首頁四格插件之后,CPU 降到 18% 左右震蕩,表現(xiàn)非常良好。
如果是我來寫首頁四格的程序,我不會(huì)采用這種方案,我會(huì)用定時(shí)15分鐘或30分鐘查詢一次數(shù)據(jù)庫,將結(jié)果寫入 TXT 文件或臨時(shí)表,然后程序再從中讀取,效率會(huì)高許多。
結(jié)論:
- 如果裝了插件的論壇碰到 CPU 高負(fù)荷時(shí),建議關(guān)掉插件再評(píng)估性能。
- 慎裝第三方插件。沒事不要亂插。:)
附4:補(bǔ)充筆記 2008-06-10:這篇文章,重要的是分析過程,而不是進(jìn)行修正的那段代碼
最近有幾位在評(píng)論中留言,以及給我 EMAIL,說到將我在文中給出的 那8行 ALTER TABLE 代碼,在他的出現(xiàn) CPU 100% 的 dz 論壇上,用了之后沒有效果。
我的解釋如下:這段代碼,不能保證在 dz 的所有版本下通用。具體問題,要具體分析。這段代碼,是我在 Dizcus! 5.5.0 的版本的基本下進(jìn)行分析得出的校正結(jié)果。其他的版本,不敢保證。
這篇文章的重點(diǎn),并不是作為結(jié)果的這段代碼,而是如何得出這個(gè)結(jié)果的分析過程。知道了原理,你自己一樣可以分析。
- mysql中迅速插入百萬條測(cè)試數(shù)據(jù)的方法
- mysql 動(dòng)態(tài)生成測(cè)試數(shù)據(jù)
- MySQL與MSSQl使用While語句循環(huán)生成測(cè)試數(shù)據(jù)的代碼
- MySQL 百萬級(jí)分頁優(yōu)化(Mysql千萬級(jí)快速分頁)
- mysql千萬級(jí)數(shù)據(jù)大表該如何優(yōu)化?
- Mysql limit 優(yōu)化,百萬至千萬級(jí)快速分頁 復(fù)合索引的引用并應(yīng)用于輕量級(jí)框架
- 如何優(yōu)化Mysql千萬級(jí)快速分頁
- 30個(gè)mysql千萬級(jí)大數(shù)據(jù)SQL查詢優(yōu)化技巧詳解
- mysql千萬級(jí)數(shù)據(jù)分頁查詢性能優(yōu)化
- MySQL如何快速的創(chuàng)建千萬級(jí)測(cè)試數(shù)據(jù)
相關(guān)文章
mysql定時(shí)刪除過期數(shù)據(jù)記錄的簡(jiǎn)單方法
今天小編就為大家分享一篇mysql定時(shí)刪除過期數(shù)據(jù)記錄的簡(jiǎn)單方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-08-08在OneProxy的基礎(chǔ)上實(shí)行MySQL讀寫分離與負(fù)載均衡
基于Libevent機(jī)制實(shí)現(xiàn),單個(gè)實(shí)例可以實(shí)現(xiàn)25萬的SQL轉(zhuǎn)發(fā)能力,用一個(gè)OneProxy節(jié)點(diǎn)可以帶動(dòng)整個(gè)MySQL集群,為業(yè)務(wù)發(fā)展貢獻(xiàn)一份力量,下面由小編來為大家簡(jiǎn)單說說2019-05-05Window下如何恢復(fù)被刪除的Mysql8.0.17 Root賬戶及密碼
這篇文章主要介紹了Window下如何恢復(fù)被刪除的Mysql8.0.17 Root賬戶及密碼,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-02-02將.sql文件導(dǎo)入到MySQL數(shù)據(jù)庫具體步驟
MySQL有多種方法導(dǎo)入多個(gè).sql文件,下面這篇文章主要介紹了將.sql文件導(dǎo)入到MySQL數(shù)據(jù)庫的具體步驟,文中將實(shí)現(xiàn)步驟介紹的非常詳細(xì),需要的朋友可以參考下2023-10-10數(shù)據(jù)庫連接池以及sequelize實(shí)現(xiàn)增刪改查等操作指南
Sequelize的連接需要傳入?yún)?shù),并且可以配置開啟線程池、讀寫分庫等操作,下面這篇文章主要給大家介紹了關(guān)于數(shù)據(jù)庫連接池以及sequelize實(shí)現(xiàn)增刪改查等操作的相關(guān)資料,需要的朋友可以參考下2022-08-08MySQL中SHOW DATABASES語句查看或顯示數(shù)據(jù)庫
在MySQL中,可使用SHOW DATABASES語句來查看或顯示當(dāng)前用戶權(quán)限范圍以內(nèi)的數(shù)據(jù)庫,下面就來介紹一下如何使用,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02mysql insert的幾點(diǎn)操作(DELAYED,IGNORE,ON DUPLICATE KEY UPDATE )
DELAYED 做為快速插入,并不是很關(guān)心失效性,提高插入性能。2010-04-04mybatis+mysql 使用存儲(chǔ)過程生成流水號(hào)的實(shí)現(xiàn)代碼
這篇文章主要介紹了mybatis+mysql 使用存儲(chǔ)過程生成流水號(hào)的實(shí)現(xiàn)代碼,需要的朋友可以參考下2018-01-01MySQL開發(fā)中存儲(chǔ)函數(shù)與觸發(fā)器使用示例
這篇文章主要為大家詳細(xì)介紹了MySQL中存儲(chǔ)函數(shù)的創(chuàng)建與觸發(fā)器的設(shè)置,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,需要的可以參考一下2023-01-01