¿sp_cursorprepexec provoca 53 millones de lecturas?

9

Estamos ejecutando una instalación de Dynamics AX 2012 con SQL Server 2012. Sé que los cursores ya no deberían usarse, pero AX lo está usando y no podemos cambiar este comportamiento, así que tenemos que trabajar con él.

Hoy capté una consulta muy mala con más de 53 millones de lecturas y un tiempo de ejecución superior a 20 minutos.

Capturé esta consulta a través de nuestra herramienta de monitoreo SentryOne.

declare @p1 int
set @p1=1073773227
declare @p2 int
set @p2=180158805
declare @p5 int
set @p5=16
declare @p6 int
set @p6=1
declare @p7 int
set @p7=2
exec sp_cursorprepexec @p1 output,@p2 output,N'@P1 bigint,@P2 nvarchar(5),@P3 bigint,@P4 nvarchar(8),@P5 bigint,@P6 bigint,@P7 bigint,@P8 bigint,@P9 bigint,@P10 bigint,@P11 bigint,@P12 bigint,@P13 bigint,@P14 bigint,@P15 bigint,@P16 bigint,@P17 bigint,@P18 bigint,@P19 nvarchar(5),@P20 bigint,@P21 bigint,@P22 bigint,@P23 bigint,@P24 bigint',N'SELECT T1.PRODUCT,T1.EXTERNALVENDPARTY,T1.LIFECYCLESTATUS,T1.RECID,T2.ECORESPRODUCT,T2.ECORESDISTINCTPRODUCTVARIANT,T2.SGE,T2.ECORESREFORDERNUM,T2.ORDERNUM,T2.RECID,T3.ECORESREFORDERNUM,T3.NAME1,T3.NAME2,T3.NAME3,T3.RECID,T4.ECORESPRODUCT,T4.EXTERNALITEMID,T4.ECORESDISTINCTPRODUCTVARIANT,T4.RECID,T5.RECID,T5.PERSON,T6.RECID,T6.NAME,T6.INSTANCERELATIONTYPE,T7.RECID,T7.NAME,T7.INSTANCERELATIONTYPE,T8.PARTY,T8.ACCOUNTNUM,T8.RECID,T9.RECID,T9.DISPLAYPRODUCTNUMBER,T9.INSTANCERELATIONTYPE,T10.PRODUCT,T10.CATEGORY,T10.RECID,T11.RECID,T11.CODE,T11.NAME,T11.INSTANCERELATIONTYPE FROM INVENTTABLE T1 CROSS JOIN ECORESPRODUCTORDERNUM T2 CROSS JOIN ECORESPRODUCTORDERNUMTRANSLATION T3 LEFT OUTER JOIN VENDEXTERNALITEM T4 ON ((T4.PARTITION=5637144576) AND ((T2.ECORESPRODUCT=T4.ECORESPRODUCT) AND (T4.ECORESDISTINCTPRODUCTVARIANT=@P1))) CROSS JOIN HCMWORKER T5 CROSS JOIN DIRPARTYTABLE T6 CROSS JOIN DIRPARTYTABLE T7 CROSS JOIN VENDTABLE T8 CROSS JOIN ECORESPRODUCT T9 CROSS JOIN ECORESPRODUCTCATEGORY T10 CROSS JOIN ECORESCATEGORY T11 WHERE (((T1.PARTITION=5637144576) AND (T1.DATAAREAID=N''087'')) AND (T1.DATAAREAID=@P2)) AND ((T2.PARTITION=5637144576) AND ((T2.ECORESPRODUCT=T1.PRODUCT) AND (T2.SGE=@P3))) AND ((T3.PARTITION=5637144576) AND ((T3.ECORESREFORDERNUM=T2.ECORESREFORDERNUM) AND (T3.LANGUAGEID=@P4))) AND ((T5.PARTITION=5637144576) AND (T5.RECID=T2.PRODUCTMANAGER)) AND (((T6.PARTITION=5637144576) AND (T6.INSTANCERELATIONTYPE IN (@P5,@P6,@P7,@P8,@P9,@P10,@P11) )) AND (T6.RECID=T5.PERSON)) AND (((T7.PARTITION=5637144576) AND (T7.INSTANCERELATIONTYPE IN (@P12,@P13,@P14,@P15,@P16,@P17,@P18) )) AND (T1.EXTERNALVENDPARTY=T7.RECID)) AND (((T8.PARTITION=5637144576) AND (T8.DATAAREAID=N''087'')) AND ((T7.RECID=T8.PARTY) AND (T8.DATAAREAID=@P19))) AND (((T9.PARTITION=5637144576) AND (T9.INSTANCERELATIONTYPE IN (@P20,@P21,@P22) )) AND (T9.RECID=T1.PRODUCT)) AND ((T10.PARTITION=5637144576) AND (T10.PRODUCT=T9.RECID)) AND (((T11.PARTITION=5637144576) AND (T11.INSTANCERELATIONTYPE IN (@P23,@P24) )) AND (T11.RECID=T10.CATEGORY))',@p5 output,@p6 output,@p7 output,0,N'087',5637146082,N'de',41,2303,2377,2975,2978,5329,6886,41,2303,2377,2975,2978,5329,6886,N'087',3265,3266,3267,2665,4423
select @p1, @p2, @p5, @p6, @p7

Lo primero que noté es que esta consulta estaba usando un cursor. Por curiosidad, copié la declaración y la ejecuté en Management Studio sin el cursor (tengo que admitir que reemplacé los parámetros de la consulta para poder ejecutarla). Dentro de SSMS, la consulta finalizó en 30 segundos. No muy rápido, pero aún más rápido que la alternativa del cursor.

Aquí te proporciono los dos planes:

El plan sin el cursor sigue siendo un plan muy malo, pero es mucho mejor. Mi pregunta aquí es: ¿Puede alguien explicarme por qué la versión del cursor necesita 53 millones de lecturas?

Estadísticas de consulta con cursor:

Duration    CPU         Reads       Writes  Est Rows    Actual Rows
1.396.212   1.379.157   53.270.895  3.878   30          2

Estadísticas de consulta sin cursor:

Duration    CPU         Reads       Writes  Est Rows    Actual Rows
23.337      1.703       665.113     13      4.287       34.813

Parece extraño obtener 34.813 filas en lugar de 2; pero estoy bastante seguro de que completé los parámetros correctos. Pensé que esto era quizás un capricho curioso de SQL Sentry ya que solo copié las estadísticas desde allí.

Espero poder proporcionarle toda la información necesaria para usted. Además, si alguien tiene algunas buenas lecturas, comprenderá mejor los cursores que sería genial.

Hans Vader
fuente

Respuestas:

10

En primer lugar, me sorprende que el número real de filas para ambas consultas de SQL Sentry no sea más o menos el mismo.

Segundo. Es difícil saber qué tan correctas son sus estimaciones en el plan con un cursor sin un plan real, pero algunas cosas me destacan. (PD: consulte mi respuesta aquí para obtener un plan real).

Dicho esto, hay un par de cosas que se pueden observar en su plan estimado.

Hay una advertencia sobre índices no coincidentes debido a la parametrización. La eliminación de la parametrización para que SQL Server pueda usar esos incomparables podría mejorar dramáticamente la E / S.

El número estimado de filas entre los 2 planes también está dramáticamente apagado. En su plan con un cursor, tiene un número estimado de filas del elemento de venta externo de 11. En su plan sin cursor tiene un número estimado y real de filas de casi 200K. Si sus registros de 200K realmente entran en ese operador de carrete, eso podría ser doloroso.

Todos los operadores tienen estimaciones muy diferentes (mucho más pequeñas en el plan con un cursor), por lo que tal vez su plan fue compilado y almacenado en caché con valores de parámetros diferentes de los que está utilizando en la consulta sin cursor. (conocido como rastreo de parámetros )

También hay una elección muy extraña en la búsqueda de índice + búsqueda de teclas en la tabla de invento. El plan está usando typeIdx y luego realiza búsquedas clave en el índice agrupado (itemidx). Si sus estimaciones están fuera de lugar y SQL Server tiene que hacer muchas búsquedas clave que podrían explicar muchas IO también. No estoy familiarizado con el stopidx que tiene en su plan sin un cursor, pero parece que está cubriendo, por lo que probablemente sea una mejor opción para los parámetros que proporcionó. Supongo que eligió el typeidx porque es mucho más estrecho, pero eso podría deberse a valores de tiempo de compilación diferentes de los que proporciona la ejecución problemática.

En resumen, eliminaría la parametrización para esta consulta en AX para que genere un plan con los valores reales, de modo que elija los índices "mejores" como lo demuestra el plan (y los tiempos de ejecución) en SSMS. Esto también permitiría a SQL Server usar los índices filtrados. Para hacerlo, haga que un desarrollador agregue la forceliteralspalabra clave en el código de la aplicación donde se ejecuta esta consulta y vea qué sucede.

Eso probablemente aún lo dejaría con una consulta que demora 30 segundos (similar a lo que tiene en SSMS), pero eso es solo una cuestión de ajuste. Faltan advertencias de índice en sus planes y creo que un índice en ecoresproductordernum.sge podría ayudar, por ejemplo, pero no conozco esas tablas y creo que se agregan mediante personalizaciones. Los principios generales de ajuste ayudarían aquí, pero eso probablemente sería demasiado amplio para esta respuesta (cubriendo índices, ...)

Tom V - prueba topanswers.xyz
fuente
Esto resolvió mi problema. Realmente tuvimos dos problemas: detección de parámetros e índices filtrados. Nos perdimos algunas relaciones en AX, por lo que la aplicación generó esta extraña cláusula "in" para DirPartyTable. Fin de la historia: nunca te saltes las relaciones en la mesa :)
Hans Vader