• Час читання ~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 [▶]
  ]
  ....

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

Якщо придивитися, то можна побачити, що в моєму прикладі було 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