Discuz 7.2坑爹集錦-SQL篇 -update 2012.02.09

Discuz 7.2坑爹集錦-SQL篇

DZ使用的是MySQL的MyISAM引擎,特色是簡單快速,很是適合網絡扁平數據。當數據量超過必定規模(大概300萬),數據關聯複雜(錶鏈接增多)後性能急劇降低。而且在高讀寫併發時鎖表嚴重(MyISAM是表鎖,InnoDB有行鎖),甚至致使表損壞。DZ7.2代碼中SQL寫法存在不標準的問題,雖然不影響執行但對維護遷移是個問題。對數據類型檢查也不嚴格,好比int字段插入的數據可能爲空字符串,讓mysql的兼容性來實現到0的自動轉換。至於查詢優化,這個因數據不一樣而實際變化很大沒有一個完美的解決,不過優化作很差也不要拖後腿呀:有些SQL低級錯誤對數據庫性能影響不小。也許SQL代碼是由對數據庫不瞭解的PHP程序員寫的,不過也應該有懂數據庫的人員來審查SQL相關代碼的吧。有些低級失誤很讓人無語:原本能夠用PHP代碼完成的事情卻要丟給數據庫作,雖然節省了PHP代碼不過卻致使DB負載大幅度增長。

整體來講一個系統最慢的一環是在數據庫,根源在於磁盤IO能力。數據庫性能、反應決定了整個系統的負載能力。因此應該儘快結束數據庫操做釋放數據庫資源,也避免PHP等待太久形成502錯誤(尤爲是fastcgi模式)


--------------------- --------------------- --------------------- ---------------------

類型:        條件缺失
坑爹指數:    ★★★
代碼:        member.php=64
$order = isset($order) && in_array($order, array('credits','gender','username')) ? $order : '';
代碼:        member.php=90
switch($order) {
                case 'credits': $orderadd = "ORDER BY credits DESC"; break;
                case 'gender':     $orderadd = "ORDER BY gender DESC"; break;
                case 'username': $orderadd = "ORDER BY username DESC"; break;
                default: $orderadd = 'ORDER BY uid'; $order = 'uid'; break;
            }
點評:        統計選項->會員列表沒法根據註冊日期排序。
FIX:        line64修改成    
$order = isset($order) && in_array($order, array('credits','gender','username', 'regdate')) ? $order : '';
line90修改成
switch($order) {
                case 'credits': $orderadd = "ORDER BY credits DESC"; break;
                case 'gender':     $orderadd = "ORDER BY gender DESC"; break;
                case 'username': $orderadd = "ORDER BY username DESC"; break;
                case 'regdate': $orderadd = " ORDER BY regdate DESC"; break;    // ADD
                default: $orderadd = 'ORDER BY uid'; $order = 'uid'; break;
            }


----------------------------------------------------------------------------------------


類型:        類型錯誤
坑爹指數:    ★★
代碼:        admin/forums.inc.php~1289
$query = $db->query("SELECT * FROM {$tablepre}threadtypes WHERE typeid IN ($typeids) AND special='' ORDER BY displayorder");
點評:        牛頭不對馬嘴,special字段明明是int類型卻去搜索''空字符串,還好這個表不會大,否則坑死人不償命

-----------------------------------------------------------------------------------------


類型:        負載分配
坑爹指數:    ★★★★★
代碼:        admin/attach.inc.php=169
$db->query("UPDATE {$tablepre}threads SET attachment='0' WHERE tid IN ($tids)".($attachtids ? " AND tid NOT IN ($attachtids)" : NULL));
代碼:        admin/attach.inc.php=176
$db->query("UPDATE {$tablepre}posts SET attachment='0' WHERE pid IN ($pids)".($attachpids ? " AND pid NOT IN ($attachpids)" : NULL));

點評:         懂點數據庫的都知道除非萬不得已不然應該避免使用「NOT IN」,使用的後果就是掃全表,若是數據量大磁盤性能再差點這一掃但是會掃出大菠蘿的喲:UPDATE命令執行時間將會很長而且將致使長時間鎖表從而阻塞住隊列中的其餘操做,最後致使SELECT都會嚴重阻塞,這時候網站基本就癱瘓了————頁面刷新緩慢,發帖失去響應重複刷新結果就成復讀機。因此應該均衡任務負荷,讓數據庫、PHP各自作擅長的工做而不是一股腦讓一方完成全部。尤爲數據庫是整個系統中最慢那一塊,應該避免讓它陷入重負荷而是及時執行完畢釋放資源不然它將會拖慢甚至拖垮系統。上面這語句其實能夠分解成兩步來執行:先執行一次SELECT查詢取出數據做爲要排除的部分與$tids數組作array_diff()運算,得出的結果再用做條件去執行UPDATE。雖然多了第一步查詢,但這個查詢是走搜索速度比全表掃快得多,整體下來性能提高明顯。即使要在一條SQL命令中執行本可使用子查詢方式,不過mysql不支持EXCEPT/MINUS結果集操做……

 

 

---------------------------------------------------------------------------------------------

 

 

類型:        多餘操做
坑爹指數:    ★★
代碼:        admin/atttach.inc.php=165
$query = $db->query("SELECT tid FROM {$tablepre}attachments WHERE tid IN ($tids) GROUP BY tid ORDER BY pid DESC");
點評:         可以使用「SELECT DISTINCT」來替代「GROUP BY」,可「ORDER BY」是啥意思呢?相關操做對結果集順序並未有要求,多餘的排序操做將會耗費CPU能力與內存佔用,結果將增長數據庫負載。只不過通常一個主題不會有海量附件,因此性能降低不明顯。

---------------------------------------------------------------------------------------------

類型:        多餘操做
坑爹指數:    ★★
代碼:        recyclebin.inc.php=160
do{
                $query = $db->query("SELECT f.name AS forumname, f.allowsmilies, f.allowhtml, f.allowbbcode, f.allowimgcode,
                    t.tid, t.fid, t.authorid, t.author, t.subject, t.views, t.replies, t.dateline,
                    p.message, p.useip, p.attachment, p.htmlon, p.smileyoff, p.bbcodeoff,
                    tm.uid AS moduid, tm.username AS modusername, tm.dateline AS moddateline, tm.action AS modaction
                    FROM {$tablepre}threads t
                    LEFT JOIN {$tablepre}posts p ON p.tid=t.tid AND p.first='1'
                    LEFT JOIN {$tablepre}threadsmod tm ON tm.tid=t.tid
                    LEFT JOIN {$tablepre}forums f ON f.fid=t.fid
                    WHERE t.displayorder='-1' $sql
                    GROUP BY t.tid ORDER BY t.dateline DESC LIMIT $ppp OFFSET ".(($pagetmp - 1) * $ppp));
                $pagetmp--;
            } while(!$query->rowCount() && $pagetmp);
點評:        "GROUP BY t.tid"是多餘的,由於主表是threads tid是PK,上方line45還有一處相似。也許此段代碼的大哥喜歡作菜。可廚藝不精,不知道何時該放什麼調料,因而手邊的調料瓶就都拿起來倒兩下,只要味道不難吃這菜就算完成了。寫代碼也如此,估摸着寫着寫着突然想起SQL還有「GROUP BY」的功能,隨手拈來搗入SQL中攪和攪和,結果正確味道正好。遂頓悟,不會作菜的廚子不是個好程序猿 :D



---------------------------------------------------------------------------------------------

類型:        多餘操做
坑爹指數:    ★
代碼:        stats.php=217
$query = $db->query("SELECT author, COUNT(*) AS posts FROM {$tablepre}posts WHERE dateline>='$timestamp'-86400 AND invisible='0' AND authorid>'0' GROUP BY author ORDER BY posts DESC LIMIT 1");
點評:        「AND authorid>'0'」 條件能夠刪除掉。這個條件毫無心義,只會讓數據庫在抓取row時過濾條件多一個結果卻沒差異。

---------------------------------------------------------------------------------------------


類型:        多餘操做
坑爹指數:    ★★
代碼:        include/requres.func.php
$query = $db->query("SELECT t.tid,t.fid,t.readperm,t.author,t.authorid,t.subject,t.dateline,t.lastpost,t.lastposter,t.views,t.replies,t.highlight,t.digest,t.typeid,t.sortid
            $sqlfrom WHERE t.readperm='0'
            $sql
            AND t.displayorder>='0'
            AND t.fid>'0'            <--------
            $attachadd
            ORDER BY t.$orderby DESC
            LIMIT $items OFFSET $startrow "
            );

點評:        難道t.fid能夠小於0?多此一舉徒勞無功 。可能這位老哥對於墨菲定律比較信服,越是怕fid小於0越是有可能出現,因而乾脆把坑……唔,是地基挖深一些,避免出現意外 :)



---------------------------------------------------------------------------------------------


類型:        多餘鏈接
坑爹指數:    ★★
代碼:        include/post.func.php=602 updateforumcount()
extract($db->fetch_first("SELECT COUNT(*) AS threadcount, SUM(t.replies)+COUNT(*) AS replycount
 FROM {$tablepre}threads t, {$tablepre}forums f
 WHERE f.fid='$fid' AND t.fid=f.fid AND t.displayorder>='0'"));
點評:        其實沒用到forums表的數據,對forums表的鏈接徹底是多餘的
FIX:      
extract($db->fetch_first("SELECT COUNT(*) AS threadcount, SUM(replies)+COUNT(*) AS replycount
 FROM {$tablepre}threads WHERE fid='$fid' AND displayorder>='0'"));



---------------------------------------------------------------------------------------------


類型:        條件模糊
坑爹指數:    ★
代碼:        admin/counter.inc.php=80
$queryt = $db->query("SELECT uid FROM {$tablepre}members LIMIT $current, $pertask");
點評:        查詢時SQL不嚴格未使用ORDER BY,致使結果集、結果順序不肯定。此頁面多個SQL均存在這個問題, 會致使分頁結果不可預料,尤爲是提取帖子(精華)分頁時!
FIX:       
$queryt = $db->query("SELECT uid FROM {$tablepre}members ORDER BY uid LIMIT $current, $pertask");



---------------------------------------------------------------------------------------------


類型:        條件惡劣
坑爹指數:    ★★★★★
代碼:        viewthread.php=354
$specialadd2 .= "AND (dp.stand='0' OR dp.stand IS NULL OR p.first='1')";
代碼:        viewthread.php=378
$thread['replies'] = $sdb->result_first("SELECT COUNT(*) FROM {$tablepre}posts p LEFT JOIN {$tablepre}debateposts dp ON p.pid=dp.pid WHERE p.tid='$tid' AND (dp.stand='0' OR dp.stand IS NULL)");
代碼:        include/task.func.php=134
$nextnewbietaskid = intval($db->result_first("SELECT t.taskid FROM {$tablepre}tasks t LEFT JOIN {$tablepre}mytasks mt ON mt.taskid=t.taskid AND mt.uid='$discuz_uid' WHERE mt.taskid IS NULL AND t.available='2' AND t.newbietask='1' ORDER BY t.newbietask DESC LIMIT 1"));

點評:        會數據庫的應該知道NULL值不會走索引,除非創建ISNULL索引,做NULL查詢將會掃全表致使性能暴跌! DZ數據庫建表風格是都採用NOT NULL約束,PHP代碼風格也是不作NULL的判斷。在字段已經明確NOT NULL約束條件下還採用(dp.stand='0' OR dp.stand IS NULL)這樣條件,對mt.taskid不使用mt.taskid>0判斷,若是不是臨時工乾的那就基本上是存心考古的…… php

FIX:    include/task.func.php=134不能簡單刪除 ISNULL判斷,不然將致使新手任務沒法結束,把 mt.taskid IS NULL 改爲 mt.taskid = 0 便可html

 



---------------------------------------------------------------------------------------------


類型:        條件惡劣
坑爹指數:    ★★★★★
代碼:        ucs/control/admin/pm.php~150 onclear()
$uids = 0;
代碼:        admin/prune.inc.php~220
$forums = '0';
代碼:        admin/prune.inc.php~230
$uids = '-1';
代碼:        viewthreads.php~220
$attachpids = -1;
代碼:        topicadmin.php~102 前臺刪除帖子
$pids = 0;
代碼:        topicadmin.php~109 前臺刪除帖子
$pids .= ','.$post['pid'];
代碼:        admin/threads.inc.php~622
$tids = 0;
代碼:        admin/forums.inc.php~1289
$query = $db->query("SELECT * FROM {$tablepre}threadtypes WHERE typeid IN ($typeids) AND special='' ORDER BY displayorder");
代碼:        modcp/moderate.inc.php~286
WHERE pid IN (0,".implode(',', $pidarray).")");
代碼:        admin/moderate.inc.php=727
$db->query("UPDATE {$tablepre}posts SET invisible='0' WHERE pid IN (0,".implode(',', $pidarray).")");
代碼:        include/misc.func.php~289
$db->query("UPDATE $tablepre$table SET $viewscol=$viewscol+'$views' WHERE $idcol IN (0$ids)" );
代碼:
$str = $comma = '';
    foreach (..) {
        $str .= $comma. 'something';
        $comma = ',';
    }

點評:        不知道爲啥,對於搜索id,DZ代碼風格是先給$id變量賦值個不可能的值(好比0或者''),而後在迭代中對此變量拼接字符串。這將會在兩個方面影響性能。一,若是迭代結果並沒有真實id被追加,那麼由於$id由於非空因此依舊會作一次無結果的查詢。白白浪費數據庫鏈接資源和PHP資源;二,即使有真實id須要查詢,雖然$id包含了不可能值(好比0,-1)但這個不可能值依舊會被用做合法的查詢條件值,結果是額外開銷。我不肯定是否會致使更多數據庫性能開銷:一般都是在PK上查詢,走的索引天然是UNIQUE————一個值有匹配即中止對該值的繼續查找判斷,當最後一個值有索引匹配就中止搜索————若是存在一個合法的不可能的值將會致使數據庫掃完整個索引來匹配該值!若是我對數據庫索引搜索工做方式判斷正確,那麼DZ這個附加不可能值SQL條件的作法將是至關影響性能很是坑爹的,由於這種風格在DZ7.2代碼中很常見。



------------------------------------------------------------------------------


類型:        條件惡劣
坑爹指數:    ★★★★★
代碼:        forumdisplay.php=317
$forumstickycount = $stickycount = $stickytids = 0;
點評:        對tid搜索包含0, 版塊精華SQL相似以下,將會致使掃全索引. 而且影響到即便沒有全局置頂主題也會作一樣查詢,很是坑爹
SELECT t.* FROM cdb_threads t
            WHERE t.tid IN (0,110) AND t.displayorder IN (2, 3, 4)
            ORDER BY displayorder DESC, lastpost DESC
            LIMIT  1 OFFSET 0

FIX:     在line338
if(($start_limit && $start_limit > $stickycount) || !$stickycount || $filterbool) {

以前加上過濾mysql

if ($stickytids) {
    $tarr = array();
    $stickytids = explode(',', $stickytids);
    foreach ($stickytids as $s_id) {
        $s_id = intval($s_id) && $s_id > 0 && $tarr[] = $s_id;
    }
    $stickytids = implode(',', $tarr);
    unset($tarr);
}
else {
    $stickytids = '';
}

而後line348:
$querysticky = $sdb->query("SELECT t.* FROM {$tablepre}threads t
        WHERE t.tid IN ($stickytids) AND t.displayorder IN (2, 3, 4)
        ORDER BY displayorder DESC, $orderby $ascdesc
        LIMIT $start_limit, ".($stickycount - $start_limit < $tpp ? $stickycount - $start_limit : $tpp));
修改爲:
if ($stickytids) {
        $querysticky = $sdb->query("SELECT t.* FROM {$tablepre}threads t
            WHERE t.tid IN ($stickytids) AND t.displayorder IN (2, 3, 4)
            ORDER BY displayorder DESC, $orderby $ascdesc
            LIMIT  ".($stickycount - $start_limit < $tpp ? $stickycount - $start_limit : $tpp). ' OFFSET '. $start_limit);
    }
    else {
        $querysticky = false;
    }



-------------------------------------------------------------------------


類型:        流程問題
坑爹指數:    ★★★★★
代碼:        search.php=166+
點評:        DZ搜索是在實際搜索前先對cdb_searchindex的進行查詢來判斷是否存存在flood以及是否存在相同搜索(條件),每一個用戶兩次搜索間隔判斷,每分鐘服務器接受搜索閾值判斷都是在此表上實現。此方式極大缺陷:是執行了查詢以後再根據結果判斷是否flood,而並不是把flood與否做爲條件去查詢。也就是說不管是否flood,任何查詢都會先走一次cdb_searchindex掃描————即便DZ系統提示你「兩次搜索時間太短」讓你待會兒再搜索,這只是減輕了對得到指望結果的數據庫表的壓力而絲絕不會減輕cdb_searchindex的壓力!攻擊者能夠持續提交查詢數據讓cdb_searchindex表查詢壓力巨大從而影響數據庫性能,尤爲是在長時間運行的系統上,cdb_searchindex表緩存的查詢數據越多越明顯。

 

---------------------------------------------------------------------------------------------


類型:        多餘鏈接
坑爹指數:    ★★★
代碼:        include/requres.func.php~450程序員

case 'weekposts':
                $week = gmdate('w', $timestamp) - 1;
                $week = $week != -1 ? $week : 6;
                $historytime = mktime(0, 0, 0, date('m', $timestamp), date('d', $timestamp) - $week, date('Y', $timestamp));
                $sql = "SELECT DISTINCT (p.author) AS username,p.authorid AS uid,COUNT(p.pid) AS postnum FROM {$tablepre}posts p LEFT JOIN {$tablepre}memberfields mf ON mf.uid = p.authorid WHERE p.dateline>=$historytime GROUP BY p.authorid, p.author ORDER BY postnum DESC";

點評:        輸出字段並未使用memberfields表中字段,貌似MySQL在這種狀況下並不會所以而放棄鏈接操做,對此表的鏈接徒耗資源。
sql

 


---------------------------------------------------------------------------------------------

類型:        邏輯錯誤
坑爹指數:    ★
代碼:        admin/recyclebin.inc.php~145數據庫

$threadcount = $db->result_first("SELECT count(*)
                FROM {$tablepre}threads t
                LEFT JOIN {$tablepre}threadsmod tm ON tm.tid=t.tid
                WHERE t.displayorder='-1' $sql");

點評:        此SQL未對threadsmod做以下do while()循環中相似的GROUP BY處理,若是一個回收站中主題被反覆刪除恢復那麼就會出現「符合條件的回收站主題數」不爲0但下方無列表顯示這種狀況

FIX1:        添加一條GROUP BY語句過濾threadsmod的結果。但限於MySQL不標準的GROUP BY語法,其結果(順序)也許非正確數組

$threadcount = $db->result_first("SELECT count(*)
                FROM {$tablepre}threads t
                LEFT JOIN {$tablepre}threadsmod tm ON tm.tid=t.tid
                GROUP BY tm.tid ORDER BY tm.dateline DESC, t.dateline DESC
                WHERE t.displayorder='-1' $sql");

 


版權曾經擁有,歡迎網上分享
轉載請保留連接 http://my.oschina.net/u/126398/blog/39255緩存

相關文章
相關標籤/搜索