Laravel中常見的錯誤與解決方法小結

Laravel中常見的錯誤與解決方法小結

一、報錯: 「Can’t swap PDO instance while within transaction」

通過查詢 Laravel 原始碼,可以確認異常是在 setPdo 方法中丟擲的: 


<?php
public function setPdo($pdo)
{
if ($this->transactions >= 1) {
throw new RuntimeException("
Can't swap PDO instance while within transaction.
");
}
$this->pdo = $pdo;
return $this;
}
?>

按字面意思理解,出現此錯誤是因為在開啟了事務的情況下,切換了資料庫連線。不過有時候,即便程式碼裡沒有顯式的切換資料庫連線,也有可能出現此錯誤。比如說在執行查詢語句出錯的時候,系統會通過 tryAgainIfCausedByLostConnection 方法判斷問題是不是因為丟失連線導致的,如果是,那麼系統會通過 reconnect 方法重新連線,在重新連線的時候,系統會通過 disconnect 方法執行一些清理工作,其中呼叫了 setPdo 方法。

理清了前因後果,自然就知道如何解決問題了:檢查網路情況,確認資料庫連線丟失的原因,這可能是某個裝置有問題,也可能是某個 timeout 設定不當所致。一個相對 dirty 的處理方法是在查詢前執行一下 DB::reconnect() 方法重新連線一下資料庫。

二、報錯:「Cannot delete job: NOT_FOUND」

此問題實際上和 Laravel 沒太大關係,而是佇列服務 Beanstalk 導致的。


Beanstalk

要解決這個問題,需要先理解一個訊息的生命週期:當一個訊息被放入佇列的時候,它就進入了 READY 狀態,與此同時,它會關聯一個 TTR(time to run) 計時器,表示此訊息允許執行的時間,當此訊息被消費時,它就進入了 RESERVED 狀態,消費完後,此訊息就會被刪除,如果消費的時間過長,比 TTR 還長,那麼系統會認為認為此消費者已經掛了,進而會把訊息從 RESERVED 狀態退回到 READY 狀態,交給另一個消費者重新處理。於是乎同一個訊息可能會被多個消費者處理,第一個處理完的消費者可以正常的刪除訊息,而其餘的消費者在刪除訊息的時候就會報無法刪除的錯誤。

解決方法很簡單,首先,需要確保 TTR 的設定不能太小;其次,實際上 Beanstalk 提供了一個專門的 touch 命令來解決執行時間過長的問題,此外,有些時候我們可能需要在應用層面上通過加鎖來規避同一個訊息被多個消費者同時處理的情況。

三、報錯:「No query results for model」

在啟用了 Laravel 讀寫分離的前提下,當消費者處理訊息的時候,可能會收到類似錯誤。一個有潛在問題的佇列命令大概如下所示: 


<?php
class Foo extends Command implements SelfHandling, ShouldBeQueued
{
use InteractsWithQueue, SerializesModels;
protected $bar;
public function __construct($id)
{
$this->bar = Bar::find($id);
}
public function handle()
{
// $this->bar
}
}
?>

很明顯,當開啟了 Laravel 讀寫分離的時候,因為主從延遲的緣故,所以 find 可能查詢不到相應的資料,一旦我們分析到了這裡,那麼很可能會把寫法修改成下面的樣子:


<?php
class Foo extends Command implements SelfHandling, ShouldBeQueued
{
use InteractsWithQueue, SerializesModels;
protected $bar;
public function __construct($id)
{
$this->bar = Bar::onWriteConnection()->find($id);
}
public function handle()
{
// $this->bar
}
}
?>

也就是說,通過 Laravel 的 onWriteConnection 方法把查詢固定在主伺服器上,不過實際上無效。問題癥結在於反序列化的時候,系統會在從伺服器上一次 findOrFail 呼叫。 


<?php
protected function getRestoredPropertyValue($value)
{
return $value instanceof ModelIdentifier
? (new $value->class)->findOrFail($value->id) : $value;
}
?>

因為我們無法 HACK 到框架內部,所以 onWriteConnection 就沒有意義了。其實換個角度看問題,只要在系列化的時候,保證別用資料庫物件做屬性即可:


<?php
class Foo extends Command implements SelfHandling, ShouldBeQueued
{
use InteractsWithQueue, SerializesModels;
protected $id;
public function __construct($id)
{
$this->id = $id;
}
public function handle()
{
$bar = Bar::onWriteConnection()->find($this->id);
}
}
?>

四、總結

以上就是我在使用Laravel遇到的幾個有代表性的報錯以及解決方案,如果有問題歡迎大家一起交流。希望這篇文章對大家的學習或者工作能帶來一定的幫助。

您可能感興趣的文章:

laravel中的錯誤與日誌用法詳解Laravel實現自定義錯誤輸出內容的方法Laravel 5.3 學習筆記之 錯誤&日誌Laravel5.5新特性之友好報錯以及展示詳解解決laravel 5.1報錯:No supported encrypter found的辦法Laravel5.1自定義500錯誤頁面示例laravel 5異常錯誤:FatalErrorException in Handler.php line 38的解決