Maison  >  Article  >  cadre php  >  Une brève analyse sur la façon de trouver la requête la plus lente dans Laravel

Une brève analyse sur la façon de trouver la requête la plus lente dans Laravel

青灯夜游
青灯夜游avant
2022-09-08 10:09:002402parcourir

Votre site Web est-il lent ? Est-ce que le chargement est long ? Les utilisateurs se plaignent-ils du fait qu'il est presque inutilisable ? Vous devriez vérifier vos requêtes de base de données. Je vais vous montrer une manière intéressante d'analyser facilement toutes vos requêtes de base de données.

Une brève analyse sur la façon de trouver la requête la plus lente dans Laravel

Bien sûr, il existe de nombreuses raisons pour lesquelles votre site Web peut être lent, mais l'une des raisons les plus courantes est la lenteur des requêtes dans la base de données.

Mais dans Laravel, nous n'utilisons (la plupart du temps) pas SQL pour obtenir des données de la base de données, nous utilisons Eloquent ORM et Query Builder, ce qui rend parfois difficile l'identification de la requête qui provoque la création de notre site. sois si lent.

DB::listen()

Heureusement, dans Laravel, nous pouvons définir un rappel qui est appelé à chaque fois qu'une requête est exécutée (voir ici). Pour ce faire, ajoutez le code suivant à n'importe quel fournisseur de services (par exemple AppServiceProvider) :

public function boot()
{
    DB::listen(function ($query) {
    // TODO: make this useful
    });
}

Comme vous pouvez le voir, nous recevons une variable $query qui est Une instance de la classe QueryExecuted$query,这个变量是 QueryExecuted 类的一个实例。这意味着我们可以访问有关已执行查询的一些信息:

 DB::listen(function ($query) {
     $query->sql; // 执行的 sql 字符串
     $query->bindings; // 传递给sql查询的参数(这将替换sql字符串中的 "?")
     $query->time; // 执行查询所用的时间;
 });

这是非常有用的信息,现在我们可以通过查看 $query->time 属性来识别慢查询。 但这并没有告诉我们在我们的代码中查询执行的位置

我怎么知道查询是在哪里执行的?

即使该 $query 变量没有给我们任何关于其来源的信息, 我们仍然可以使用 PHP 内置函数  debug_backtrace() 获取该信息。

DB::listen(function ($query) {
    dd(debug_backtrace());
});

如果你在你的项目上运行它,你会在浏览器上看到类似这样的东西:

array:63 [▼
  0 => array:7 [▼
 "file"=>"/home/cosme/Documents/projects/cosme.dev/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php"
    "line" => 404
    "function" => "App\Providers\{closure}"
    "class" => "App\Providers\AppServiceProvider"
    "object" => App\Providers\AppServiceProvider {#140 ▶}
    "type" => "->"
    "args" => array:1 [▶]
  ]
  1 => array:7 [▼
    "file" => "/home/cosme/Documents/projects/cosme.dev/vendor/laravel/framework/src/Illuminate/Events/Dispatcher.php"
    "line" => 249
    "function" => "Illuminate\Events\{closure}"
    "class" => "Illuminate\Events\Dispatcher"
    "object" => Illuminate\Events\Dispatcher {#27 ▶}
    "type" => "->"
    "args" => array:2 [▶]
  ]
  2 => array:7 [▼
    "file" => "/home/cosme/Documents/projects/cosme.dev/vendor/laravel/framework/src/Illuminate/Database/Connection.php"
    "line" => 887
    "function" => "dispatch"
    "class" => "Illuminate\Events\Dispatcher"
    "object" => Illuminate\Events\Dispatcher {#27 ▶}
    "type" => "->"
    "args" => array:1 [▶]
  ]
  ....

这是一个数组,其中包含请求中到目前为止的每个函数调用。我将只关注每个数组中的 fileline 键。

如果你仔细看,你会发现在我的例子中有 63 个函数调用,这是一个简单的应用程序,如果在更复杂的应用程序中,可能会更多。更糟糕的是,如果您查看顶部的那些, 它们都是 laravel 框架的内部函数。我们是否应该逐一查看,直到找到可能对我们有帮助的东西?

查找查询位置

正如我之前所说,它们中的大多数是内部框架调用,这意味着这些文件中的大多数都在我们的 vendor/ 目录中。这意味着我们可以检查每个 file 并过滤掉任何具有 vendor/ 的调用,如下所示:

DB::listen(function ($query) {
    $stackTrace = collect(debug_backtrace())->filter(function ($trace) {
        return !str_contains($trace['file'], 'vendor/');
    });

    dd($stackTrace);
});

在这里,我将数组转换为集合以使用该 filter 方法,如果 file 当前 $tracevendor/. Cela signifie que nous avons accès à certaines informations sur la requête exécutée :

Illuminate\Support\Collection {#1237 ▼
  #items: array:5 [▼
    12 => array:7 [▼
      "file" => "/home/cosme/Documents/projects/cosme.dev/app/Models/Post.php"
      "line" => 61
      "function" => "get"
      "class" => "Illuminate\Database\Eloquent\Builder"
      "object" => Illuminate\Database\Eloquent\Builder {#310 ▶}
      "type" => "->"
      "args" => []
    ]
    16 => array:6 [▶]
    17 => array:6 [▶]
    61 => array:7 [▶]
    62 => array:4 [▶]
  ]
  #escapeWhenCastingToString: false
}

C'est une information très utile, nous pouvons maintenant identifier les requêtes lentes en regardant l'attribut $query->time.

Mais cela ne nous dit pas où dans notre code la requête est exécutée

.

Comment savoir où la requête a été exécutée ?

Même si la variable $query ne nous donne aucune information sur son origine, nous pouvons toujours obtenir ces informations en utilisant la fonction intégrée PHP debug_backtrace() .

public function boot()
{
    DB::listen(function ($query) {
        $location = collect(debug_backtrace())->filter(function ($trace) {
            return !str_contains($trace['file'], 'vendor/');
        })->first(); // grab the first element of non vendor/ calls

        $bindings = implode(", ", $query->bindings); // format the bindings as string

        Log::info("
            ------------
            Sql: $query->sql
            Bindings: $bindings
            Time: $query->time
            File: ${location['file']}
            Line: ${location['line']}
            ------------
        ");
    });
}

Si vous exécutez ceci sur votre projet, vous verrez quelque chose comme ceci sur le navigateur :

[2022-02-03 02:20:14] local.INFO:
------------
Sql: select "title", "slug", "body" from "posts" where "published" = ? order by "id" desc   
Bindings: 1
Time: 0.18
File: /home/cosme/Documents/projects/cosme.dev/app/Models/Post.php
Line: 61
----------

Il s'agit d'un tableau contenant tous les appels de fonction jusqu'à présent dans la requête. Je vais juste me concentrer sur les clés file et line dans chaque tableau.

Si vous regardez attentivement, vous verrez que dans mon exemple il y a 63 appels de fonction, c'est une application simple, si dans une application plus complexe cela pourrait être plus. Pire encore, si vous regardez celles du haut, ce sont toutes des fonctions internes du framework laravel. Devrions-nous examiner chacun d’entre eux jusqu’à ce que nous trouvions quelque chose qui pourrait nous aider ?

Trouver des emplacements de requête

Comme je l'ai déjà dit, la plupart d'entre eux sont des appels de framework internes, ce qui signifie que la plupart de ces fichiers se trouvent au milieu de notre répertoire vendor/. Cela signifie que nous pouvons vérifier chaque fichier et filtrer tous les appels avec vendor/ comme ceci :

rrreee

Ici, je convertis le tableau en collection en utilisant le filter , si le fichier actuellement $trace a un fournisseur/ nous le supprimons de la collection. Si vous exécutez le code ci-dessus vous verrez quelque chose comme ceci :

rrreee

Les articles sont beaucoup moins nombreux, nous sommes passés de 63 à seulement 5. La meilleure partie est que le premier élément de la collection est l'emplacement exact où nous déclenchons la requête SQL. Cela signifie que nous pouvons extraire ces informations pour trouver les requêtes les plus lentes.

Print to Log

Maintenant que nous avons toutes les informations dont nous avons besoin, pourquoi ne pas les enregistrer afin que nous puissions inspecter et trouver les requêtes les plus lentes ? :

rrreee🎜Si vous l'utilisez dans votre application, vous pouvez vérifier vos fichiers journaux et vous devriez voir des informations de requête comme celle-ci :🎜rrreee🎜Vous savez maintenant quelles requêtes sont les plus lentes et commencez à les traiter une par une, essayez de les faire plus rapidement, ou au moins les mettre en cache. 🎜🎜🎜🎜🎜Débogage étendu🎜🎜🎜C'est idéal pour le débogage, mais cette technique peut être utilisée de différentes manières. 🎜🎜🎜Vous pouvez créer un rapport hebdomadaire montrant les requêtes les plus lentes de la semaine. 🎜🎜🎜🎜Vous pouvez recevoir des alertes Slack si une requête dépasse un seuil de temps 🎜🎜🎜🎜 Vous pouvez créer un tableau de bord où vous et votre équipe pouvez voir chaque requête exécutée 🎜🎜🎜Le ciel est la limite. 🎜🎜【Recommandation associée : 🎜tutoriel vidéo laravel🎜】🎜

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer
Article précédent:Laravel est-il aop ?Article suivant:Laravel est-il aop ?