Showing posts with label sql. Show all posts
Showing posts with label sql. Show all posts

Tuesday, September 23, 2025

N+1 query problem? More like 1+N query problem!

Some years back, when I first learned about the N+1 query problem, I had a little bit of difficulty wrapping my mind around it -- or at least the naming of it. Inadvertently making lots of round trips to the database is bad, sure. And the problem gets a lot worse when you have record counts in the thousands (or more) instead of just a handful, sure. But why are we calling this "N+1"?

I eventually realized that the problem is that the nomenclature is backwards, chronologically! The order of operations happening when this problem gets triggered is:

  1. The database is queried for a list of records. (1 query)
  2. For each of those records, a follow-up query is made. (N queries)

I started thinking of the problem instead as the "1+N query problem." And for me, at least, the naming immediately became much more intuitive to grasp!

 


A quick perusal of search results makes it clear that this is a pretty #unpopularopinion. Almost all of the discussion of this problem out there exclusively uses the "N+1" terminology. 

I'm going to keep saying "1+N" in conversations with peers, though. I don't mind being one man standing against the tide on this specific issue! 🌊👨‍💻

Saturday, April 23, 2022

Investigation and fix: A SQL Server stored procedure slowdown

What happened?

At work this past week, I was alerted to a problem where a background job which processes incoming messages from a particular queue had slowed down dramatically. As a result, a significant delay had developed in providing certain event notifications to our customers.  

Our monitoring tooling showed that the culprit was a particular SQL Server stored procedure -- one that gets invoked as a part of processing each message from the queue. Execution time of that sproc had suddenly increased from near-instant to an average of 4 seconds, with spikes up to around 20 seconds. (The queue processing job is multi-threaded, so we were still processing about one message per second, but that was no longer keeping up with the incoming volume of messages during the busy hours of each day.)

No code deployments or changes to the environment had been made around the time of the initial slowdown; this problem had started occurring without an obvious root cause. 

Our investigation

The stored procedure in question comprises about a dozen individual SQL statements and queries. It wasn’t immediately clear which individual statement in the sproc was responsible for the slowness.

After a few initial investigative paths didn’t yield results, I decided to run a short trace on the live production database. The trace revealed that one particular SELECT query was consistently taking a long time to run during each run of the stored procedure.

Interestingly: When I ran that same SELECT query SQL manually, it completed near-instantly. However, when I ran some minor variations on that SELECT query, they did take a long time to execute.

Execution plans and indexes

SQL Server internally generates an “execution plan” for each query that it runs, whereby it decides how it will go about performing the operation that the query's SQL calls for. The SQL Server Management Studio tool is able to display these execution plans in a nice graphical format.

When the problematic SELECT SQL was run manually, the execution plan showed that it was – correctly – leveraging an existing index on the target table. That index had been originally added to support this specific query. It includes fields needed by the query’s lookup criteria.

Looking back at the results of the trace, the problematics SELECT queries being run during execution of the stored procedure were being run using a different execution plan – not leveraging the custom-designed index at all! A full table scan was being run by SQL Server to gather the query results. As that  table currently sits at a total row count of over 150 million, those queries were taking a while to run!

The specific SQL being run (simplified a bit here for readability) was:

SELECT TOP 1 ID
FROM myTable t
WHERE t.CreationDate > DATEADD(day, -30, GetDate())
  AND (t.Email = @Email OR t.PhoneNumber = @PhoneNumber)
  AND t.CompanyID = @CompanyID)
ORDER BY t.ID ASC

The custom index includes the following fields:
  •     CompanyID
  •     Email
  •     CreationDate
  •     PhoneNumber

Can you spot the problem?



Take a look at the ID field. That field isn’t part of the index; but it is being used in the SQL’s ORDER BY clause! And that clause, as it turns out, is used by the database as one of the factors in how it seeks for the single (TOP 1) row to return.

Through its own inscrutable internal algorithms, prior to April, SQL Server had been using an execution plan that did leverage the custom index for this query, despite the presence of the ID field in the ORDER BY clause. After April, some tipping point in the database’s characteristics was evidently hit that made SQL Server decide to no longer use that index in its execution plan during runs of the stored procedure.

The fix was to adjust the SELECT query to ORDER BY CreationDate, rather than by ID. This had the same logical effect with regard to selection of the returned row; and it got SQL Server to once again use our custom index while running the stored procedure, resulting in the resumption of speedy performance in processing of messages from the queue.

How did we verify that the fix worked?

  • We observed that new records continued to be inserted correctly (and now, a lot more quickly!) into the database table.
  • Execution times for the stored procedure went way down. We could see this both from the database itself, and from the our queue's web interface – the green line here is the rate of messages being processed off the queue, with the fix having been applied just after the 10:20 mark:

So what are the lessons here?

  • Having the ability to determine which specific SQL statement was the root cause of the downstream slowness was key to being able to devise a fix. (The fact that our monitoring instrumentation only reported to the granularity of which stored procedure was running slowly -- but not which individual SQL statement within the sproc was slow -- made this trickier.)
  • The availability of multiple weeks of monitoring data from our instrumentation did make it quick and obvious to confirm that there had been a dramatic change in performance from one of our stored procedures, which was the cause of the problem.
  • When a particular SELECT statement is slow, ensuring that a properly-matching database index is in place – and that index is being applied to live queries in the expected manner – is a very good thing to check!

Wednesday, August 10, 2016

Bulk upsert into SQL Server

I recently worked through a situation where a bulk upsert (update if a matching record already exists; otherwise insert a new record) to SQL Server implemented using Entity Framework 6 was producing the correct results, but running very slowly. 

I was able to improve the performance of the bulk upsert by about 600x (!!!) by replacing the Entity Framework implementation with a an new approach based on a stored procedure taking a table-valued parameter.

Entity Framework implementation – very slow

The original code, using Entity Framework 6 (altered to use a table / object type of “Employee” instead of the actual type I was working with):

DateTime now = DateTime.Now;
using (MyCustomContext context = new MyCustomContext())
{
    foreach (Employee employee in employeeData)
    {
        // Get the matching Employee record from the database, if there is one.
        Employee employeeRecord = context.Employees
            .Where(e => e.EmployeeID == employee.EmployeeID)
            .SingleOrDefault();

        bool isNewRecord = false;
        if (employeeRecord == null)
        {
            // We don't have a record in the database for this employee yet, 
            // so we'll add a new one.
            isNewRecord = true;
            employeeRecord = new Employee()
            {
                EmployeeID = employee.EmployeeID,
                CreationDate = now
            };
        }

        employeeRecord.ModificationDate = now;
        employeeRecord.FirstName = employee.FirstName;
        // (Set the remaining attributes...)

        if (isNewRecord)
        {
            context.Employees.Add(employeeRecord);
        }

        context.SaveChanges();
    }
}

That approach was taking around 300 seconds per 1000 records inserted.  It was bogging down my application in situations where I needed to insert/update tens of thousands of records at once.

Stored Procedure + Table-Valued Parameter implementation – fast!

In my testing, the revised approach below, replacing the Entity Framework implementation with a stored procedure taking a table-valued parameter, was able to upsert the same 1000 records in 0.5 second – a huge improvement!

I needed to add a custom table type with columns matching the Employees table:

CREATE TYPE EmployeesTableType AS TABLE
(
    [EmployeeID] [int] NOT NULL,
    [CreationDate] [datetime] NOT NULL,
    [FirstName] [nvarchar(100)] NOT NULL,
    /* More fields here... */
);

Next, the stored procedure to perform the upsert, taking input of an instance of the custom EmployeesTableType, and using the SQL Server MERGE command:

CREATE PROCEDURE dbo.UpsertEmployees
    @UpdateRecords dbo.EmployeesTableType READONLY
AS
BEGIN
    MERGE INTO Employees AS Target
    USING @UpdateRecords AS Source
    ON Target.EmployeeID = Source.EmployeeID
    WHEN MATCHED THEN
        UPDATE SET Target.EmployeeID = Source.EmployeeID,
            Target.FirstName = Source.FirstName,
            /* More fields here... */
    WHEN NOT MATCHED THEN           
        INSERT (EmployeeID,
            FirstName,
            /* More field names here... */
            )
        VALUES (Source.EmployeeID,
            Source.FirstName,
            /* More field values here... */
            );
END

Finally, here’s the C# code I used to execute the stored procedure:

public static void UpsertEmployeeData(List<Employee> employees)
{
    string connectionString = MyCustomMethodToGetConnectionString();
    using(SqlConnection connection = new SqlConnection(connectionString))
    {
        connection.Open();

        using (SqlCommand command = connection.CreateCommand())
        {
            command.CommandText = "dbo.UpsertEmployees";
            command.CommandType = CommandType.StoredProcedure;

            SqlParameter parameter = command.Parameters.AddWithValue("@UpdateRecords",
                CreateUpdateRecordsSqlParam(employees));  // See implementation below
            parameter.SqlDbType = SqlDbType.Structured;
            parameter.TypeName = "dbo.EmployeesTableType";

            command.ExecuteNonQuery();
        }
    }
}

private static DataTable CreateUpdateRecordsSqlParam(IEnumerable employees)
{
    DataTable table = new DataTable();
    table.Columns.Add("EmployeeID", typeof(Int32));
    table.Columns.Add("FirstName", typeof(string));
    // More fields here...

    foreach (Employee employee in employees)
    {
        table.Rows.Add(employee.EmployeeID,
            employee.FirstName,
            // More fields here...
    }

    return table;
}

Ideally, this post will serve as a useful reference the next time I, or you, need to code up a bulk upsert into SQL Server!

Credit to this StackOverflow answer by Ryan Prechel which was the primary inspiration for this approach.

Wednesday, January 27, 2016

Entity Framework perf tip: Turn off AutoDetectChangesEnabled when doing bulk inserts

I was working on a situation this morning where, as part of a C# unit test method using Entity Framework 6 for database access, I was inserting about 1000 records into a SQL Server database.

This test was taking around 30-35 seconds to run, and I wanted to speed it up.  I tried re-coding the program to do the inserts using raw SQL, and that sped things up by about a factor of 3.

Thanks to a tip I found in a StackOverflow answer by “Steve”, I was able to get the same perf increase using Entity Framework by simply disabling AutoDetectChangesEnabled:

mycontext.Configuration.AutoDetectChangesEnabled = false;

Doing that before the loop with my calls to .Add() sped up the EF code to the point where it was performing just as well as the raw SQL. 

So, in situations where you’re using EF to do lots of inserts and you don’t need AutoDetectChangesEnabled (because you’re not also doing any updates to existing records), try turning it off for a possible nice performance improvement.

More info on this from Microsoft EF team member Arthur Vickers: Secrets of DetectChanges Part 3: Switching off automatic DetectChanges

Monday, June 01, 2015

SQL – Getting the record counts of each distinct value from a table column

SELECT column_name, COUNT(column_name) as RecordCount
FROM table_name
WHERE (whatever criteria)
GROUP BY column_name
ORDER BY column_name

Blogged to help me remember and memorize!

Monday, September 17, 2007

Fix: "Field references in ORDER BY clause must appear in the select list if SELECT DISTINCT is specified."

After upgrading a Java batch application with a Progress/OpenEdge back end to use a new JDBC driver version this morning, while testing the upgraded application to check for any issues with using the new driver, the app generated a SQL error: "[DataDirect][OpenEdge JDBC Driver][OpenEdge] Field references in ORDER BY clause must appear in the select list if SELECT DISTINCT is specified. (13637)".

I googled for that error message and the search didn't come up with any results, so I figured I'd blog the solution briefly here. It turns out the error message in this case is actually pretty good, as SQL datasource error messages go. Given a query that involves a SELECT DISTINCT, a JOIN, and a ORDER BY, like this one:

SELECT DISTINCT a.customer_id
 FROM users a
 INNER JOIN sales b  
 ON a.customer_id = b.customer_id
 ORDER BY b.customer_id 

The problem is that the field reference in the SELECT clause and the reference in the ORDER BY clause don't match. In this case, the SELECT clause references table "a" and the ORDER BY clause references table "b".

The fix is to change the query such that both references are the same:

SELECT DISTINCT a.customer_id
 FROM users a
 INNER JOIN sales b  
 ON a.customer_id = b.customer_id
 ORDER BY a.customer_id 

To double-check my example, I tested the first simple query above briefly in Microsoft Access, and it turns out that Access doesn't like the field mismatch either. The Access error message was "ORDER BY clause (...) conflicts with DISTINCT."