Sample Slow Running Query
SELECT
r.RegionName,
s.StoreName,
YEAR(o.OrderDate) AS OrderYear,
MONTH(o.OrderDate) AS OrderMonth,
COUNT(DISTINCT o.OrderID) AS TotalOrders,
SUM(od.Quantity * od.UnitPrice * (1 - od.Discount)) AS TotalRevenue,
AVG(od.Quantity * od.UnitPrice * (1 - od.Discount)) AS AverageOrderValue
FROM
Sales.Orders o
INNER JOIN
Sales.OrderDetails od ON o.OrderID = od.OrderID
INNER JOIN
Sales.Stores s ON o.StoreID = s.StoreID
INNER JOIN
Sales.Regions r ON s.RegionID = r.RegionID
WHERE
r.RegionName = 'North America'
AND o.OrderDate >= '2025-01-01'
AND o.OrderDate < '2025-02-01'
GROUP BY
r.RegionName,
s.StoreName,
YEAR(o.OrderDate),
MONTH(o.OrderDate);
Steps to Resolve
Step 1: Set profiling and capture baseline performance
Action: Enable query profiling in MySQL to measure the exact execution duration of our slow query before making any modifications.
SQL Command:
SQLSET profiling = 1; -- Execute the slow running query above SELECT r.RegionName, s.StoreName, YEAR(o.OrderDate) AS OrderYear, MONTH(o.OrderDate) AS OrderMonth, COUNT(DISTINCT o.OrderID) AS TotalOrders, SUM(od.Quantity * od.UnitPrice * (1 - od.Discount)) AS TotalRevenue, AVG(od.Quantity * od.UnitPrice * (1 - od.Discount)) AS AverageOrderValue FROM Sales.Orders o INNER JOIN Sales.OrderDetails od ON o.OrderID = od.OrderID INNER JOIN Sales.Stores s ON o.StoreID = s.StoreID INNER JOIN Sales.Regions r ON s.RegionID = r.RegionID WHERE r.RegionName = 'North America' AND o.OrderDate >= '2025-01-01' AND o.OrderDate < '2025-02-01' GROUP BY r.RegionName, s.StoreName, YEAR(o.OrderDate), MONTH(o.OrderDate); -- Check the query duration profile SHOW PROFILES;Sample Output (
SHOW PROFILES):PlaintextQuery_ID Duration Query 1 4.85230000 SELECT r.RegionName, s.StoreName, YEAR(o.OrderDate)...Explanation of Output: The baseline execution time is 4.8523 seconds, indicating severe performance bottlenecks caused by full table scans and temporary sorting files.
Step 2: Run and analyze the execution plan using EXPLAIN FORMAT=TREE
Action: Prepend
EXPLAIN FORMAT=TREEto the slow query to visualize the hierarchical execution flow and access paths.SQL Command:
SQLEXPLAIN FORMAT=TREE SELECT r.RegionName, s.StoreName, YEAR(o.OrderDate) AS OrderYear, MONTH(o.OrderDate) AS OrderMonth, COUNT(DISTINCT o.OrderID) AS TotalOrders, SUM(od.Quantity * od.UnitPrice * (1 - od.Discount)) AS TotalRevenue, AVG(od.Quantity * od.UnitPrice * (1 - od.Discount)) AS AverageOrderValue FROM Sales.Orders o INNER JOIN Sales.OrderDetails od ON o.OrderID = od.OrderID INNER JOIN Sales.Stores s ON o.StoreID = s.StoreID INNER JOIN Sales.Regions r ON s.RegionID = r.RegionID WHERE r.RegionName = 'North America' AND o.OrderDate >= '2025-01-01' AND o.OrderDate < '2025-02-01' GROUP BY r.RegionName, s.StoreName, YEAR(o.OrderDate), MONTH(o.OrderDate);Sample Output (
EXPLAIN FORMAT=TREE):Plaintext-> Table scan on <temporary> (actual time=0.001..0.002 rows=1 loops=1) -> Aggregate: count(distinct o.OrderID), sum(...), avg(...) (cost=125430.20 rows=45200) -> Inner hash join (s.StoreID = o.StoreID) (cost=125430.20 rows=45200) -> Table scan on s (cost=12.50 rows=150) -> Inner hash join (r.RegionID = s.RegionID) (cost=110020.10 rows=45200) -> Filter: (r.RegionName = 'North America') (cost=1.20 rows=5) -> Table scan on r (cost=1.20 rows=10) -> Inner hash join (o.OrderID = od.OrderID) (cost=98500.00 rows=452000) -> Table scan on o (cost=45200.00 rows=452000) -> Table scan on od (cost=48100.00 rows=1250000)Explanation of Output: The output renders an indented tree layout showing multiple
Table scanoperations on base tablesoandod, forcing MySQL to read millions of rows from disk and create a heavy<temporary>table for aggregation.
Step 3: Create targeted indexes for joins and filters
Action: Build structural indexes on foreign keys and filter targets to replace expensive table scans with fast index lookups.
SQL Command:
SQLALTER TABLE Sales.Regions ADD INDEX idx_regions_name (RegionName); ALTER TABLE Sales.Stores ADD INDEX idx_stores_region (RegionID, StoreID); ALTER TABLE Sales.Orders ADD INDEX idx_orders_store_date (StoreID, OrderDate); ALTER TABLE Sales.OrderDetails ADD INDEX idx_orderdetails_covering (OrderID, Quantity, UnitPrice, Discount);Sample Output (
SHOW INDEX):PlaintextTable Non_unique Key_name Seq_in_index Column_name Cardinality Regions 1 idx_regions_name 1 RegionName 2 Stores 1 idx_stores_region 1 RegionID 2 Stores 1 idx_stores_region 2 StoreID 150 Orders 1 idx_orders_store_date 1 StoreID 150 Orders 1 idx_orders_store_date 2 OrderDate 452000 OrderDetails 1 idx_orderdetails_covering 1 OrderID 452000Explanation of Output: Confirms that the indexes are active and mapped correctly to the designated table columns.
Step 4: Update table statistics
Action: Force InnoDB to recalculate internal index cardinalities and table distribution statistics.
SQL Command:
SQLANALYZE TABLE Sales.Orders, Sales.OrderDetails, Sales.Stores, Sales.Regions;Sample Output (
ANALYZE TABLEresponse):PlaintextTable Op Msg_type Msg_text Sales.Orders analyze status OK Sales.OrderDetails analyze status OK Sales.Stores analyze status OK Sales.Regions analyze status OKExplanation of Output: The
OKstatus status confirms storage engine metrics are refreshed for the query optimizer.
Step 5: Verify memory buffer configuration
Action: Check that the database buffer pool size is configured correctly to cache indexed data pages in system RAM.
SQL Command:
SQLSHOW VARIABLES LIKE 'innodb_buffer_pool_size';Sample Output (
SHOW VARIABLES):PlaintextVariable_name Value innodb_buffer_pool_size 4294967296Explanation of Output: Returns the allocated memory buffer size (4 GB), verifying adequate memory caching to avoid disk input/output bottlenecks.
Step 6: Rewrite the query using Common Table Expressions (CTEs)
Action: Restructure the query logic to isolate date ranges and region filters upfront using CTEs before aggregating metrics.
Tuned SQL Query:
SQLWITH FilteredOrders AS ( SELECT o.OrderID, o.StoreID, YEAR(o.OrderDate) AS OrderYear, MONTH(o.OrderDate) AS OrderMonth FROM Sales.Orders o INNER JOIN Sales.Stores s ON o.StoreID = s.StoreID INNER JOIN Sales.Regions r ON s.RegionID = r.RegionID WHERE r.RegionName = 'North America' AND o.OrderDate >= '2025-01-01' AND o.OrderDate < '2025-02-01' ), AggregatedSales AS ( SELECT fo.OrderYear, fo.OrderMonth, fo.StoreID, COUNT(DISTINCT fo.OrderID) AS TotalOrders, SUM(od.Quantity * od.UnitPrice * (1 - od.Discount)) AS TotalRevenue, AVG(od.Quantity * od.UnitPrice * (1 - od.Discount)) AS AverageOrderValue FROM FilteredOrders fo INNER JOIN Sales.OrderDetails od ON fo.OrderID = od.OrderID GROUP BY fo.OrderYear, fo.OrderMonth, fo.StoreID ) SELECT r.RegionName, s.StoreName, a.OrderYear, a.OrderMonth, a.TotalOrders, a.TotalRevenue, a.AverageOrderValue FROM AggregatedSales a INNER JOIN Sales.Stores s ON a.StoreID = s.StoreID INNER JOIN Sales.Regions r ON s.RegionID = r.RegionID;Sample Output Execution Profile:
SQLSET profiling = 1; -- [Run Fully Tuned Query] SHOW PROFILES;PlaintextQuery_ID Duration Query 2 0.14210000 WITH FilteredOrders AS (...) SELECT ...Explanation of Output: The execution time drops from 4.85230000 seconds down to 0.14210000 seconds, proving a massive 34x performance improvement.
No comments:
Post a Comment