2012-03-22 81 views
3

我聽說用MySQL數據庫準備好的語句可以提高速度,如果查詢多次完成,我認爲我在項目中有一個理想的情況。但我跑了一些基準,發現完全相反。我是否使用這些陳述錯誤(不是預備陳述的理想情況),還是他們沒有我想象的那麼快?準備好的聲明沒有速度優勢?

這種情況是比賽結果網格。有多個學校參加多個活動,每個學校都有每個活動的分數。爲了得到一個個別學校的分數的所有事件,它需要一個左一個SQL查詢中的連接,如:

SELECT e.`id`, e.`name`, c.`competing`, c.`raw`, c.`final` FROM `events` e LEFT JOIN `scores` c ON e.`id`=c.`event_id` WHERE c.`school_id`=:school_id; 

我寫了兩個PHP測試腳本來對樣本數據(200個事件)上運行,使用本機PDO對象(prepare()/bindValue()/​​與query()):

EDIT低於建議修改測試(香草查詢需要取回,取不同的ID,並結合循環外的準備)。只給出了一個溫和的速度優勢,現在,預處理語句:

預處理語句:

$start = microtime(true); 
$sql = 'SELECT e.`id`, e.`name`, c.`competing`, c.`raw`, c.`final` FROM `events` e LEFT JOIN `scores` c ON e.`id`=c.`event_id` WHERE c.`school_id`=:school_id'; 
echo $sql."<br />\n"; 
$stmt = $db->prepare($sql); 
$sid = 0; 
$stmt->bindParam(':school_id', $sid); 
for ($i=0; $i<$max; $i++) { 
    $sid = rand(1,499); 
    $stmt->execute(); 
    $rs = $stmt->fetchAll(); 
} 
$delta = bcsub(microtime(true), $start, 4); 
echo "<strong>Overall time:</strong> $delta<br />\n"; 
echo "<strong>Average time:</strong> ".($delta/$max)."<br />\n"; 

香草查詢:

set_time_limit(15); // Add time for each run 
$start = microtime(true); 
$sql = 'SELECT e.`id`, e.`name`, c.`competing`, c.`raw`, c.`final` FROM `events` e LEFT JOIN `scores` c ON e.`id`=c.`event_id` WHERE c.`school_id`={$sid}'; 
echo $sql."<br />\n"; 
for ($i=0; $i<$max; $i++) { 
    $sid = rand(1,499); 
    $stmt = $db->query("SELECT e.`id`, e.`name`, c.`competing`, c.`raw`, c.`final` FROM `events` e LEFT JOIN `scores` c ON e.`id`=c.`event_id` WHERE c.`school_id`={$sid}"); 
    $rs = $stmt->fetchAll(); 
} 
$delta = bcsub(microtime(true), $start, 4); 
echo "<strong>Overall time:</strong> $delta<br />\n"; 
echo "<strong>Average time:</strong> ".($delta/$max)."<br />\n"; 

我取了同一所學校的活動成績(學校編號#10),並將$max設置爲10,000,我得到的結果顯示vanilla query快30%(25.72秒比36.79)。我做錯了,還是準確的說準備好的聲明即使在重複的情況下也不會更快?

編輯更新的測試現在得到33.95秒準備與34.10香草。 Huzzah,準備好的陳述更快。但是隻有10,000次迭代只需幾分之一秒。可能是因爲我的查詢不是那麼複雜(Prepared語句爲了他們的優勢緩存分析樹)?還是有更多的優化在這裏呢?

+1

如何使用存儲過程比較?你是否確定你有適當的索引? – 2012-03-22 15:44:57

+0

我不熟悉「存儲」過程;我得看看那個。在'scores'和'events'表有相應的索引('events'具有'id'字段的主要和'scores'作爲'school_id' /'event_id'一個兩列PRIMARY指數) – MidnightLightning 2012-03-22 15:55:00

+0

「存儲過程」很好。另一方面,「準備好的陳述」只是另一種工具。它們會產生一些開銷......但在適當的情況下,它們也可以產生性能遊戲,增加安全性並簡化代碼。這裏有一個很好的鏈接:http://blog.ulf-wendel.de/?p=187#procon – paulsm4 2012-03-22 15:56:23

回答

-2

看來,你可能不會比較蘋果和蘋果。

PDO::query()執行SQL語句,返回結果集作爲PDOStatement對象。

獲得實際的結果,你需要遍歷返回的對象,或與準備好的聲明,呼籲fetchAll()裝載整個結果集到一個數組

正確的香草查詢循環應該可能再是:

for ($i=0; $i<$max; $i++) { 
    $stmt = $db->query($sql); 
    $rs = $stmt->fetchAll(); 
} 

或替代地移除從所述準備語句循環的fetchAll()呼叫。

您還可以通過使用bindParam(),而不是這些人的bindValue()

$school_id = null; 
$stmt->bindParam(':school_id', $school_id); 
for ($i=0; $i<$max; $i++) { 
    $school_id = 10; 
    $stmt->execute(); 
    $rs = $stmt->fetchAll(); 
} 
+0

好點!移動循環之外的綁定會加快準備好的語句,並且向香草中添加提取速度會減慢它們的速度。現在我準備了33.95秒,而34.10香草。 Huzzah,準備好的陳述更快。但是隻有10,000次迭代只需幾分之一秒。可能是因爲我的查詢不那麼複雜? – MidnightLightning 2012-03-22 16:51:41

+0

對於更復雜的查詢,您很可能會看到更大的差異。看起來你有一個使用主鍵的連接,這對於數據庫來說確實不難評估。更多的連接,更大的數據集以及多個索引可供選擇將導致創建執行計劃時產生更多開銷。 – gapple 2012-03-22 22:15:33

+0

以及只有一個變量的查詢。 Prepared語句只允許在每次調用execute()時將參數發送到數據庫,這對於具有更多變量的較長查詢具有更大的優勢。 – gapple 2012-03-22 22:18:38

4

香草查詢每次執行完全相同的查詢,因此您只是測試「從查詢緩存中獲取結果」時間,而不是實際的查詢執行時間。這不是一個有效的測試。

你不得不做查詢建築物內循環,所以你每次都強迫一個新的查詢:

for ($i=0; $i<$max; $i++) { 
    $sql = <<<EOL 
SELECT e.id, e.name, c.competing, c.raw, c.final 
FROM events e 
LEFT JOIN scores c ON e.id=c.event_id 
WHERE c.school_id= $i; 
EOL; 
    $rs = $db->query($sql); 
} 
+0

在Prepared Statement中,每次都綁定相同的值。你是否在說,即使相同的價值被綁定,也沒有緩存? – 2012-03-22 15:48:37

+1

綁定不是一個'自由'的操作,它比反覆解析整個查詢稍便宜。 – 2012-03-22 15:49:42

+0

好吧,但這不是我想我問的。我從你的第一句話推斷出* data *來自Vanilla中的緩存而不是準備好的聲明中。但我想我誤解了。 – 2012-03-22 15:56:33

1

無的減少預處理語句所需的方法調用指出公然明顯:

PDO 模擬預處理語句MySQL 默認爲,即使驅動程序支持它們。

這是完全相反PHP PDO manual page說什麼,但是,但是view the source code and you'll see they always, de facto, use emulated prepared statements(我認爲這是一個編碼錯誤,但是當我提起它被列爲WONT_FIX的報告,我還有一些Zend的開發僅僅是狀態, 「這是我們的政策總是效仿,只是曲子。」這是沒有意義的給我,但不過,噢。


要使用真正準備好的語句,你必須通過一個箍跳。如果你不「T喜歡這個,怪Zend的開發者,爲修復看起來像它會採取所有的5分鐘(只要將身邊是否)。

$pdo = new PDO($dsn, $user, $pass, array(ATTR::PDO_EMULATE_PREPARES => false)); 

我會非常感激,如果您想進行此更改後,重新運行的基準,並更新我們爲這個代碼的時機。

+3

「這是我們的政策,總是模仿,只是因爲」。不,它不是「僅僅因爲」。 2.有些人默認情況下正努力實現「真實」準備好的陳述。 – PeeHaa 2012-09-11 18:05:48

+1

你爲什麼會責怪「zend devs」? – 2012-09-12 00:11:38

+0

您在代碼示例中混合了ATTR和PDO。 – felixfbecker 2015-04-06 17:13:56