• Время чтения ~4 мин
  • 11.10.2023

Ваш сайт работает медленно? Загружается ли целая вечность? Пользователи жалуются, что он почти непригоден для использования?. Вы должны проверить свои запросы к базе данных. И я покажу вам изящный способ легко профилировать все запросы к базе данных.

Конечно, ваш сайт может быть медленным по многим причинам, но одной из наиболее распространенных причин являются медленные запросы к базе данных.

Но в laravel мы не используем SQL (большую часть времени) для получения данных из нашей базы данных, мы используем красноречивый ORM и конструктор запросов laravel, что иногда затрудняет определение того запроса, который делает наш сайт таким медленным.

DB::listen()

К счастью, в laravel мы можем определить обратный вызов, который вызывается каждый раз при выполнении запроса (см. здесь). Для этого добавьте следующий код к любому поставщику сервиса (например, AppServiceProvider):

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

Как видите, мы получаем переменную $query, эта переменная является экземпляром класса QueryExecuted. Это означает, что у нас есть доступ к некоторой информации о выполненном запросе:

        DB::listen(function ($query) {
            $query->sql; // the sql string that was executed
            $query->bindings; // the parameters passed to the sql query (this replace the '?'s in the sql string)
            $query->time; // the time it took for the query to execute;
        });

Это очень полезная информация, теперь у нас есть способ определить медленные запросы, посмотрев на $query->time свойство. Но это не говорит нам о том, где в нашем коде был выполнен запрос.

Как узнать, где был выполнен запрос?

Несмотря на то, что $query переменная не дает нам никакой информации о том, откуда она $query взялась, мы все равно можем получить эту информацию с помощью встроенной функции debug_backtrace()PHP.

        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 [▶]
  ]
  ....

содержащий все вызовы функций до этого момента в запросе. Я сосредоточусь только на ключах и line в каждом массивеfile.

Если присмотреться, то можно увидеть, что в моем примере было 63 вызова функций, что можно считать много, и это простое приложение, в более сложном может быть намного больше. Что еще хуже, если вы посмотрите на те, что наверху, все они являются внутренними функциями фреймворка laravel. Должны ли мы рассматривать каждый из них до тех пор, пока не найдем что-то, что может нам помочь?

Как

я уже говорил, большинство из них являются внутренними вызовами фреймворка, что означает, что большая часть этих файлов находится внутри нашего vendor/ каталога. Это означает, что мы можем проверить каждый file и отфильтровать любой вызов, который имеет , например:

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

Здесь я преобразую массив в коллекцию для использования filter метода, если file текущий $trace имеет vendor/vendor/ , мы удаляем его из коллекции.

Если вы запустите приведенный выше код, вы увидите что-то вроде этого:

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
}

Это намного меньше элементов, мы перешли от 63 к 5. И самое приятное, что первый элемент в коллекции — это именно то место, где мы запустили SQL-запрос. Это означает, что мы можем извлечь эту информацию, чтобы найти самые медленные запросы.

Теперь

, когда у нас есть вся необходимая информация, почему бы нам не зарегистрировать ее, чтобы мы могли проверять и искать самые медленные запросы?:Если вы используете это в своем приложении, вы можете проверить свой файл журнала, и вы должны увидеть информацию о запросах следующим образом:

    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']}
                   ------------
            ");
        });
    }

[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
                    ----------

Теперь вы знаете, какие запросы самые медленные, и начинаете обрабатывать их один за другим, Постарайтесь сделать их быстрее или, по крайней мере, кэшировать.

Помимо отладки Это очень полезно для отладки

, но этот метод можно использовать множеством способов.

Вы можете создать еженедельный отчет, показывающий самые медленные запросы за неделю.

Вы можете получить оповещение о нефиксированном значении, если запрос превышает пороговое значение

по времениВы можете создать панель мониторинга, на которой вы и ваша команда сможете видеть каждый выполненный

запросНет предела совершенству.

Последнее обновление 1 год назад.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Об авторе CrazyBoy49z
WORK EXPERIENCE
Контакты
Ukraine, Lutsk
+380979856297