Error de rendimiento del índice de fecha y hora de SQL Server 2008

11

Estamos utilizando SQL Server 2008 R2 y tenemos una tabla muy grande (más de 100 millones de filas) con un índice de identificación principal y una datetimecolumna con un índice no agrupado. Estamos viendo un comportamiento de cliente / servidor muy inusual basado en el uso de una order bycláusula específicamente en una columna de fecha y hora indexada .

Leí la siguiente publicación: /programming/1716798/sql-server-2008-ordering-by-datetime-is-too-slow pero hay más cosas con el cliente / servidor de lo que es Comience descrito aquí.

Si ejecutamos la siguiente consulta (editada para proteger algún contenido):

select * 
from [big table] 
where serial_number = [some number] 
order by test_date desc

La consulta agota el tiempo de espera cada vez. En el SQL Server Profiler, la consulta ejecutada se ve así para el servidor:

exec sp_cursorprepexec @p1 output,@p2 output,NULL,N'select * .....

Ahora, si modifica la consulta a, diga esto:

declare @temp int;
select * from [big table] 
where serial_number = [some number] 
order by test_date desc

El SQL Server Profiler muestra que la consulta ejecutada se ve así para el servidor, y FUNCIONA instantáneamente:

exec sp_prepexec @p1 output, NULL, N'declare @temp int;select * from .....

De hecho, incluso puede poner un comentario vacío ('-;') en lugar de una declaración de declaración no utilizada y obtener el mismo resultado. Inicialmente, apuntamos al preprocesador sp como la causa principal de este problema, pero si hace esto:

select * 
from [big table] 
where serial_number = [some number] 
order by Cast(test_date as smalldatetime) desc

Funciona instantáneamente también (puede lanzarlo como cualquier otro datetimetipo), devolviendo el resultado en milisegundos. Y el generador de perfiles muestra la solicitud al servidor como:

exec sp_cursorprepexec @p1 output, @p2 output, NULL, N'select * from .....

Eso excluye un poco el sp_cursorprepexecprocedimiento de la causa completa del problema. Agregue a esto el hecho de que sp_cursorprepexectambién se llama cuando no se usa 'ordenar por' y el resultado también se devuelve instantáneamente.

Hemos buscado en Google este problema bastante, y veo problemas similares publicados por otros, pero ninguno lo desglosa a este nivel.

Entonces, ¿han presenciado otros este comportamiento? ¿Alguien tiene una solución mejor que poner SQL sin sentido delante de la instrucción select para cambiar el comportamiento? Siendo que SQL Server debería invocar el orden una vez que se recopilan los datos, parece que este es un error en el servidor que ha persistido durante mucho tiempo. Hemos encontrado que este comportamiento es consistente en muchas de nuestras tablas grandes y es reproducible.

Ediciones:

También debería agregar poner un forceseektambién hace que el problema desaparezca.

Debo agregar para ayudar a los buscadores, el error de tiempo de espera ODBC arrojado es: [Microsoft] [ODBC SQL Server Driver] Operación cancelada

Agregado 10/12/2012: Aún buscando la causa raíz, (junto con haber creado una muestra para dar a Microsoft, publicaré todos los resultados aquí después de enviar). He estado cavando en el archivo de rastreo ODBC entre una consulta de trabajo (con un comentario agregado / declaración de declaración) y una consulta que no funciona. La diferencia fundamental de seguimiento se publica a continuación. Se produce en la llamada a la llamada SQLExtendedFetch después de que todas las discusiones SQLBindCol se hayan completado. La llamada falla con el código de retorno -1, y el subproceso principal luego ingresa SQLCancel. Como podemos producir esto con los controladores ODBC Native Client y Legacy, sigo señalando algunos problemas de compatibilidad en el lado del servidor.

(clip)
MSSQLODBCTester 1664-1718   EXIT  SQLBindCol  with return code 0 (SQL_SUCCESS)
        HSTMT               0x001EEA10
        UWORD                       16 
        SWORD                        1 <SQL_C_CHAR>
        PTR                0x03259030
        SQLLEN                    51
        SQLLEN *            0x0326B820 (0)

MSSQLODBCTester 1664-1718   ENTER SQLExtendedFetch 
        HSTMT               0x001EEA10
        UWORD                        1 <SQL_FETCH_NEXT>
        SQLLEN                     1
        SQLULEN *           0x032677C4
        UWORD *             0x032679B0

MSSQLODBCTester 1664-1fd0   ENTER SQLCancel 
        HSTMT               0x001EEA10

MSSQLODBCTester 1664-1718   EXIT  SQLExtendedFetch  with return code -1 (SQL_ERROR)
        HSTMT               0x001EEA10
        UWORD                        1 <SQL_FETCH_NEXT>
        SQLLEN                     1
        SQLULEN *           0x032677C4
        UWORD *             0x032679B0

        DIAG [S1008] [Microsoft][ODBC SQL Server Driver]Operation canceled (0) 

MSSQLODBCTester 1664-1fd0   EXIT  SQLCancel  with return code 0 (SQL_SUCCESS)
        HSTMT               0x001EEA10

MSSQLODBCTester 1664-1718   ENTER SQLErrorW 
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C
        SDWORD *            0x08BFFF08
        WCHAR *             0x08BFF85C 
        SWORD                      511 
        SWORD *             0x08BFFEE6

MSSQLODBCTester 1664-1718   EXIT  SQLErrorW  with return code 0 (SQL_SUCCESS)
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C [       5] "S1008"
        SDWORD *            0x08BFFF08 (0)
        WCHAR *             0x08BFF85C [      53] "[Microsoft][ODBC SQL Server Driver]Operation canceled"
        SWORD                      511 
        SWORD *             0x08BFFEE6 (53)

MSSQLODBCTester 1664-1718   ENTER SQLErrorW 
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C
        SDWORD *            0x08BFFF08
        WCHAR *             0x08BFF85C 
        SWORD                      511 
        SWORD *             0x08BFFEE6

MSSQLODBCTester 1664-1718   EXIT  SQLErrorW  with return code 100 (SQL_NO_DATA_FOUND)
        HENV                0x001E7238
        HDBC                0x001E7B30
        HSTMT               0x001EEA10
        WCHAR *             0x08BFFC5C
        SDWORD *            0x08BFFF08
        WCHAR *             0x08BFF85C 
        SWORD                      511 
        SWORD *             0x08BFFEE6
(clip)

Se agregó un caso de Microsoft Connect 10/12/2012:

https://connect.microsoft.com/SQLServer/feedback/details/767196/order-by-datetime-in-odbc-fails-for-clean-sql-statements#details

También debo tener en cuenta que buscamos los planes de consulta tanto para las consultas que funcionan como para las que no funcionan. Ambos se reutilizan adecuadamente según el recuento de ejecuciones. Vaciar los planes en caché y volver a ejecutarlos no cambia el éxito de la consulta.

DBtheDBA
fuente
¿Qué sucede si lo intentas select id, test_date from [big table] where serial_number = ..... order by test_date? Me pregunto si SELECT *tiene un impacto negativo en tu rendimiento. Si usted tiene un índice no agrupado en test_datey un índice agrupado en id(suponiendo que eso es lo que se llama), esta consulta debe ser cubierta por dicho índice no agrupado y por lo tanto debe regresar con bastante rapidez
marc_s
Lo siento, buen punto. Debería haber incluido que intentamos modificar el espacio de columna seleccionado (eliminando el '*', etc.) en gran medida con varias combinaciones. El comportamiento descrito anteriormente persistió a través de esos cambios.
DBtheDBA
He vinculado mis cuentas ahora a ese sitio. Si un moderador quiere mover la publicación a ese sitio, estoy bien de cualquier manera. Uno de mis desarrolladores me señaló ese sitio después de publicar aquí.
DBtheDBA
¿Qué pila de clientes se está utilizando aquí? Sin todo el texto de seguimiento, ese parece ser el problema. Intente envolver la llamada original dentro sp_executesqly vea qué sucede.
Jon Seigel
1
¿Cómo se ve el plan de ejecución lenta? Parámetro olfateando?
Martin Smith

Respuestas:

6

No hay misterio, obtienes un buen (er) o (realmente) mal plan básicamente al azar porque no hay una opción clara para que el índice lo use. Si bien es convincente para la cláusula ORDER BY y, por lo tanto, evita la ordenación, su índice no agrupado en la columna de fecha y hora es una muy mala elección para esta consulta. Lo que haría un índice mucho mejor para esta consulta sería uno en (serial_number, test_date). Aún mejor, esto sería un muy buen candidato para una clave de índice agrupada .

Como regla general, las series de tiempo deben estar agrupadas por la columna de tiempo, porque la gran mayoría de las solicitudes están interesadas en rangos de tiempo específicos. Si los datos también están particionados inherentemente en una columna con baja selectividad, como parece ser el caso con su número de serie, entonces esta columna debe agregarse como la más a la izquierda en la definición de clave agrupada.

Remus Rusanu
fuente
Estoy un poco confundido aquí. ¿Por qué se basaría el plan en una the ordercláusula? ¿No debería el plan limitarse a las wherecondiciones ya que el orden solo debería ocurrir después de que se hayan obtenido las filas? ¿Por qué el servidor intentaría ordenar los registros antes de tener todo el conjunto de resultados?
DBtheDBA
55
Esto tampoco explica por qué agregar un comentario al comienzo de la consulta afecta la duración de la ejecución.
cfradenburg
Además, nuestras tablas casi siempre se consultan por número de serie, no por test_date. Tenemos índices no agrupados en ambos, y solo agrupados en la columna id de la tabla. Es un almacén de datos operativos, y agregar índices agrupados en otras columnas solo generaría divisiones de página y un rendimiento más bajo.
DBtheDBA
1
@DBtheDBA: si desea hacer un reclamo por un 'error', debe hacer una investigación y divulgación adecuadas. Para ver el esquema exacto de la tabla y las estadísticas exportadas, siga Cómo generar un script de los metadatos de base de datos necesarios para crear una base de datos solo de estadísticas en SQL Server 2005 y en SQL Server 2008 , específicamente todas las estadísticas de script importantes : estadísticas de script e histogramas . Agregue estos a la información de la publicación junto con los pasos que reproducen el problema.
Remus Rusanu
1
Leímos eso antes durante nuestras búsquedas, y entiendo lo que estás diciendo, pero hay una falla fundamental en algo que el servidor está haciendo aquí. Hemos reconstruido la tabla y los índices, y la hemos reproducido en una tabla nueva. La opción de recompilación no soluciona el problema, lo cual es una gran pista de que algo está mal. No dudo que poner índices agrupados en todo podría solucionar este problema, pero no es una solución a la causa raíz, es una solución alternativa y costosa en una tabla grande.
DBtheDBA
0

Documente los detalles de cómo reproducir el error y envíelo en connect.microsoft.com. Lo comprobé y ya no podía ver nada que estuviera relacionado con esto.

Cfradenburg
fuente
Haré que mi DBA escriba un script mañana para crear un entorno para reproducir. No creo que sea tan difícil. Lo publicaré aquí también si alguien está interesado en probarlo por sí mismo.
DBtheDBA
Publique el elemento de conexión también cuando se abra. De esa manera, si alguien más tiene este problema, se lo señalará. Y cualquiera que esté viendo esta pregunta puede querer votar el artículo, por lo que es más probable que Microsoft le preste atención.
cfradenburg
0

Mi hipótesis es que estás en conflicto con el caché del plan de consulta. (Remus podría estar diciendo lo mismo que yo, pero de una manera diferente).

Aquí hay un montón de detalles sobre cómo SQL planifica el almacenamiento en caché .

Pasando por alto los detalles: Alguien ejecutó esa consulta anteriormente, para un [algún número] en particular. SQL examinó el valor proporcionado, los índices y las estadísticas para la tabla / columnas relevantes, etc. y creó un plan que funcionó bien para ese [número] en particular. Luego almacenó en caché el plan, lo ejecutó y devolvió los resultados a la persona que llamó.

Más tarde, alguien más está ejecutando la misma consulta, para un valor diferente de [algún número]. Este valor particular da como resultado un número muy diferente de filas de resultados y el motor debería crear un plan diferente para esta instancia de la consulta. Pero no funciona de esa manera. En cambio, SQL toma la consulta y (más o menos) realiza una búsqueda de mayúsculas y minúsculas en la caché de consultas, buscando una versión preexistente de la consulta. Cuando encuentra el de antes, solo usa ese plan.

La idea es que ahorre el tiempo requerido para decidir sobre el plan y construirlo. El agujero en la idea es cuando se ejecuta la misma consulta con valores que producen resultados muy diferentes. Deberían tener planes diferentes, pero no lo tienen. Quien ejecutó la consulta primero ayuda a establecer el comportamiento de todos los que la ejecutan después.

Un ejemplo rápido: seleccione * de [personas] donde apellido = 'SMITH' - apellido muy popular en los EE. UU. IR seleccione * de [personas] donde apellido = 'BONAPARTE' - NO apellido popular en los EE. UU.

Cuando se ejecuta la consulta para BONAPARTE, se reutilizará el plan que se creó para SMITH. Si SMITH causó un escaneo de la tabla (que podría ser bueno , si las filas en la tabla son 99% SMITH), entonces BONAPARTE también obtendrá un escaneo de la tabla. Si BONAPARTE se ejecutó antes de SMITH, se podría construir y usar un plan con un índice, y luego volver a usarlo para SMITH (que podría ser mejor con el escaneo de la tabla). Es posible que las personas no se den cuenta de que el rendimiento de SMITH es pobre, ya que esperan un mal rendimiento, ya que se debe leer toda la tabla y leer el índice y no se nota directamente el salto a la tabla.

Con respecto a sus cambios que deberían cambiar cualquier cosa, sospecho que SQL solo lo ve como una consulta totalmente diferente y está creando un nuevo plan, específico para su valor de [algún número].

Para probar esto, realice un cambio sin sentido en la consulta, como agregar algunos espacios entre FOR y el nombre de la tabla, o ponga un comentario al final. ¿Es rápido? Si es así, eso se debe a que esa consulta es ligeramente diferente de lo que está en la memoria caché, por lo que SQL hizo lo que hace para consultas "nuevas".

Para una solución, miraría tres cosas. Primero, asegúrese de que sus estadísticas estén actualizadas. Esto realmente debería ser lo primero que haces cuando una consulta parece actuar de manera extraña o aleatoria. Su DBA debería estar haciendo esto, pero las cosas suceden. La forma habitual de garantizar estadísticas actualizadas es volver a indexar sus tablas, lo cual no es necesariamente algo liviano, pero también hay opciones para actualizar las estadísticas.

La segunda cosa a considerar es agregar índices a lo largo de las líneas de las sugerencias de Remus. Con un índice mejor / diferente, un valor versus otro podría ser más estable y no variar tan salvajemente.

Si eso no ayuda, lo tercero que debe intentar es forzar un nuevo plan cada vez que ejecute la declaración, utilizando la palabra clave RECOMPILE:

seleccione * de [tabla grande] donde serial_number = [algún número] ordenar por test_date desc OPTION (RECOMPILE)

Hay un artículo que describe una situación similar aquí . Francamente, solo había visto RECOMPILE aplicado a procedimientos almacenados antes, pero parece funcionar con sentencias SELECT "regulares". Kimberly Tripp nunca me ha dirigido mal.

También puede buscar en la función llamada " guías de plan ", pero es más compleja y puede ser exagerada.

estrecho de Darin
fuente
Para cubrir algunas de estas preocupaciones: 1. Las estadísticas se han actualizado, se están actualizando. 2. Hemos intentado indexar de varias maneras (cubriendo índices, etc.) pero el problema parece estar más relacionado con el order byuso específico de un índice de fecha y hora. 3. Solo probé tu idea con la opción RECOMPILAR, todavía falló, lo que me sorprendió un poco, esperaba que funcionara, aunque no sé si es una solución para la producción.
DBtheDBA