Post view

終於解決了瀏覽相簿的MySQL查詢指令,幾乎超過1秒〜4.7秒以上的頭痛問題

請先閱讀前一篇文章,那已經是「02/19/2017」大約半年前的事情:

相簿的查詢MySQL指令,幾乎都是超過1秒以上

為了得知哪些MySQL指令的執行速度異常緩慢,我在my.cnf設定檔案裡面加入這兩個指令。意思是只要資料庫的query查詢指令超過1秒以上,就會記錄在slow.log檔案裡面。當然,這個記錄會放在MySQL安裝的data檔案夾裡面。

long_query_time = 1
slow_query_log = ON

資料庫專用的Mac mini伺服器主機(只有安裝MySQL,並沒有安裝其他Server服務)的情況比較好的時候,mysql_query的資料查詢時間需要花1.3秒左右:

# Time: 170217 10:41:54
# Query_time: 1.346684  Lock_time: 0.000125 Rows_sent: 1  Rows_examined: 474884
SET timestamp=1487295714;
SELECT `bx_photos_main`.`ID` as `medID`, `bx_photos_main`.`Categories` as `Categories`, `bx_photos_main`.`Owner` as `medProfId`, `bx_photos_main`.`Title` as `medTitle`, `bx_photos_main`.`Uri` as `medUri`, `bx_photos_main`.`Desc` as `medDesc`, `bx_photos_main`.`Tags` as `medTags`, `bx_photos_main`.`Date` as `medDate`, `bx_photos_main`.`Views` as `medViews`, `bx_photos_main`.`Status` as `Approved`, `bx_photos_main`.`Featured` as `Featured`, `bx_photos_main`.`Rate` as `Rate`, `bx_photos_main`.`RateCount` as `RateCount`, `bx_photos_main`.`Ext` as `medExt`, `bx_photos_main`.`Size` as `medSize`, `bx_photos_main`.`Hash` as `Hash`,  COUNT(`share1`.`ID`) as `Count`,  `sys_albums`.`ID` as `albumId`, `sys_albums`.`Caption` as `albumCaption`, `sys_albums`.`Uri` as `albumUri`, `sys_albums`.`AllowAlbumView`, `sys_albums_objects`.`obj_order`
                     FROM `bx_photos_main`
                     LEFT JOIN `bx_photos_main` as `share1` USING (`Owner`)
                    
                INNER JOIN `sys_albums_objects` ON `sys_albums_objects`.`id_object`=`bx_photos_main`.`ID`
                INNER JOIN `sys_albums` ON (`sys_albums`.`ID`=`sys_albums_objects`.`id_album` AND `sys_albums`.`Type`='bx_photos')
           
                     WHERE `bx_photos_main`.`Uri`='14395448347056' GROUP BY `share1`.`Owner` LIMIT 1;

MySQL資料庫伺服器主機的情況很差的時候,甚至完全相同的mysql_query查詢指令,竟然需要花費4.7秒!

/usr/local/mysql/bin/mysqld, Version: 5.6.37-log (MySQL Community Server (GPL)). started with:
Tcp port: 0  Unix socket: (null)
Time                 Id Command    Argument
# Time: 170719  2:51:14
# Query_time: 4.736743  Lock_time: 0.000352 Rows_sent: 1  Rows_examined: 497338
SET timestamp=1500400274;
SELECT `bx_photos_main`.`ID` as `medID`, `bx_photos_main`.`Categories` as `Categories`, `bx_photos_main`.`Owner` as `medProfId`, `bx_photos_main`.`Title` as `medTitle`, `bx_photos_main`.`Uri` as `medUri`, `bx_photos_main`.`Desc` as `medDesc`, `bx_photos_main`.`Tags` as `medTags`, `bx_photos_main`.`Date` as `medDate`, `bx_photos_main`.`Views` as `medViews`, `bx_photos_main`.`Status` as `Approved`, `bx_photos_main`.`Featured` as `Featured`, `bx_photos_main`.`Rate` as `Rate`, `bx_photos_main`.`RateCount` as `RateCount`, `bx_photos_main`.`Ext` as `medExt`, `bx_photos_main`.`Size` as `medSize`, `bx_photos_main`.`Hash` as `Hash`,  COUNT(`share1`.`ID`) as `Count`,  `sys_albums`.`ID` as `albumId`, `sys_albums`.`Caption` as `albumCaption`, `sys_albums`.`Uri` as `albumUri`, `sys_albums`.`AllowAlbumView`, `sys_albums_objects`.`obj_order`
                     FROM `bx_photos_main`
                     LEFT JOIN `bx_photos_main` as `share1` USING (`Owner`)
                    
                INNER JOIN `sys_albums_objects` ON `sys_albums_objects`.`id_object`=`bx_photos_main`.`ID`
                INNER JOIN `sys_albums` ON (`sys_albums`.`ID`=`sys_albums_objects`.`id_album` AND `sys_albums`.`Type`='bx_photos')
           
                     WHERE `bx_photos_main`.`Uri`='14016060603254' GROUP BY `share1`.`Owner` LIMIT 1;

雖然MySQL的slow.log檔案告訴你這個SELECT指令花費太多時間,但真正的問題是,這是由哪一個PHP程式檔案?哪一行的PHP程式碼執行這個SELECT指令?重要的事情卻完全沒有記錄!

這也是必然的結果,畢竟MySQL只負責執行傳送給MySQL Server要執行的指令,至於到底是哪一行PHP程式碼?哪一個PHP程式檔案?那就不是MySQL要負責記錄的事情。於是,我只好從上面的SELECT指令的一些蛛絲馬跡去搜尋整個BOONEX的海豚程式碼,比如搜尋「INNER JOIN」或是搜尋「JOIN `sys_albums_objects`」「JOIN `sys_albums`」等等。

不幸的是,光是搜尋「INNER JOIN」這樣的關鍵字,會得到109個搜尋結果在42個PHP檔案。

搜尋「JOIN `sys_albums_objects`」會找到14個搜尋結果,在14個PHP檔案。

搜尋「JOIN `sys_albums」會找到15個搜尋結果,在15個PHP檔案。

所以只要花一點時間,檢查一下這14、15個PHP程式檔案,一定能找到執行那個SELECT指令的PHP程式碼... 如果你跟我一樣,也是這樣想的話,就錯了!因為我乾脆刪除這15個檔案裡面全部的「JOIN `sys_albums_objects`」與「JOIN `sys_albums」的指令(當然是每次只刪除一個指令,希望能找到真正執行SELECT指令的PHP程式碼),試圖故意讓程式出錯,但是非常不幸的是slow.log檔案仍然繼續記錄新的SELECT指令,而且瀏覽照片的動作仍然正常執行!

竟然會有這種離譜的事情!我明明就已經刪除所有的「JOIN `sys_albums_objects`」與「JOIN `sys_albums」指令(測試的動作,當然是在另外的地下社群網站裡面進行,以免影響正式運作的insoler網站),slow.log檔案仍然繼續記錄新的SELECT指令,而且還是有「INNER JOIN `sys_albums_objects`」與「INNER JOIN `sys_albums`」的加入表格的指令!

 雖然ayaka室友建議我使用MySQL的view指令來避免每次執行mysql_query查詢指令,都要重新加入另外兩個表格,但是我竟然找不到執行這個SELECT指令的PHP程式碼!於是這件事就因為「觸礁」卡死了很長的一段時間。

某一天我突然想到,這個SELECT指令,會不會根本就沒有寫在PHP程式碼裡面,而是從MySQL資料庫的某一筆資料讀入,然後再執行這個SELECT指令?確實是有這種可能性!於是就去搜尋 .SQL 資料庫匯出的檔案,果然被我找到在sys_stat_site資料表裡面有一筆資料,真的有儲存left JOIN `sys_albums_objects`這樣的指令,但是就算刪除這筆資料,只要瀏覽照片的話,slow.log檔案仍然會繼續記錄新的SELECT指令!

可說是非常的令人氣餒!刪除PHP程式碼以及資料庫裡面全部的「INNER JOIN `sys_albums_objects`」與「INNER JOIN `sys_albums`」指令,想不到slow.log檔案仍然繼續記錄SELECT指令!為什麼???

雖然說「山不轉路轉」但是要用什麼方式才能找到,到底是哪一個PHP檔案的哪一行PHP程式碼執行SELECT指令?因為想不出什麼辦法,就這樣又卡死了很久。

某一天我突然想到,乾脆利用「debug_backtrace」指令,在mysql_query查詢指令的副程式裡面,加入用來追蹤除錯的「debug_backtrace」指令,看看到底是哪些PHP的function副程式會呼叫mysql_query資料庫查詢指令?而且傳給mysql_query的參數又是什麼?

我想透過「debug_backtrace」指令,應該可以找到有某一個參數,一定有傳送給mysql_query的「INNER JOIN `sys_albums_objects`」與「INNER JOIN `sys_albums`」的參數,而且因為某一個的SELECT指令,同時需要查詢:

「bx_photos_main」、「sys_albums_objects」、「sys_albums」

這三個資料表,一定會在某一個PHP的副程式,傳送這些參數給mysql_query。我只要用「debug_backtrace」指令,再去「逆向追蹤」應該就能找到了!

事實上證明,我的想法並沒有錯誤,我真的找到明明全部的PHP程式碼裡面已經沒有「INNER JOIN `sys_albums_objects`」與「INNER JOIN `sys_albums`」(全部都被我刪除),但是在「debug_backtrace」指令儲存的array陣列變數裡面,仍然可以搜尋到這兩個參數指令!

雖然我的想法並沒有錯誤,而且我也真的找到了「INNER JOIN `sys_albums_objects`」與「INNER JOIN `sys_albums`」的SELECT指令。但問題是「debug_backtrace」記錄陣列變數儲存在檔案裡面,竟然高達100MB〜400MB以上!而且往往陣列變數內容太長,連鎖呼叫的副程式也非常的多!找到歸找到,幾乎等於「大海撈針」...

於是我又被超大檔案的「debug_backtrace」記錄又卡死很久,總是找不到真正執行SELECT指令的PHP程式碼。

又過了很長一段時間,我才想到,用「人工」的方式來搜尋、追蹤「debug_backtrace」的超長記錄實在是太笨了!乾脆寫程式來搜尋還比較快!直接記錄傳給mysql_query的參數裡面有包含「INNER JOIN `sys_albums_objects`」與「INNER JOIN `sys_albums`」的時候,才儲存那一段副程式呼叫記錄的話,就會變得簡短很多,也比較容易找到到底是由哪些PHP程式檔案,哪一行PHP副程式呼叫mysql_query。

「debug_backtrace」的執行結果會像是這樣。這只是我另外寫的簡短的測試程式。

Array
(
    [0] => Array
        (
            [file] =>debug_backtrace.php
            [line] => 21
            [function] => test
            [args] => Array

花了好幾個月的時間,我終於找到包含「INNER JOIN `sys_albums_objects`」與「INNER JOIN `sys_albums`」的SELECT指令是寫在「BxDolFilesDb.php」的「function getFileInfo」這個副程式,主要的資料庫執行指令被BOONEX寫成這樣:

        if (!$bSimple) {
            // album joins
            $oAlbum = new BxDolAlbums($this->_oConfig->getMainPrefix());
            $sqlAlbumJoin = "
                INNER JOIN `{$oAlbum->sAlbumObjectsTable}` ON `{$oAlbum->sAlbumObjectsTable}`.`id_object`=`{$this->sFileTable}`.`{$this->aFileFields['medID']}`
                INNER JOIN `{$oAlbum->sAlbumTable}` ON (`{$oAlbum->sAlbumTable}`.`ID`=`{$oAlbum->sAlbumObjectsTable}`.`id_album` AND `{$oAlbum->sAlbumTable}`.`Type`='" . $this->_oConfig->getMainPrefix() . "')
            ";
            $sqlAlbumFields = "`{$oAlbum->sAlbumTable}`.`ID` as `albumId`, `{$oAlbum->sAlbumTable}`.`Caption` as `albumCaption`, `{$oAlbum->sAlbumTable}`.`Uri` as `albumUri`, `{$oAlbum->sAlbumTable}`.`AllowAlbumView`, `{$oAlbum->sAlbumObjectsTable}`.`obj_order`";

            $sqlCount = "COUNT(`share1`.`{$this->aFileFields['medID']}`) as `Count`, ";
            $sqlCountJoin = "LEFT JOIN `{$this->sFileTable}` as `share1` USING (`{$this->aFileFields['medProfId']}`)";
            $sqlGroup = "GROUP BY `share1`.`{$this->aFileFields['medProfId']}`";
        } else
            $sqlFields = rtrim($sqlFields, ', ');
        $sqlQuery = "SELECT $sqlFields $sqlCount $sqlAlbumFields
                     FROM `{$this->sFileTable}`
                     $sqlCountJoin
                     $sqlAlbumJoin
                     WHERE $sqlCondition $sqlGroup LIMIT 1";

你可以看到,BOONEX原廠竟然是使用「變數」的方式,寫成「INNER JOIN `{$oAlbum->sAlbumObjectsTable}`」而不是直接寫「INNER JOIN `sys_albums_objects`」難怪我怎麼搜尋都找不到!而且記錄在slow.log檔案裡面的SELECT指令明明很長(雖然不算複雜),真正的程式碼卻寫成極為簡短的:

        $sqlQuery = "SELECT $sqlFields $sqlCount $sqlAlbumFields
                     FROM `{$this->sFileTable}`
                     $sqlCountJoin
                     $sqlAlbumJoin
                     WHERE $sqlCondition $sqlGroup LIMIT 1";

這麼簡短的SELECT指令,帶入很長的變數內容,完全展開以後,就會真的成為前面slow.log檔案裡面記錄的那個SELECT指令。另外,雖然花了很多時間,終於找到了問題是出在「BxDolFilesDb.php」這個PHP程式檔案的「function getFileInfo」副程式裡面。但是要怎樣才能解決SELECT指令處理速度龜速的問題點?

雖然是在「地下社群網站」進行各種測試、實驗,當然是不能直接拿「function getFileInfo」副程式來實驗,因為已經非常複雜的程式,繼續進行各種「執行速度改善計畫」很可能又會徒勞無功!所以我另外寫了一個簡短的「MySQLtest.php」程式,來測試各種不同的SELECT指令,看看哪一種方式的執行速度可以有明顯的改善?

我把展開以後的SELECT指令複製到我寫的「MySQLtest.php」程式裡面來測試看看。為了避免MySQL內建的cache快取功能會導致時間計算錯誤,只好每次執行「MySQLtest.php」程式之前,都先停止MySQL Server,再重新啟動MySQL Server,這樣的話,就能清空cache快取,模擬第一次執行這個很慢的SELECT指令。你可以看到竟然花了2.7秒。

BOONEX在「BxDolFilesDb.php」原版的MySQL指令:

2.7240350246429

2017-07-24 12:44:35=MySQL test, getRow, aImageInfo=Array
(
    [medID] => 138133
    [Categories] => 結婚式
    [medProfId] => 7
    [medTitle] => IMG_9923
    [medUri] => 15003955776221
    [medDesc] => IMG_9923
    [medTags] => 結婚式
    [medDate] => 1500395577
    [medViews] => 2
    [Approved] => approved
    [Featured] => 0
    [Rate] => 0
    [RateCount] => 0
    [medExt] => CR2
    [medSize] => 5472x3648
    [Hash] => 818e6dce2e71d30860511327f0160bb8
    [Count] => 89450
    [albumId] => 4904
    [albumCaption] => 社長の息子さんが結婚・ウェディング・披露宴 MWB 7:21 親戚の記念写真
    [albumUri] => 社長の息子さんが結婚-ウェディング-披露宴-MWB-7-21-親戚の記念写真
    [AllowAlbumView] => 3
    [obj_order] => 0
)

 

因為MySQL內建的cache快取功能的關係,第二次以後再執行相同的程式、相同的SELECT指令就會變得超快無比,只需要0.0003秒!竟然快了7782倍!第一次執行SELECT指令,可能要花1〜4秒的時間,這裡是2.7秒,第二次就只要不到0.01秒的時間,幾乎完全不需要等待!

BOONEX在「BxDolFilesDb.php」原版的MySQL指令:

0.0003509521484375

2017-07-24 14:50:37=MySQL test, getRow, aImageInfo=Array
(
    [medID] => 138133
    [Categories] => 結婚式
    [medProfId] => 7
    [medTitle] => IMG_9923
    [medUri] => 15003955776221
    [medDesc] => IMG_9923
    [medTags] => 結婚式
    [medDate] => 1500395577
    [medViews] => 2
    [Approved] => approved
    [Featured] => 0
    [Rate] => 0
    [RateCount] => 0
    [medExt] => CR2
    [medSize] => 5472x3648
    [Hash] => 818e6dce2e71d30860511327f0160bb8
    [Count] => 89450
    [albumId] => 4904
    [albumCaption] => 社長の息子さんが結婚・ウェディング・披露宴 MWB 7:21 親戚の記念写真
    [albumUri] => 社長の息子さんが結婚-ウェディング-披露宴-MWB-7-21-親戚の記念写真
    [AllowAlbumView] => 3
    [obj_order] => 0
)

 

但是,誰會一直瀏覽同一張照片?當MySQL沒有cache這筆資料的情況,同樣的SELECT指令,又會變得超龜速無比!所以想要依靠MySQL的cache是沒什麼意義。因為從slow.log檔案裡面,不斷新增執行時間超過1秒以上的SELECT指令就可以知道,還是有許多時候,會有某些人瀏覽一些舊的照片,導致MySQL的cache沒有記錄那些資料,於是就必須花1〜4秒的時間來執行這個SELECT指令。

在前面這篇文章,ayaka室友建議「生成view比較好」:

相簿的查詢MySQL指令,幾乎都是超過1秒以上

所以我使用CREATE VIEW bnw_photos_main指令建立一個新的檢視表:

CREATE VIEW bnw_photos_main (medID, Categories, medProfId, medTitle, medUri, medDesc, medTags, medDate, medViews, Approved, Featured, Rate, RateCount, medExt, medSize, Hash, albumId, albumCaption, albumUri, AllowAlbumView, obj_order) AS
SELECT `bx_photos_main`.`ID` as `medID`, `bx_photos_main`.`Categories` as `Categories`, `bx_photos_main`.`Owner` as `medProfId`, `bx_photos_main`.`Title` as `medTitle`, `bx_photos_main`.`Uri` as `medUri`, `bx_photos_main`.`Desc` as `medDesc`, `bx_photos_main`.`Tags` as `medTags`, `bx_photos_main`.`Date` as `medDate`, `bx_photos_main`.`Views` as `medViews`, `bx_photos_main`.`Status` as `Approved`, `bx_photos_main`.`Featured` as `Featured`, `bx_photos_main`.`Rate` as `Rate`, `bx_photos_main`.`RateCount` as `RateCount`, `bx_photos_main`.`Ext` as `medExt`, `bx_photos_main`.`Size` as `medSize`, `bx_photos_main`.`Hash` as `Hash`, `sys_albums`.`ID` as `albumId`, `sys_albums`.`Caption` as `albumCaption`, `sys_albums`.`Uri` as `albumUri`, `sys_albums`.`AllowAlbumView`, `sys_albums_objects`.`obj_order`
        FROM `bx_photos_main`
        LEFT JOIN `bx_photos_main` as `share1` USING (`Owner`)
        INNER JOIN `sys_albums_objects` ON `sys_albums_objects`.`id_object`=`bx_photos_main`.`ID`
        INNER JOIN `sys_albums` ON (`sys_albums`.`ID`=`sys_albums_objects`.`id_album` AND `sys_albums`.`Type`='bx_photos')

打開phpMyAdmin網頁,就會在「檢視表」看到我建立的bnw_photos_main資料表:

新的bnw_photos_main檢視表格的欄位名稱,就是在BxDolFilesDb.php的查詢的結果。

透過新增的檢視表,要搜尋一筆資料,只要執行非常簡單的MySQL的SELECT指令即可:

$sqlQuery = "SELECT * FROM `bnw_photos_main` WHERE `medUri`='14996109934719'";
$aImageInfo = _getsqlQuery($sqlQuery);

可是,就算透過新增的bnw_photos_main檢視表來搜尋一張照片的資料,查詢時間還是需要花1.1秒左右。當然,這個時間的計算結果,同樣是重新啟動MySQL Server,再執行mysqltest.php的測試結果。這樣可以避免因為MySQL內建的cache快取功能,導致得到錯誤的資料查詢時間。

完全不使用新增的bnw_photos_main檢視表,使用傳統的方式,分別查詢所需的bx_photos_main、sys_albums_objects、sys_albums三個資料表,再組成所需的查詢結果的陣列變數,反而只需要0.1秒左右的時間,還是比bnw_photos_main檢視表的執行速度要快10倍左右!

運氣不好的時候,還可能會比前面的1.1秒的查詢時間還要更慢。

使用 檢視表;bnw_photos_main 的版本:

1.6381180286407

2017-07-25 08:13:02=MySQL test, getPhotoDbInfo, aImageInfo=Array
(
    [medID] => 138133
    [Categories] => 結婚式
    [medProfId] => 7
    [medTitle] => IMG_9923
    [medUri] => 15003955776221
    [medDesc] => IMG_9923
    [medTags] => 結婚式
    [medDate] => 1500395577
    [medViews] => 2
    [Approved] => approved
    [Featured] => 0
    [Rate] => 0
    [RateCount] => 0
    [medExt] => CR2
    [medSize] => 5472x3648
    [Hash] => 818e6dce2e71d30860511327f0160bb8
    [albumId] => 4904
    [albumCaption] => 社長の息子さんが結婚・ウェディング・披露宴 MWB 7:21 親戚の記念写真
    [albumUri] => 社長の息子さんが結婚-ウェディング-披露宴-MWB-7-21-親戚の記念写真
    [AllowAlbumView] => 3
    [obj_order] => 0
)

 

為了避免依靠MySQL的cache,又可以提高SELECT指令的執行速度,我測試了許多方式,總算找到一個令人滿意的方法!同樣在關閉MySQL Server再重新啟動,然後執行相同的程式,你可以看到這個SELECT指令版本的執行時間可以縮短到只要「0.1」秒就能得到幾乎完全相同的SELECT查詢結果!

getPhotoDbInfo 分別執行 3 次 mysql_query 的版本:

0.10050106048584

2017-07-24 12:44:31=MySQL test, getPhotoDbInfo, aImageInfo=Array
(
    [medID] => 138133
    [Categories] => 結婚式
    [medProfId] => 7
    [medTitle] => IMG_9923
    [medUri] => 15003955776221
    [medDesc] => IMG_9923
    [medTags] => 結婚式
    [medDate] => 1500395577
    [medViews] => 2
    [Approved] => approved
    [Featured] => 0
    [Rate] => 0
    [RateCount] => 0
    [medExt] => CR2
    [medSize] => 5472x3648
    [Hash] => 818e6dce2e71d30860511327f0160bb8
    [Count] => 0
    [albumId] => 4904
    [albumCaption] => 社長の息子さんが結婚・ウェディング・披露宴 MWB 7:21 親戚の記念写真
    [albumUri] => 社長の息子さんが結婚-ウェディング-披露宴-MWB-7-21-親戚の記念写真
    [AllowAlbumView] => 3
    [obj_order] => 0
)

 

以前在瀏覽照片時,按「<ー」「ー>」方向鍵瀏覽「下一張」、「前一張」照片的時候,因為MySQL資料庫還沒有cache快取過資料的關係,會需要等1〜3秒(運氣差的時候甚至可能會超過5秒以上)的時間,才能看到照片。現在就會變得流暢許多,可以用相當快的速度翻頁。laughing

insolerphotoraw023.jpg

我的解決方法很簡單,就是在function getFileInfo副程式的前面加入一段if判斷指令,只要是處理照片的時候,就直接呼叫我寫的另外一段SELECT查詢副程式,而且直接return離開這個副程式,這樣就能略過BOONEX原廠的龜速SELECT查詢。

    function getFileInfo ($aIdent, $bSimple = false, $aFields = array())
    {
        if (isset($aIdent['fileUri']))
            $sqlCondition = "`{$this->sFileTable}`.`{$this->aFileFields['medUri']}`='" . process_db_input($aIdent['fileUri'], BX_TAGS_STRIP) . "'";
        elseif (isset($aIdent['fileId']))
            $sqlCondition = "`{$this->sFileTable}`.`{$this->aFileFields['medID']}`='" . (int)$aIdent['fileId'] . "'";
        else
            return;
        if ($this->sFileTable == 'bx_photos_main' && !$bSimple) //BNW
            return $this->_getPhotoDbInfo(process_db_input($aIdent['fileUri']));

 

當然,這是因為我不知道到底還有哪些功能會執行getFileInfo副程式,所以暫時只處理bx_photos_main,也就是照片資料表的部分,其他的資料表我就先不處理。這麼做的主要理由是我並沒有完全了解整個BOONEX原廠的海豚程式碼,不知道會有哪些模組會呼叫這個getFileInfo副程式,如果直接修改程式裡面的SELECT查詢指令,只怕其他模組呼叫到這個getFileInfo副程式的時候,就會發生重大的資料庫查詢錯誤!

只加上一小段我寫的程式碼,而且直接return返回原本的呼叫程式,略過之後的程式碼,至少可以確保處理bx_photos_main照片資料表的時候,會得到正確的SELECT查詢結果,而且執行速度只要0.1秒。經過cache以後還會更快一點點,但是竟然沒有比最前面的BOONEX原廠的「JOIN `sys_albums_objects`」「JOIN `sys_albums`」還要快!反而還要更慢一點。但無論如何都在0.1秒以內,還是比需要1〜4秒的查詢時間,還要快很多。

不過,光是「略過BOONEX原廠的程式碼」並不是什麼好辦法,我在前一篇文章裡面有提到,或許試著新增「INDEX索引表」可以提高許多查詢的速度。用phpMyAdmin打開insoler資料庫的「bx_photos_main」資料表,然後按一下「結構」按鈕,就會看到有許多欄位都有建立「INDEX索引表」,但是以「Uri」來瀏覽照片時,偏偏「Uri」欄位沒有建立索引,這或許是前面原版的MySQL的SELECT查詢指令,竟然要花2.724秒以上時間的主要原因。

所以我按一下「Uri」欄位後面的「唯一」按鈕,就會看到「你確定要執行ALTER TABLE `bx_photos_main` ADD UNIQUE(`Uri`);」這個指令嗎?按一下「確定」。

大約花10秒左右,就能建立好「Uri」網址欄位的唯一的索引表格(也就是Uri網址的內容不可以重複,事實上Uri也絕對不能重複,因為系統必須靠Uri來顯示對應的照片,每張照片都必須有完全不同的Uri網址)。你可以看到在「Uri」後面多了一個「鑰匙」的符號。

在「Uri」網址欄位新增INDEX索引表以前,MySQL的SELECT資料查詢的執行速度大約要2.65秒左右。在進行速度測試之前,當然還是要重新啟動MySQL Server,然後再執行MySQLtest.php測試程式。

BOONEX在「BxDolFilesDb.php」原版的 MySQL 指令:

2.6540589332581

2017-07-26 14:01:50=MySQL test, getRow, aImageInfo=Array
(
    [medID] => 138133
    [Categories] => 結婚式
    [medProfId] => 7
    [medTitle] => IMG_9923
    [medUri] => 15003955776221
    [medDesc] => IMG_9923
    [medTags] => 結婚式
    [medDate] => 1500395577
    [medViews] => 5
    [Approved] => approved
    [Featured] => 0
    [Rate] => 0
    [RateCount] => 0
    [medExt] => CR2
    [medSize] => 5472x3648
    [Hash] => 818e6dce2e71d30860511327f0160bb8
    [Count] => 89640
    [albumId] => 4904
    [albumCaption] => 社長の息子さんが結婚・ウェディング・披露宴 MWB 7:21 親戚の記念写真
    [albumUri] => 社長の息子さんが結婚-ウェディング-披露宴-MWB-7-21-親戚の記念写真
    [AllowAlbumView] => 3
    [obj_order] => 0
)

 

在「Uri」網址欄位新增INDEX索引表之後,你可以看到MySQL的SELECT資料查詢的執行速度縮短到只要0.54秒左右。當然,在進行速度測試之前,必須重新啟動MySQL Server,然後再執行MySQLtest.php測試程式。

BOONEX在「BxDolFilesDb.php」原版的 MySQL 指令:

0.54152798652649

2017-07-26 13:58:36=MySQL test, getRow, aImageInfo=Array
(
    [medID] => 138133
    [Categories] => 結婚式
    [medProfId] => 7
    [medTitle] => IMG_9923
    [medUri] => 15003955776221
    [medDesc] => IMG_9923
    [medTags] => 結婚式
    [medDate] => 1500395577
    [medViews] => 5
    [Approved] => approved
    [Featured] => 0
    [Rate] => 0
    [RateCount] => 0
    [medExt] => CR2
    [medSize] => 5472x3648
    [Hash] => 818e6dce2e71d30860511327f0160bb8
    [Count] => 89640
    [albumId] => 4904
    [albumCaption] => 社長の息子さんが結婚・ウェディング・披露宴 MWB 7:21 親戚の記念写真
    [albumUri] => 社長の息子さんが結婚-ウェディング-披露宴-MWB-7-21-親戚の記念写真
    [AllowAlbumView] => 3
    [obj_order] => 0
)

 

所以,你可以看到完全不修改任何PHP程式碼,光是幫「Uri」網址欄位新增INDEX索引表,資料庫的查詢時間,就可以從2.6秒縮短到0.5秒,幾乎縮短了5倍的查詢時間,提高5倍的速度。

在「Uri」網址欄位擁有INDEX索引表的協助下,我另外寫的SELECT查詢程式碼的執行速度,甚至只要0.001秒就能完成!換句話說,在雙重加速的情況下,資料庫的SELECT查詢時間,一下就可以從以前的「2.6秒」縮短到「0.001秒」!執行速度提升了大約2600倍左右!非常的不可思議!laughing

0.0010411739349365

2017-07-26 13:58:35=MySQL test, getPhotoDbInfo, aImageInfo=Array
(
    [medID] => 135524
    [Categories] => Roma 歐洲旅行
    [medProfId] => 3
    [medTitle] => DSC_8813
    [medUri] => 14936307201121
    [medDesc] => DSC_8813
    [medTags] => Roma 歐洲旅行
    [medDate] => 1493630720
    [medViews] => 2
    [Approved] => approved
    [Featured] => 0
    [Rate] => 0
    [RateCount] => 0
    [medExt] => NEF
    [medSize] => 4352x2868
    [Hash] => 1fb158a0bddd5c1f42358cf03d77def9
    [Count] => 0
    [albumId] => 4797
    [albumCaption] => St. Peter's Basilica サン・ピエトロ大聖堂
    [albumUri] => St-Peter-s-Basilica-サン-ピエトロ大聖堂
    [AllowAlbumView] => 3
    [obj_order] => 0
)

 

既然只要在「Uri」欄位新增INDEX索引表,就能大幅度提升資料的查詢速度,至少可以提升5倍到2600倍左右,為什麼在第一篇文章的時候,我只有提到「試著建立INDEX索引表」,但是拖了半年都沒有著手進行?

理由非常簡單,當時只知道「建立INDEX索引表」應該可以加快SELECT的查詢速度,但卻因為完全不知道MySQL處理速度超慢無比,到底是卡在什麼地方?在沒有完全理解BOONEX的PHP程式碼,以及MySQL的資料表的行為以前,當然就不知道到底要在哪裡「建立INDEX索引表」,直到現在才完全弄清楚,也才能真正的對症下藥,徹底解決問題,而不是頭痛醫頭、腳痛醫腳。

那麼,已經在「Uri」欄位新增INDEX索引表,就能大幅度提升資料的查詢速度之後,再用CREATE VIEW bnw_photos_main重新建立一個檢視表呢?資料的搜尋速度會變得更快嗎?

使用 檢視表: bnw_photos_main 的版本:

0.61469197273254

2017-07-27 01:03:31=MySQL test, getPhotoDbInfo, aImageInfo=Array
(
    [medID] => 138133
    [Categories] => 結婚式
    [medProfId] => 7
    [medTitle] => IMG_9923
    [medUri] => 15003955776221
    [medDesc] => IMG_9923
    [medTags] => 結婚式
    [medDate] => 1500395577
    [medViews] => 5
    [Approved] => approved
    [Featured] => 0
    [Rate] => 0
    [RateCount] => 0
    [medExt] => CR2
    [medSize] => 5472x3648
    [Hash] => 818e6dce2e71d30860511327f0160bb8
    [albumId] => 4904
    [albumCaption] => 社長の息子さんが結婚・ウェディング・披露宴 MWB 7:21 親戚の記念写真
    [albumUri] => 社長の息子さんが結婚-ウェディング-披露宴-MWB-7-21-親戚の記念写真
    [AllowAlbumView] => 3
    [obj_order] => 0
)

 

你可以看到在建立在「Uri」欄位新增INDEX索引表之前的「查詢時間還是需要花1.1秒左右」,現在也加快到0.6秒左右。但還是比只要0.001秒就能完成慢很多。可見得CREATE VIEW雖然方便,但只能加快一些速度,還是可能因為VIEW表格太複雜,導致MySQL的執行速度太慢。

以上的測試全部是在「地下社群網站」進行的測試,使用的是一台Mac mini (Late 2009) 2.53 GHz Intel Core 2 Duo、8 GB 1067 MHz DDR3,只有雙核心處理器的Mac mini電腦,最多只能安裝8GB記憶體。原廠只有使用傳統的2.5吋硬碟,在硬碟損壞以後,已經被我換成一個高速的256GB SSD固態硬碟。

insoler的MySQL Server主機則是安裝在一台Mac mini (Late 2012) 2.6 四核心 GHz Intel Core i7、16 GB 1600 MHz DDR3。一台4核心處理器的Mac mini電腦。搭載的記憶體是Apple原廠的16 GB 1600 MHz DDR3,而且出廠時就已經是安裝到最高記憶體容量。儲存媒體也是Apple原廠的251GB SSD固態硬碟。也就是說,整台Mac mini全部都是Apple原廠配備。

當然,處理器也已經從標準的「2.3GHz 四核心 Intel Core i7 處理器 (Turbo Boost 可達 3.3GHz),配備 6MB L3 快取」在訂購時選配更好的「可訂製 2.6GHz 四核心 Intel Core i7 (Turbo Boost 可達 3.6GHz)。」處理器。

因此在Mac mini 2.53 GHz Intel Core 2 Duo的電腦上「竟然花了2.7秒」,在insoler正式的資料庫主機的Mac mini 2.6GHz 四核心 Intel Core i7,其實只要花「1.34秒」。很顯然「四核心 Intel Core i7」的運算速度比「雙核心Core 2 Duo」快了一倍!這是在沒有在「Uri」建立INDEX索引之前的MySQL資料搜尋速度。

BOONEX在「BxDolFilesDb.php」原版的 MySQL 指令:

1.3444330692291

2017-07-26 15:00:15=MySQL test, getRow, aImageInfo=Array
(
    [medID] => 138133
    [Categories] => 結婚式
    [medProfId] => 7
    [medTitle] => IMG_9923
    [medUri] => 15003955776221
    [medDesc] => IMG_9923
    [medTags] => 結婚式
    [medDate] => 1500395577
    [medViews] => 5
    [Approved] => approved
    [Featured] => 0
    [Rate] => 0
    [RateCount] => 0
    [medExt] => CR2
    [medSize] => 5472x3648
    [Hash] => 818e6dce2e71d30860511327f0160bb8
    [Count] => 89640
    [albumId] => 4904
    [albumCaption] => 社長の息子さんが結婚・ウェディング・披露宴 MWB 7:21 親戚の記念写真
    [albumUri] => 社長の息子さんが結婚-ウェディング-披露宴-MWB-7-21-親戚の記念写真
    [AllowAlbumView] => 3
    [obj_order] => 0
)

 

雖然「Mac mini 2.6GHz 四核心 Intel Core i7」與「Mac mini 2.53 GHz 二核心 Core 2 Duo」的時脈速度,只有從2.53GHz提高到2.6GHz。但很顯然MySQL的運算速度能快一倍,都是因為「四核心」比「二核心」多了一倍核心數的關係!

在bx_photos_main資料表的「Uri」欄位建立INDEX索引表,關閉並重新啟動MySQL Server,重新執行MySQLtest.php測試程式,在完全沒有改變任何MySQL查詢指令的情況下,就可以看到資料的搜尋速度從「1.34秒」提高到「0.26」秒!幾乎提高了5.1倍的執行速度!

BOONEX在「BxDolFilesDb.php」原版的 MySQL 指令:

0.26585507392883

2017-07-26 15:02:42=MySQL test, getRow, aImageInfo=Array
(
    [medID] => 138133
    [Categories] => 結婚式
    [medProfId] => 7
    [medTitle] => IMG_9923
    [medUri] => 15003955776221
    [medDesc] => IMG_9923
    [medTags] => 結婚式
    [medDate] => 1500395577
    [medViews] => 5
    [Approved] => approved
    [Featured] => 0
    [Rate] => 0
    [RateCount] => 0
    [medExt] => CR2
    [medSize] => 5472x3648
    [Hash] => 818e6dce2e71d30860511327f0160bb8
    [Count] => 89640
    [albumId] => 4904
    [albumCaption] => 社長の息子さんが結婚・ウェディング・披露宴 MWB 7:21 親戚の記念写真
    [albumUri] => 社長の息子さんが結婚-ウェディング-披露宴-MWB-7-21-親戚の記念写真
    [AllowAlbumView] => 3
    [obj_order] => 0
)

 

雖然提高到「0.26」秒,已經有很顯著的效能提升,但這只是因為「Uri」使用INDEX索引表而已。BOONEX原廠的MySQL搜尋指令太過複雜,我自己重寫的3次查詢版本,反而只要「0.0013」秒而已!當然,這也是經過重新啟動MySQL,再重新測試的結果。而不是MySQL的Cache快取的結果。

0.0013399124145508

2017-07-26 15:02:41=MySQL test, getPhotoDbInfo, aImageInfo=Array
(
    [medID] => 135524
    [Categories] => Roma 歐洲旅行
    [medProfId] => 3
    [medTitle] => DSC_8813
    [medUri] => 14936307201121
    [medDesc] => DSC_8813
    [medTags] => Roma 歐洲旅行
    [medDate] => 1493630720
    [medViews] => 2
    [Approved] => approved
    [Featured] => 0
    [Rate] => 0
    [RateCount] => 0
    [medExt] => NEF
    [medSize] => 4352x2868
    [Hash] => 1fb158a0bddd5c1f42358cf03d77def9
    [Count] => 0
    [albumId] => 4797
    [albumCaption] => St. Peter's Basilica サン・ピエトロ大聖堂
    [albumUri] => St-Peter-s-Basilica-サン-ピエトロ大聖堂
    [AllowAlbumView] => 3
    [obj_order] => 0
)

 

在insoler的會員數還不是很多的時候,在沒有在「Uri」建立INDEX索引表之前的MySQL資料搜尋速度需要「1.34秒」,經過建立INDEX索引表,以及改寫SELECT搜尋指令,分成3次搜尋,再重新組合成相同的查詢結果,現在只要「0.0013」秒。我想這個速度已經令人相當滿意。至少在這幾年內,只要insoler會員沒有大幅度暴增的情況下,雖然有點老舊的「Mac mini 2.6GHz 四核心 Intel Core i7」而且還是2012年機種,應該還可以繼續使用3-5年左右吧。

當然,我還是期望insoler會員可以大幅度暴增,就算要花經費添購更高級的、更高速的電腦,我還是願意投資。

蘇言霖 2017/07/24 2 1064
Comments
Order by: 
Per page:
 
  •  ayaka: 
     

    我個人角度是從資料庫最佳化來看的,肯花功夫去精簡程式碼肯定是最好的yell

    cache還有index其實有策略可以調整的tongue-out您可以嘗試一下smile

     
     2017/07/281 replies1 replies 
    0 points
     
Rate
0 votes
Post info
蘇言霖
「超級懶貓級」社群網站站長
2017/07/24 (2461 days ago)
Actions