-
Notifications
You must be signed in to change notification settings - Fork 0
Query Profiler
The query log is a per-Connection in-memory buffer of every statement executed while logging is enabled. Use it to inspect what SQL your app actually issues, measure query timings, and catch N+1 problems.
$db->enableQueryLog();
$db->read('users', ['id', 'name'], ['active' => 1]);
$db->read('posts', ['id', 'title'], ['user_id' => 5]);
print_r($db->getQueryLogs());Output:
Array
(
[0] => Array
(
[query] => SELECT id, name FROM users WHERE active = :active
[args] => Array ( [:active] => 1 )
[timer] => 0.000642
)
[1] => Array
(
[query] => SELECT id, title FROM posts WHERE user_id = :user_id
[args] => Array ( [:user_id] => 5 )
[timer] => 0.000478
)
)
| Key | Type | Meaning |
|---|---|---|
query |
string |
The SQL string handed to PDO. Already includes named placeholders. |
args |
array<string, mixed>|null |
The bound parameter map (may be null for raw query() calls without parameters). |
timer |
float |
Wall-clock seconds, microtime(true) difference. |
$db->enableQueryLog();
$db->read('users'); // recorded
$db->disableQueryLog();
$db->read('users'); // NOT recorded
print_r($db->getQueryLogs()); // only the first read appearsDisabling preserves existing entries — it only stops new ones from being appended.
Set queryLogs: true in the credentials array to have the log running before any code touches the Database:
DB::createImmutable([
'dsn' => '…',
'queryLogs' => true,
]);This is convenient for development — flip it on in your local .env and forget about it.
The most common reason to enable the profiler.
$db->enableQueryLog();
$users = $db->read('users')->asAssoc()->rows();
foreach ($users as $user) {
$posts = $db->read('posts', ['*'], ['user_id' => $user['id']])->asAssoc()->rows();
// …
}
$logs = $db->getQueryLogs();
echo count($logs); // 1 + number of users → that's an N+1Fix by joining or pre-fetching:
$rows = $db->select('users.id', 'users.name', 'posts.id AS post_id', 'posts.title')
->from('users')
->leftJoin('posts', 'posts.user_id = users.id')
->read()
->asAssoc()
->rows();
// Or pre-fetch all related rows in one IN()
$ids = array_column($users, 'id');
$posts = $db->read('posts', ['*'], [DB::raw('user_id IN (' . implode(',', $ids) . ')')]);$slow = array_filter($db->getQueryLogs(), fn ($entry) => $entry['timer'] > 0.05);
foreach ($slow as $entry) {
error_log(sprintf('SLOW: %.3fs %s', $entry['timer'], $entry['query']));
}Inject a header summarizing query activity in development:
register_shutdown_function(function () use ($db) {
if (headers_sent()) {
return;
}
$logs = $db->getQueryLogs();
header('X-Query-Count: ' . count($logs));
header('X-Query-Time: ' . round(array_sum(array_column($logs, 'timer')) * 1000, 2) . 'ms');
});public function test_does_not_n_plus_one(): void
{
$this->db->enableQueryLog();
$this->repository->loadAllWithRoles();
self::assertLessThanOrEqual(2, count($this->db->getQueryLogs()), 'Should run at most two queries.');
}The log lives in process memory and grows unbounded. For long-running CLI workers, drain it periodically:
while ($job = $queue->pop()) {
$job->handle();
if (count($db->getQueryLogs()) > 10_000) {
// Reset the underlying buffer
$db->getConnection()->setQueryLogs(false)->setQueryLogs(true);
}
}In production, leave queryLogs off unless you're actively profiling — the timing measurement itself is cheap, but unbounded memory growth is not.
Failure log (log credential) |
Query profiler (queryLogs) |
|
|---|---|---|
| Captures | Errors only | Every successful + failed query |
| Storage | External sink (file, Monolog, …) | In-process memory array |
| Default | Disabled | Disabled |
| Cost when enabled | Negligible (only fires on error) | One microtime() call per query, plus memory |
| Use case | Production error tracking | Development profiling, test assertions |
They are independent — enable both, either, or neither.
- Logging and Debug — for failure-only logging
- Recipes — concrete profiler-driven patterns
-
FAQ — "why is
timerzero?", "can I get the unbound SQL?"
InitORM Database · MIT · maintained by Muhammet ŞAFAK · part of the InitORM stack
Getting Started
Core Operations
Cross-Cutting
Reference
Upgrading
Project