14

I have a C# program that executes a SQL query, using the code listed below. I've been using this code for a while with no problems until the other day.

I'm passing a query string to SQL that includes a list of strings, which are stock identifiers. A few days ago I ran it and the query timed out, and will run more than an hour if I let it. I've spent the past few days trying to debug this. In my original query, there were about 900 identifiers.

I've tried changing everything I can think of, and I get results I can't explain.

For example:

  1. the query works with one list of stocks, but not with another list of the same length in terms of number of string and total length

  2. it works with one list but not with the same list in reverse order

  3. with one list, it works if there are exactly 900 identifiers but not if there are 899 or 901, and I can include or exclude different identifiers and get the same results, so it isn't something funky with one of the identifiers.

In each of these cases, I captured the query string that is being passed by my program and copied into SQL Server Management Studio, and in every case, the query runs in 1 second.

I have read everything I can on this and other forums about queries that work in SQL Server Management Studio but time out when run from a program, but this seems different in that I can find cases where it fails and similar cases where it doesn't work.

I would appreciate suggestions about where I might look to see what might be going on.

using (SqlConnection conn = new SqlConnection(_connectString))
{
    conn.Open();

    using (SqlCommand cmd = new SqlCommand(queryString, conn))
    {
        cmd.Parameters.Clear();
        cmd.CommandTimeout = _timeout;

        SqlParameter param;

        if (parms != null)
        {
            foreach (string parm in parms.Keys)
            {
                param = cmd.Parameters.AddWithValue(parm, parms[parm]);
            }
        }

        SqlDataReader reader = cmd.ExecuteReader();

        while (reader.Read())
        {
            QueryResult record = new QueryResult();
            record.Fields = new List<object>();

            for (int i = 0; i < returnColumns; ++i)
            {
                object value = reader.GetValue(i);

                if (value == DBNull.Value)
                    record.Fields.Add(null);
                else
                    record.Fields.Add(value);
            }

            result.Add(record);
        }

        reader.Close();
    }

    conn.Close();
}

Here is my query. In this version, I include 65 stocks and it doesn't work (<=64 does work).

select
    distinct a.Cusip
,   d.Value_ / f.CumAdjFactor as split_adj_val

from qai.prc.PrcScChg a

join qai.dbo.SecMapX b
    on a.Code = b.venCode
    and b.VenType = 1
    and b.exchange = 1
    and b.Rank = (select Min(Rank) from qai.dbo.SecMapX where VenCode = a.Code and VenType = 1 and Exchange = 1)

join qai.dbo.SecMapX b2
    on b2.seccode = b.seccode
    and b2.ventype = 40
    and b2.exchange = 1
    and b2.Rank = (select Min(Rank) from qai.dbo.SecMapX where SecCode = b.SecCode and VenType = 40 and Exchange = 1)

join qai.dbo.SecMapX b3
    on b3.seccode = b.seccode
    and b3.ventype = 33
    and b3.exchange = 1
    and b3.Rank = (select Min(Rank) from qai.dbo.SecMapX where SecCode = b.SecCode and VenType = 33 and Exchange = 1)

join qai.dbo.DXLSecInfo c
    on b2.VenCode = c.Code

join qai.dbo.DXLAmData d
    on c.Code = d.Code
    and d.Date_ = @Date
    and d.Item = 6

left JOIN qai.dbo.DS2Adj f 
    ON f.InfoCode = b3.VenCode
    AND f.AdjType = 2
    and f.AdjDate <= @Date
    and ( f.EndAdjDate >= @Date or f.EndAdjDate is null )

where 
    a.cusip in ('00101J10', '00105510', '00120410', '00130H10', '00206R10',
    '00282410', '00287Y10', '00289620', '00724F10', '00817Y10', '00846U10',
    '00915810', '00936310', '00971T10', '01381710', '01535110', '01741R10',
    '01849010', '02000210', '02144110', '02209S10', '02313510', '02360810',
    '02553710', '02581610', '02687478', '03027X10', '03073E10', '03076C10',
    '03110010', '03116210', '03209510', '03251110', '03265410', '03741110',
    '03748R10', '03783310', '03822210', '03948310', '04621X10', '05276910',
    '05301510', '05329W10', '05333210', '05348410', '05361110', '05430310',
    '05493710', '05722410', '05849810', '06050510', '06405810', '06738310',
    '07181310', '07373010', '07588710', '07589610', '08143710', '08467070',
    '08651610', '09062X10', '09247X10', '09367110', '09702310', '09972410')
Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
freckles
  • 143
  • 1
  • 6
  • 2
    we would need an example of the query string? Can be many things, could require an index, a query rewrite, etc... – Bruno Mar 31 '15 at 18:36
  • you need to specify timeout (long enough to complete your query) in connection string – Yasir Majeed Mar 31 '15 at 18:37
  • What is the value for _timeout? – UnhandledExcepSean Mar 31 '15 at 18:39
  • I'm really skeptical that a query with 900 parameters is running in 1 second. Caching? – crthompson Mar 31 '15 at 18:39
  • Your are using AddWithValue which requires .net to hit the database for each parameter to determine the datatype. And of course it may or may not get the proper datatype anyway. http://blogs.msmvps.com/jcoehoorn/blog/2014/05/12/can-we-stop-using-addwithvalue-already/ – Sean Lange Mar 31 '15 at 18:40
  • I came across a similar issue, what i noticed is whenever you are querying a table with a query string with named parameters(@parm) and the column you are querying is not indexed and also if column is VARCHAR even with index .. Performance is very random it not consistent. Using Stored Procedure should help you. – Prashant Mar 31 '15 at 18:48
  • I'm pretty much a beginner here, but it's one parameter that is a list of strings, and there are 900 strings in the list. – freckles Apr 01 '15 at 19:50
  • I've tried with various _timeout values, up to 50 minutes. – freckles Apr 01 '15 at 19:50

2 Answers2

8

Three things to look at, in order of preference:

  1. Avoid using the AddWithValue() function, as that can have catastrophic performance implications when ADO.Net guesses a column type wrong. Do what you must to be able to set an explicit DB type for each parameter
  2. Look into OPTION RECOMPILE.
  3. Look into OPTIMIZE FOR UNKNOWN. Do this only after the others have failed.
Community
  • 1
  • 1
Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • Thanks, I'll try these. I also just found out that there was some Windows updates that were applied to the server on Thursday night (I started having problems on Friday) so our IT person is looking into those. – freckles Apr 01 '15 at 19:52
  • Add instead of AddWithValue didn't help (though I made that change). And thanks for the link to the optimize for unknown information, which was very helpful. I added that option, and it absolutely fixed the problem. Thanks! – freckles Apr 01 '15 at 20:57
2

You haven't posted your query, but just based on how it's being built with the dynamic list of parameters and the sheer number of parameters, I'm going to make a guess and say it has something to do with parameter sniffing - see:

http://www.brentozar.com/archive/2013/06/the-elephant-and-the-mouse-or-parameter-sniffing-in-sql-server/

http://blogs.msdn.com/b/sqlprogrammability/archive/2008/11/26/optimize-for-unknown-a-little-known-sql-server-2008-feature.aspx

The basic idea of the issue is an optimal query execution plan is created for a specific set of parameters, which is very sub-optimal for another set.

There are several ways to get around parameter sniffing issues (thankfully, many of which opened up in sql server 2008).

You could:

  1. refactor your query
  2. add WITH RECOMPILE to your stored proc / option (recompile) to your query
  3. optimize for unknown/option (optimize for... to your proc/query
  4. others?
Kritner
  • 13,557
  • 10
  • 46
  • 72
  • 1
    Additionally you can try viewing your execution plan with `SET SHOWPLAN_ALL ON; ` at the start of your query in a test mode. Run it this way in both the working query and your non-working query and see if there are any glaring differences (this format is a bit ugly as it returns a Table of info you have to go through, unlike the query manager, but it could be useful in figuring out what the inconsistency is) – DarrenMB Mar 31 '15 at 18:48