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 datetime
columna con un índice no agrupado. Estamos viendo un comportamiento de cliente / servidor muy inusual basado en el uso de una order by
clá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 datetime
tipo), 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_cursorprepexec
procedimiento de la causa completa del problema. Agregue a esto el hecho de que sp_cursorprepexec
tambié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 forceseek
tambié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:
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.
fuente
select id, test_date from [big table] where serial_number = ..... order by test_date
? Me pregunto siSELECT *
tiene un impacto negativo en tu rendimiento. Si usted tiene un índice no agrupado entest_date
y un índice agrupado enid
(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 rapidezsp_executesql
y vea qué sucede.Respuestas:
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.
fuente
the order
cláusula? ¿No debería el plan limitarse a laswhere
condiciones 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?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.
fuente
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.
fuente
order by
uso 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.