[{"data":1,"prerenderedAt":1085},["ShallowReactive",2],{"blog-\u002Fblog\u002Fsql-query-optimization-explain":3},{"id":4,"title":5,"body":6,"description":1071,"difficulty":1072,"extension":1073,"framework":1074,"frameworkSlug":36,"meta":1075,"navigation":296,"order":58,"path":1076,"qaPath":1077,"seo":1078,"stem":1079,"subtopic":1080,"topic":1081,"topicSlug":1082,"updated":1083,"__hash__":1084},"blog\u002Fblog\u002Fsql-query-optimization-explain.md","SQL Query Optimization — Reading EXPLAIN, Fixing Slow Queries",{"type":7,"value":8,"toc":1056},"minimark",[9,14,27,31,197,200,208,211,246,250,255,326,330,421,425,431,544,548,617,621,740,744,750,817,821,827,909,913,1028,1031,1035,1052],[10,11,13],"h2",{"id":12},"the-optimization-mindset","The optimization mindset",[15,16,17,18,22,23,26],"p",{},"The database query optimizer generates an execution plan for every query. It\nestimates the cost of different approaches — which indexes to use, in what order\nto join tables, whether to hash or sort — and picks the cheapest. Sometimes it\nguesses wrong. ",[19,20,21],"code",{},"EXPLAIN"," shows you the plan it chose; ",[19,24,25],{},"EXPLAIN ANALYZE"," runs the\nquery and shows actual timings. That is where optimization starts.",[10,28,30],{"id":29},"reading-explain-analyze","Reading EXPLAIN ANALYZE",[32,33,38],"pre",{"className":34,"code":35,"language":36,"meta":37,"style":37},"language-sql shiki shiki-themes github-light github-dark","EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)\nSELECT o.id, c.email, o.total_amount\nFROM   orders    o\nJOIN   customers c ON c.id = o.customer_id\nWHERE  o.status = 'pending'\n  AND  o.created_at >= '2026-06-01'\nORDER  BY o.created_at DESC\nLIMIT  50;\n","sql","",[19,39,40,56,93,102,131,151,170,185],{"__ignoreMap":37},[41,42,45,49,53],"span",{"class":43,"line":44},"line",1,[41,46,48],{"class":47},"sVt8B","EXPLAIN (ANALYZE, BUFFERS, FORMAT ",[41,50,52],{"class":51},"szBVR","TEXT",[41,54,55],{"class":47},")\n",[41,57,59,62,66,69,72,75,78,80,83,85,88,90],{"class":43,"line":58},2,[41,60,61],{"class":51},"SELECT",[41,63,65],{"class":64},"sj4cs"," o",[41,67,68],{"class":47},".",[41,70,71],{"class":64},"id",[41,73,74],{"class":47},", ",[41,76,77],{"class":64},"c",[41,79,68],{"class":47},[41,81,82],{"class":64},"email",[41,84,74],{"class":47},[41,86,87],{"class":64},"o",[41,89,68],{"class":47},[41,91,92],{"class":64},"total_amount\n",[41,94,96,99],{"class":43,"line":95},3,[41,97,98],{"class":51},"FROM",[41,100,101],{"class":47},"   orders    o\n",[41,103,105,108,111,114,117,119,121,124,126,128],{"class":43,"line":104},4,[41,106,107],{"class":51},"JOIN",[41,109,110],{"class":47},"   customers c ",[41,112,113],{"class":51},"ON",[41,115,116],{"class":64}," c",[41,118,68],{"class":47},[41,120,71],{"class":64},[41,122,123],{"class":51}," =",[41,125,65],{"class":64},[41,127,68],{"class":47},[41,129,130],{"class":64},"customer_id\n",[41,132,134,137,140,142,145,147],{"class":43,"line":133},5,[41,135,136],{"class":51},"WHERE",[41,138,139],{"class":64},"  o",[41,141,68],{"class":47},[41,143,144],{"class":64},"status",[41,146,123],{"class":51},[41,148,150],{"class":149},"sZZnC"," 'pending'\n",[41,152,154,157,159,161,164,167],{"class":43,"line":153},6,[41,155,156],{"class":51},"  AND",[41,158,139],{"class":64},[41,160,68],{"class":47},[41,162,163],{"class":64},"created_at",[41,165,166],{"class":51}," >=",[41,168,169],{"class":149}," '2026-06-01'\n",[41,171,173,176,178,180,182],{"class":43,"line":172},7,[41,174,175],{"class":51},"ORDER  BY",[41,177,65],{"class":64},[41,179,68],{"class":47},[41,181,163],{"class":64},[41,183,184],{"class":51}," DESC\n",[41,186,188,191,194],{"class":43,"line":187},8,[41,189,190],{"class":51},"LIMIT",[41,192,193],{"class":64},"  50",[41,195,196],{"class":47},";\n",[15,198,199],{},"Sample output annotations:",[32,201,206],{"className":202,"code":204,"language":205},[203],"language-text","Limit  (cost=... rows=50) (actual time=4.2..4.3 ms rows=50)\n  -> Sort  (actual time=4.1..4.2 ms rows=50)\n       Sort Key: o.created_at DESC\n       Sort Method: top-N heapsort  Memory: 30kB\n       -> Hash Join  (actual time=1.2..3.8 ms rows=412)\n            Hash Cond: (o.customer_id = c.id)\n            -> Index Scan using idx_orders_status_created on orders o\n                 (actual time=0.05..2.1 ms rows=412)\n                 Index Cond: (status = 'pending')\n                 Filter: (created_at >= '2026-06-01')\n            -> Hash  (actual time=0.8..0.8 ms rows=9541)\n                 -> Seq Scan on customers c\n","text",[19,207,204],{"__ignoreMap":37},[15,209,210],{},"Key things to read:",[212,213,214,222,231,237],"ul",{},[215,216,217,221],"li",{},[218,219,220],"strong",{},"Seq Scan on a large table"," — look for a missing index.",[215,223,224,227,228,68],{},[218,225,226],{},"Rows estimate vs actual rows"," — a big gap means stale statistics; run ",[19,229,230],{},"ANALYZE",[215,232,233,236],{},[218,234,235],{},"Buffers: hit vs read"," — \"read\" means disk I\u002FO; \"hit\" means cache.",[215,238,239,242,243,68],{},[218,240,241],{},"Sort Method: external merge"," — sort spilled to disk; increase ",[19,244,245],{},"work_mem",[10,247,249],{"id":248},"the-most-common-slow-query-patterns","The most common slow-query patterns",[251,252,254],"h3",{"id":253},"_1-sequential-scan-on-a-large-table","1. Sequential scan on a large table",[32,256,258],{"className":34,"code":257,"language":36,"meta":37,"style":37},"-- Slow: full scan of 10M-row orders table\nSELECT * FROM orders WHERE customer_id = 1001;\n\n-- EXPLAIN shows: Seq Scan on orders (rows=10000000)\n-- Fix: add an index\nCREATE INDEX idx_orders_customer_id ON orders (customer_id);\n",[19,259,260,266,292,298,303,308],{"__ignoreMap":37},[41,261,262],{"class":43,"line":44},[41,263,265],{"class":264},"sJ8bj","-- Slow: full scan of 10M-row orders table\n",[41,267,268,270,273,276,279,281,284,287,290],{"class":43,"line":58},[41,269,61],{"class":51},[41,271,272],{"class":51}," *",[41,274,275],{"class":51}," FROM",[41,277,278],{"class":47}," orders ",[41,280,136],{"class":51},[41,282,283],{"class":47}," customer_id ",[41,285,286],{"class":51},"=",[41,288,289],{"class":64}," 1001",[41,291,196],{"class":47},[41,293,294],{"class":43,"line":95},[41,295,297],{"emptyLinePlaceholder":296},true,"\n",[41,299,300],{"class":43,"line":104},[41,301,302],{"class":264},"-- EXPLAIN shows: Seq Scan on orders (rows=10000000)\n",[41,304,305],{"class":43,"line":133},[41,306,307],{"class":264},"-- Fix: add an index\n",[41,309,310,313,316,320,323],{"class":43,"line":153},[41,311,312],{"class":51},"CREATE",[41,314,315],{"class":51}," INDEX",[41,317,319],{"class":318},"sScJk"," idx_orders_customer_id",[41,321,322],{"class":51}," ON",[41,324,325],{"class":47}," orders (customer_id);\n",[251,327,329],{"id":328},"_2-function-on-an-indexed-column","2. Function on an indexed column",[32,331,333],{"className":34,"code":332,"language":36,"meta":37,"style":37},"-- Slow: index on email is not used\nSELECT * FROM users WHERE LOWER(email) = 'alice@example.com';\n\n-- Fix: functional index\nCREATE INDEX idx_users_email_ci ON users (LOWER(email));\nSELECT * FROM users WHERE LOWER(email) = 'alice@example.com';  -- now uses index\n",[19,334,335,340,366,370,375,395],{"__ignoreMap":37},[41,336,337],{"class":43,"line":44},[41,338,339],{"class":264},"-- Slow: index on email is not used\n",[41,341,342,344,346,348,351,353,356,359,361,364],{"class":43,"line":58},[41,343,61],{"class":51},[41,345,272],{"class":51},[41,347,275],{"class":51},[41,349,350],{"class":47}," users ",[41,352,136],{"class":51},[41,354,355],{"class":64}," LOWER",[41,357,358],{"class":47},"(email) ",[41,360,286],{"class":51},[41,362,363],{"class":149}," 'alice@example.com'",[41,365,196],{"class":47},[41,367,368],{"class":43,"line":95},[41,369,297],{"emptyLinePlaceholder":296},[41,371,372],{"class":43,"line":104},[41,373,374],{"class":264},"-- Fix: functional index\n",[41,376,377,379,381,384,386,389,392],{"class":43,"line":133},[41,378,312],{"class":51},[41,380,315],{"class":51},[41,382,383],{"class":318}," idx_users_email_ci",[41,385,322],{"class":51},[41,387,388],{"class":47}," users (",[41,390,391],{"class":64},"LOWER",[41,393,394],{"class":47},"(email));\n",[41,396,397,399,401,403,405,407,409,411,413,415,418],{"class":43,"line":153},[41,398,61],{"class":51},[41,400,272],{"class":51},[41,402,275],{"class":51},[41,404,350],{"class":47},[41,406,136],{"class":51},[41,408,355],{"class":64},[41,410,358],{"class":47},[41,412,286],{"class":51},[41,414,363],{"class":149},[41,416,417],{"class":47},";  ",[41,419,420],{"class":264},"-- now uses index\n",[251,422,424],{"id":423},"_3-n1-query-the-hidden-loop","3. N+1 query — the hidden loop",[15,426,427,428,430],{},"The N+1 problem is not visible in ",[19,429,21],{}," because each query is individually\nfast. The problem is issuing N queries in a loop instead of one set-based query.",[32,432,436],{"className":433,"code":434,"language":435,"meta":37,"style":37},"language-python shiki shiki-themes github-light github-dark","# N+1 — 1 query for orders, then 1 per order to fetch customer (BAD)\norders = db.query(\"SELECT * FROM orders WHERE status = 'pending'\")\nfor order in orders:\n    customer = db.query(\"SELECT * FROM customers WHERE id = ?\", order.customer_id)\n    # → 1 + N queries total\n\n# Fix: join everything in one query\norders = db.query(\"\"\"\n    SELECT o.*, c.email, c.display_name\n    FROM   orders o\n    JOIN   customers c ON c.id = o.customer_id\n    WHERE  o.status = 'pending'\n\"\"\")\n","python",[19,437,438,443,458,472,487,492,496,501,512,518,524,530,536],{"__ignoreMap":37},[41,439,440],{"class":43,"line":44},[41,441,442],{"class":264},"# N+1 — 1 query for orders, then 1 per order to fetch customer (BAD)\n",[41,444,445,448,450,453,456],{"class":43,"line":58},[41,446,447],{"class":47},"orders ",[41,449,286],{"class":51},[41,451,452],{"class":47}," db.query(",[41,454,455],{"class":149},"\"SELECT * FROM orders WHERE status = 'pending'\"",[41,457,55],{"class":47},[41,459,460,463,466,469],{"class":43,"line":95},[41,461,462],{"class":51},"for",[41,464,465],{"class":47}," order ",[41,467,468],{"class":51},"in",[41,470,471],{"class":47}," orders:\n",[41,473,474,477,479,481,484],{"class":43,"line":104},[41,475,476],{"class":47},"    customer ",[41,478,286],{"class":51},[41,480,452],{"class":47},[41,482,483],{"class":149},"\"SELECT * FROM customers WHERE id = ?\"",[41,485,486],{"class":47},", order.customer_id)\n",[41,488,489],{"class":43,"line":133},[41,490,491],{"class":264},"    # → 1 + N queries total\n",[41,493,494],{"class":43,"line":153},[41,495,297],{"emptyLinePlaceholder":296},[41,497,498],{"class":43,"line":172},[41,499,500],{"class":264},"# Fix: join everything in one query\n",[41,502,503,505,507,509],{"class":43,"line":187},[41,504,447],{"class":47},[41,506,286],{"class":51},[41,508,452],{"class":47},[41,510,511],{"class":149},"\"\"\"\n",[41,513,515],{"class":43,"line":514},9,[41,516,517],{"class":149},"    SELECT o.*, c.email, c.display_name\n",[41,519,521],{"class":43,"line":520},10,[41,522,523],{"class":149},"    FROM   orders o\n",[41,525,527],{"class":43,"line":526},11,[41,528,529],{"class":149},"    JOIN   customers c ON c.id = o.customer_id\n",[41,531,533],{"class":43,"line":532},12,[41,534,535],{"class":149},"    WHERE  o.status = 'pending'\n",[41,537,539,542],{"class":43,"line":538},13,[41,540,541],{"class":149},"\"\"\"",[41,543,55],{"class":47},[251,545,547],{"id":546},"_4-select-pulling-unused-columns","4. SELECT * pulling unused columns",[32,549,551],{"className":34,"code":550,"language":36,"meta":37,"style":37},"-- Slow: fetches all columns including large TEXT and JSONB blobs\nSELECT * FROM products WHERE category = 'Electronics';\n\n-- Fast: fetch only what the application needs\nSELECT id, product_name, unit_price, stock_qty\nFROM   products\nWHERE  category = 'Electronics';\n",[19,552,553,558,581,585,590,597,604],{"__ignoreMap":37},[41,554,555],{"class":43,"line":44},[41,556,557],{"class":264},"-- Slow: fetches all columns including large TEXT and JSONB blobs\n",[41,559,560,562,564,566,569,571,574,576,579],{"class":43,"line":58},[41,561,61],{"class":51},[41,563,272],{"class":51},[41,565,275],{"class":51},[41,567,568],{"class":47}," products ",[41,570,136],{"class":51},[41,572,573],{"class":47}," category ",[41,575,286],{"class":51},[41,577,578],{"class":149}," 'Electronics'",[41,580,196],{"class":47},[41,582,583],{"class":43,"line":95},[41,584,297],{"emptyLinePlaceholder":296},[41,586,587],{"class":43,"line":104},[41,588,589],{"class":264},"-- Fast: fetch only what the application needs\n",[41,591,592,594],{"class":43,"line":133},[41,593,61],{"class":51},[41,595,596],{"class":47}," id, product_name, unit_price, stock_qty\n",[41,598,599,601],{"class":43,"line":153},[41,600,98],{"class":51},[41,602,603],{"class":47},"   products\n",[41,605,606,608,611,613,615],{"class":43,"line":172},[41,607,136],{"class":51},[41,609,610],{"class":47},"  category ",[41,612,286],{"class":51},[41,614,578],{"class":149},[41,616,196],{"class":47},[251,618,620],{"id":619},"_5-missing-join-index","5. Missing join index",[32,622,624],{"className":34,"code":623,"language":36,"meta":37,"style":37},"-- Without index on order_items.order_id, joining is a full scan each time\nEXPLAIN SELECT o.id, COUNT(oi.id) AS item_count\nFROM orders o JOIN order_items oi ON oi.order_id = o.id\nGROUP BY o.id;\n-- Hash Join with Seq Scan on order_items — bad at scale\n\nCREATE INDEX idx_order_items_order_id ON order_items (order_id);\n-- Now uses Index Scan — much faster\n",[19,625,626,631,668,699,712,717,721,735],{"__ignoreMap":37},[41,627,628],{"class":43,"line":44},[41,629,630],{"class":264},"-- Without index on order_items.order_id, joining is a full scan each time\n",[41,632,633,636,638,640,642,644,646,649,652,655,657,659,662,665],{"class":43,"line":58},[41,634,635],{"class":47},"EXPLAIN ",[41,637,61],{"class":51},[41,639,65],{"class":64},[41,641,68],{"class":47},[41,643,71],{"class":64},[41,645,74],{"class":47},[41,647,648],{"class":64},"COUNT",[41,650,651],{"class":47},"(",[41,653,654],{"class":64},"oi",[41,656,68],{"class":47},[41,658,71],{"class":64},[41,660,661],{"class":47},") ",[41,663,664],{"class":51},"AS",[41,666,667],{"class":47}," item_count\n",[41,669,670,672,675,677,680,682,685,687,690,692,694,696],{"class":43,"line":95},[41,671,98],{"class":51},[41,673,674],{"class":47}," orders o ",[41,676,107],{"class":51},[41,678,679],{"class":47}," order_items oi ",[41,681,113],{"class":51},[41,683,684],{"class":64}," oi",[41,686,68],{"class":47},[41,688,689],{"class":64},"order_id",[41,691,123],{"class":51},[41,693,65],{"class":64},[41,695,68],{"class":47},[41,697,698],{"class":64},"id\n",[41,700,701,704,706,708,710],{"class":43,"line":104},[41,702,703],{"class":51},"GROUP BY",[41,705,65],{"class":64},[41,707,68],{"class":47},[41,709,71],{"class":64},[41,711,196],{"class":47},[41,713,714],{"class":43,"line":133},[41,715,716],{"class":264},"-- Hash Join with Seq Scan on order_items — bad at scale\n",[41,718,719],{"class":43,"line":153},[41,720,297],{"emptyLinePlaceholder":296},[41,722,723,725,727,730,732],{"class":43,"line":172},[41,724,312],{"class":51},[41,726,315],{"class":51},[41,728,729],{"class":318}," idx_order_items_order_id",[41,731,322],{"class":51},[41,733,734],{"class":47}," order_items (order_id);\n",[41,736,737],{"class":43,"line":187},[41,738,739],{"class":264},"-- Now uses Index Scan — much faster\n",[10,741,743],{"id":742},"stale-statistics-when-the-planner-guesses-wrong","Stale statistics — when the planner guesses wrong",[15,745,746,747,749],{},"The optimizer uses table statistics (row counts, value distributions) to estimate\nthe cost of each plan. If a table grew significantly since the last ",[19,748,230],{},",\nestimates diverge from reality and the planner picks bad plans.",[32,751,753],{"className":34,"code":752,"language":36,"meta":37,"style":37},"-- Update statistics manually\nANALYZE orders;\nANALYZE VERBOSE customers;  -- shows what it collected\n\n-- Postgres autovacuum runs ANALYZE automatically, but you can force it\n-- after a large bulk load:\nINSERT INTO orders SELECT ... FROM staging;  -- bulk insert\nANALYZE orders;\n",[19,754,755,760,765,779,783,788,793,813],{"__ignoreMap":37},[41,756,757],{"class":43,"line":44},[41,758,759],{"class":264},"-- Update statistics manually\n",[41,761,762],{"class":43,"line":58},[41,763,764],{"class":47},"ANALYZE orders;\n",[41,766,767,770,773,776],{"class":43,"line":95},[41,768,769],{"class":47},"ANALYZE ",[41,771,772],{"class":51},"VERBOSE",[41,774,775],{"class":47}," customers;  ",[41,777,778],{"class":264},"-- shows what it collected\n",[41,780,781],{"class":43,"line":104},[41,782,297],{"emptyLinePlaceholder":296},[41,784,785],{"class":43,"line":133},[41,786,787],{"class":264},"-- Postgres autovacuum runs ANALYZE automatically, but you can force it\n",[41,789,790],{"class":43,"line":153},[41,791,792],{"class":264},"-- after a large bulk load:\n",[41,794,795,798,800,802,805,807,810],{"class":43,"line":172},[41,796,797],{"class":51},"INSERT INTO",[41,799,278],{"class":47},[41,801,61],{"class":51},[41,803,804],{"class":47}," ... ",[41,806,98],{"class":51},[41,808,809],{"class":47}," staging;  ",[41,811,812],{"class":264},"-- bulk insert\n",[41,814,815],{"class":43,"line":187},[41,816,764],{"class":47},[10,818,820],{"id":819},"work_mem-sort-and-hash-join-memory","work_mem — sort and hash join memory",[15,822,823,824,826],{},"Sorts and hash joins spill to disk when they exceed ",[19,825,245],{},". Setting it too\nlow forces disk I\u002FO; setting it globally too high wastes RAM. Set it per session\nfor expensive queries:",[32,828,830],{"className":34,"code":829,"language":36,"meta":37,"style":37},"-- For this session only, allow more memory for sorts and hash joins\nSET work_mem = '256MB';\n\nEXPLAIN (ANALYZE, BUFFERS)\nSELECT customer_id, SUM(total_amount)\nFROM orders\nGROUP BY customer_id\nORDER BY SUM(total_amount) DESC;\n-- Sort Method changes from \"external merge Disk\" to \"quicksort Memory\"\n",[19,831,832,837,852,856,861,874,881,888,904],{"__ignoreMap":37},[41,833,834],{"class":43,"line":44},[41,835,836],{"class":264},"-- For this session only, allow more memory for sorts and hash joins\n",[41,838,839,842,845,847,850],{"class":43,"line":58},[41,840,841],{"class":51},"SET",[41,843,844],{"class":47}," work_mem ",[41,846,286],{"class":51},[41,848,849],{"class":149}," '256MB'",[41,851,196],{"class":47},[41,853,854],{"class":43,"line":95},[41,855,297],{"emptyLinePlaceholder":296},[41,857,858],{"class":43,"line":104},[41,859,860],{"class":47},"EXPLAIN (ANALYZE, BUFFERS)\n",[41,862,863,865,868,871],{"class":43,"line":133},[41,864,61],{"class":51},[41,866,867],{"class":47}," customer_id, ",[41,869,870],{"class":64},"SUM",[41,872,873],{"class":47},"(total_amount)\n",[41,875,876,878],{"class":43,"line":153},[41,877,98],{"class":51},[41,879,880],{"class":47}," orders\n",[41,882,883,885],{"class":43,"line":172},[41,884,703],{"class":51},[41,886,887],{"class":47}," customer_id\n",[41,889,890,893,896,899,902],{"class":43,"line":187},[41,891,892],{"class":51},"ORDER BY",[41,894,895],{"class":64}," SUM",[41,897,898],{"class":47},"(total_amount) ",[41,900,901],{"class":51},"DESC",[41,903,196],{"class":47},[41,905,906],{"class":43,"line":514},[41,907,908],{"class":264},"-- Sort Method changes from \"external merge Disk\" to \"quicksort Memory\"\n",[10,910,912],{"id":911},"using-pg_stat_statements-to-find-the-slowest-queries","Using pg_stat_statements to find the slowest queries",[32,914,916],{"className":34,"code":915,"language":36,"meta":37,"style":37},"-- Top 10 queries by total execution time (Postgres extension)\nSELECT\n    query,\n    calls,\n    ROUND(total_exec_time::NUMERIC, 2) AS total_ms,\n    ROUND(mean_exec_time::NUMERIC, 2)  AS avg_ms,\n    ROUND(stddev_exec_time::NUMERIC, 2) AS stddev_ms\nFROM pg_stat_statements\nORDER BY total_exec_time DESC\nLIMIT 10;\n",[19,917,918,923,928,933,938,961,982,1002,1009,1019],{"__ignoreMap":37},[41,919,920],{"class":43,"line":44},[41,921,922],{"class":264},"-- Top 10 queries by total execution time (Postgres extension)\n",[41,924,925],{"class":43,"line":58},[41,926,927],{"class":51},"SELECT\n",[41,929,930],{"class":43,"line":95},[41,931,932],{"class":47},"    query,\n",[41,934,935],{"class":43,"line":104},[41,936,937],{"class":47},"    calls,\n",[41,939,940,943,946,949,951,954,956,958],{"class":43,"line":133},[41,941,942],{"class":64},"    ROUND",[41,944,945],{"class":47},"(total_exec_time::",[41,947,948],{"class":51},"NUMERIC",[41,950,74],{"class":47},[41,952,953],{"class":64},"2",[41,955,661],{"class":47},[41,957,664],{"class":51},[41,959,960],{"class":47}," total_ms,\n",[41,962,963,965,968,970,972,974,977,979],{"class":43,"line":153},[41,964,942],{"class":64},[41,966,967],{"class":47},"(mean_exec_time::",[41,969,948],{"class":51},[41,971,74],{"class":47},[41,973,953],{"class":64},[41,975,976],{"class":47},")  ",[41,978,664],{"class":51},[41,980,981],{"class":47}," avg_ms,\n",[41,983,984,986,989,991,993,995,997,999],{"class":43,"line":172},[41,985,942],{"class":64},[41,987,988],{"class":47},"(stddev_exec_time::",[41,990,948],{"class":51},[41,992,74],{"class":47},[41,994,953],{"class":64},[41,996,661],{"class":47},[41,998,664],{"class":51},[41,1000,1001],{"class":47}," stddev_ms\n",[41,1003,1004,1006],{"class":43,"line":187},[41,1005,98],{"class":51},[41,1007,1008],{"class":47}," pg_stat_statements\n",[41,1010,1011,1013,1016],{"class":43,"line":514},[41,1012,892],{"class":51},[41,1014,1015],{"class":47}," total_exec_time ",[41,1017,1018],{"class":51},"DESC\n",[41,1020,1021,1023,1026],{"class":43,"line":520},[41,1022,190],{"class":51},[41,1024,1025],{"class":64}," 10",[41,1027,196],{"class":47},[15,1029,1030],{},"Focus on total execution time — a query called 100,000 times for 1 ms each\n(100 s total) is a better optimization target than one called once for 5 s.",[10,1032,1034],{"id":1033},"recap","Recap",[15,1036,1037,1038,1041,1042,1044,1045,1047,1048,1051],{},"Start optimization with ",[19,1039,1040],{},"EXPLAIN (ANALYZE, BUFFERS)"," — look for sequential scans\non large tables, row estimate errors, and disk sorts. Fix sequential scans with\nindexes; fix N+1 with joins; fix stale statistics with ",[19,1043,230],{},"; fix spilling\nsorts by raising ",[19,1046,245],{}," per session. Use ",[19,1049,1050],{},"pg_stat_statements"," to find which\nqueries consume the most cumulative time across all calls — that is where\noptimization pays off most.",[1053,1054,1055],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":37,"searchDepth":58,"depth":58,"links":1057},[1058,1059,1060,1067,1068,1069,1070],{"id":12,"depth":58,"text":13},{"id":29,"depth":58,"text":30},{"id":248,"depth":58,"text":249,"children":1061},[1062,1063,1064,1065,1066],{"id":253,"depth":95,"text":254},{"id":328,"depth":95,"text":329},{"id":423,"depth":95,"text":424},{"id":546,"depth":95,"text":547},{"id":619,"depth":95,"text":620},{"id":742,"depth":58,"text":743},{"id":819,"depth":58,"text":820},{"id":911,"depth":58,"text":912},{"id":1033,"depth":58,"text":1034},"SQL query optimization guide — reading EXPLAIN ANALYZE output, identifying sequential scans, fixing N+1 queries, join strategies, and statistics.","hard","md","SQL",{},"\u002Fblog\u002Fsql-query-optimization-explain","\u002Fsql\u002Fperformance\u002Fquery-optimization",{"title":5,"description":1071},"blog\u002Fsql-query-optimization-explain","Query Optimization","Indexes & Performance","performance","2026-06-20","oeQQNCcJ3Dt-2GhUV5U_acxIUqpyun5S_BpEZpjF4eg",1782244088560]