Tengo una tabla relativamente grande (actualmente 2 millones de registros) y me gustaría saber si es posible mejorar el rendimiento de las consultas ad-hoc. La palabra ad-hoc es clave aquí. Agregar índices no es una opción (ya hay índices en las columnas que se consultan con más frecuencia).
Ejecutando una consulta simple para devolver los 100 registros actualizados más recientemente:
select top 100 * from ER101_ACCT_ORDER_DTL order by er101_upd_date_iso desc
Tarda varios minutos. Vea el plan de ejecución a continuación:
Detalles adicionales del escaneo de la tabla:
SQL Server Execution Times:
CPU time = 3945 ms, elapsed time = 148524 ms.
El servidor es bastante potente (memoria ram de 48 GB, procesador de 24 núcleos) y ejecuta sql server 2008 r2 x64.
Actualizar
Encontré este código para crear una tabla con 1,000,000 de registros. Pensé que podría ejecutar SELECT TOP 100 * FROM testEnvironment ORDER BY mailAddress DESC
en algunos servidores diferentes para averiguar si las velocidades de acceso al disco eran bajas en el servidor.
WITH t1(N) AS (SELECT 1 UNION ALL SELECT 1),
t2(N) AS (SELECT 1 FROM t1 x, t1 y),
t3(N) AS (SELECT 1 FROM t2 x, t2 y),
Tally(N) AS (SELECT TOP 98 ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM t3 x, t3 y),
Tally2(N) AS (SELECT TOP 5 ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM t3 x, t3 y),
Combinations(N) AS (SELECT DISTINCT LTRIM(RTRIM(RTRIM(SUBSTRING(poss,a.N,2)) + SUBSTRING(vowels,b.N,1)))
FROM Tally a
CROSS JOIN Tally2 b
CROSS APPLY (SELECT 'B C D F G H J K L M N P R S T V W Z SCSKKNSNSPSTBLCLFLGLPLSLBRCRDRFRGRPRTRVRSHSMGHCHPHRHWHBWCWSWTW') d(poss)
CROSS APPLY (SELECT 'AEIOU') e(vowels))
SELECT IDENTITY(INT,1,1) AS ID, a.N + b.N AS N
INTO #testNames
FROM Combinations a
CROSS JOIN Combinations b;
SELECT IDENTITY(INT,1,1) AS ID, firstName, secondName
INTO #testNames2
FROM (SELECT firstName, secondName
FROM (SELECT TOP 1000 --1000 * 1000 = 1,000,000 rows
N AS firstName
FROM #testNames
ORDER BY NEWID()) a
CROSS JOIN (SELECT TOP 1000 --1000 * 1000 = 1,000,000 rows
N AS secondName
FROM #testNames
ORDER BY NEWID()) b) innerQ;
SELECT firstName, secondName,
firstName + '.' + secondName + '@fake.com' AS eMail,
CAST((ABS(CHECKSUM(NEWID())) % 250) + 1 AS VARCHAR(3)) + ' ' AS mailAddress,
(ABS(CHECKSUM(NEWID())) % 152100) + 1 AS jID,
IDENTITY(INT,1,1) AS ID
INTO #testNames3
FROM #testNames2
SELECT IDENTITY(INT,1,1) AS ID, firstName, secondName, eMail,
mailAddress + b.N + b.N AS mailAddress
INTO testEnvironment
FROM #testNames3 a
INNER JOIN #testNames b ON a.jID = b.ID;
--CLEAN UP USELESS TABLES
DROP TABLE #testNames;
DROP TABLE #testNames2;
DROP TABLE #testNames3;
Pero en los tres servidores de prueba, la consulta se ejecutó casi instantáneamente. ¿Alguien puede explicar esto?
Actualización 2
Gracias por los comentarios, por favor, sigan viniendo ... me llevaron a intentar cambiar el índice de clave principal de no agrupado a agrupado con resultados bastante interesantes (¿e inesperados?).
No agrupado:
SQL Server Execution Times:
CPU time = 3634 ms, elapsed time = 154179 ms.
Agrupado:
SQL Server Execution Times:
CPU time = 2650 ms, elapsed time = 52177 ms.
¿Cómo es esto posible? Sin un índice en la columna er101_upd_date_iso, ¿cómo se puede utilizar un escaneo de índice agrupado?
Actualización 3
Según lo solicitado, este es el script de creación de tabla:
CREATE TABLE [dbo].[ER101_ACCT_ORDER_DTL](
[ER101_ORG_CODE] [varchar](2) NOT NULL,
[ER101_ORD_NBR] [int] NOT NULL,
[ER101_ORD_LINE] [int] NOT NULL,
[ER101_EVT_ID] [int] NULL,
[ER101_FUNC_ID] [int] NULL,
[ER101_STATUS_CDE] [varchar](2) NULL,
[ER101_SETUP_ID] [varchar](8) NULL,
[ER101_DEPT] [varchar](6) NULL,
[ER101_ORD_TYPE] [varchar](2) NULL,
[ER101_STATUS] [char](1) NULL,
[ER101_PRT_STS] [char](1) NULL,
[ER101_STS_AT_PRT] [char](1) NULL,
[ER101_CHG_COMMENT] [varchar](255) NULL,
[ER101_ENT_DATE_ISO] [datetime] NULL,
[ER101_ENT_USER_ID] [varchar](10) NULL,
[ER101_UPD_DATE_ISO] [datetime] NULL,
[ER101_UPD_USER_ID] [varchar](10) NULL,
[ER101_LIN_NBR] [int] NULL,
[ER101_PHASE] [char](1) NULL,
[ER101_RES_CLASS] [char](1) NULL,
[ER101_NEW_RES_TYPE] [varchar](6) NULL,
[ER101_RES_CODE] [varchar](12) NULL,
[ER101_RES_QTY] [numeric](11, 2) NULL,
[ER101_UNIT_CHRG] [numeric](13, 4) NULL,
[ER101_UNIT_COST] [numeric](13, 4) NULL,
[ER101_EXT_COST] [numeric](11, 2) NULL,
[ER101_EXT_CHRG] [numeric](11, 2) NULL,
[ER101_UOM] [varchar](3) NULL,
[ER101_MIN_CHRG] [numeric](11, 2) NULL,
[ER101_PER_UOM] [varchar](3) NULL,
[ER101_MAX_CHRG] [numeric](11, 2) NULL,
[ER101_BILLABLE] [char](1) NULL,
[ER101_OVERRIDE_FLAG] [char](1) NULL,
[ER101_RES_TEXT_YN] [char](1) NULL,
[ER101_DB_CR_FLAG] [char](1) NULL,
[ER101_INTERNAL] [char](1) NULL,
[ER101_REF_FIELD] [varchar](255) NULL,
[ER101_SERIAL_NBR] [varchar](50) NULL,
[ER101_RES_PER_UNITS] [int] NULL,
[ER101_SETUP_BILLABLE] [char](1) NULL,
[ER101_START_DATE_ISO] [datetime] NULL,
[ER101_END_DATE_ISO] [datetime] NULL,
[ER101_START_TIME_ISO] [datetime] NULL,
[ER101_END_TIME_ISO] [datetime] NULL,
[ER101_COMPL_STS] [char](1) NULL,
[ER101_CANCEL_DATE_ISO] [datetime] NULL,
[ER101_BLOCK_CODE] [varchar](6) NULL,
[ER101_PROP_CODE] [varchar](8) NULL,
[ER101_RM_TYPE] [varchar](12) NULL,
[ER101_WO_COMPL_DATE] [datetime] NULL,
[ER101_WO_BATCH_ID] [varchar](10) NULL,
[ER101_WO_SCHED_DATE_ISO] [datetime] NULL,
[ER101_GL_REF_TRANS] [char](1) NULL,
[ER101_GL_COS_TRANS] [char](1) NULL,
[ER101_INVOICE_NBR] [int] NULL,
[ER101_RES_CLOSED] [char](1) NULL,
[ER101_LEAD_DAYS] [int] NULL,
[ER101_LEAD_HHMM] [int] NULL,
[ER101_STRIKE_DAYS] [int] NULL,
[ER101_STRIKE_HHMM] [int] NULL,
[ER101_LEAD_FLAG] [char](1) NULL,
[ER101_STRIKE_FLAG] [char](1) NULL,
[ER101_RANGE_FLAG] [char](1) NULL,
[ER101_REQ_LEAD_STDATE] [datetime] NULL,
[ER101_REQ_LEAD_ENDATE] [datetime] NULL,
[ER101_REQ_STRK_STDATE] [datetime] NULL,
[ER101_REQ_STRK_ENDATE] [datetime] NULL,
[ER101_LEAD_STDATE] [datetime] NULL,
[ER101_LEAD_ENDATE] [datetime] NULL,
[ER101_STRK_STDATE] [datetime] NULL,
[ER101_STRK_ENDATE] [datetime] NULL,
[ER101_DEL_MARK] [char](1) NULL,
[ER101_USER_FLD1_02X] [varchar](2) NULL,
[ER101_USER_FLD1_04X] [varchar](4) NULL,
[ER101_USER_FLD1_06X] [varchar](6) NULL,
[ER101_USER_NBR_060P] [int] NULL,
[ER101_USER_NBR_092P] [numeric](9, 2) NULL,
[ER101_PR_LIST_DTL] [numeric](11, 2) NULL,
[ER101_EXT_ACCT_CODE] [varchar](8) NULL,
[ER101_AO_STS_1] [char](1) NULL,
[ER101_PLAN_PHASE] [char](1) NULL,
[ER101_PLAN_SEQ] [int] NULL,
[ER101_ACT_PHASE] [char](1) NULL,
[ER101_ACT_SEQ] [int] NULL,
[ER101_REV_PHASE] [char](1) NULL,
[ER101_REV_SEQ] [int] NULL,
[ER101_FORE_PHASE] [char](1) NULL,
[ER101_FORE_SEQ] [int] NULL,
[ER101_EXTRA1_PHASE] [char](1) NULL,
[ER101_EXTRA1_SEQ] [int] NULL,
[ER101_EXTRA2_PHASE] [char](1) NULL,
[ER101_EXTRA2_SEQ] [int] NULL,
[ER101_SETUP_MSTR_SEQ] [int] NULL,
[ER101_SETUP_ALTERED] [char](1) NULL,
[ER101_RES_LOCKED] [char](1) NULL,
[ER101_PRICE_LIST] [varchar](10) NULL,
[ER101_SO_SEARCH] [varchar](9) NULL,
[ER101_SSB_NBR] [int] NULL,
[ER101_MIN_QTY] [numeric](11, 2) NULL,
[ER101_MAX_QTY] [numeric](11, 2) NULL,
[ER101_START_SIGN] [char](1) NULL,
[ER101_END_SIGN] [char](1) NULL,
[ER101_START_DAYS] [int] NULL,
[ER101_END_DAYS] [int] NULL,
[ER101_TEMPLATE] [char](1) NULL,
[ER101_TIME_OFFSET] [char](1) NULL,
[ER101_ASSIGN_CODE] [varchar](10) NULL,
[ER101_FC_UNIT_CHRG] [numeric](13, 4) NULL,
[ER101_FC_EXT_CHRG] [numeric](11, 2) NULL,
[ER101_CURRENCY] [varchar](3) NULL,
[ER101_FC_RATE] [numeric](12, 5) NULL,
[ER101_FC_DATE] [datetime] NULL,
[ER101_FC_MIN_CHRG] [numeric](11, 2) NULL,
[ER101_FC_MAX_CHRG] [numeric](11, 2) NULL,
[ER101_FC_FOREIGN] [numeric](12, 5) NULL,
[ER101_STAT_ORD_NBR] [int] NULL,
[ER101_STAT_ORD_LINE] [int] NULL,
[ER101_DESC] [varchar](255) NULL
) ON [PRIMARY]
SET ANSI_PADDING OFF
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_PRT_SEQ_1] [varchar](12) NULL
SET ANSI_PADDING ON
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_PRT_SEQ_2] [varchar](120) NULL
SET ANSI_PADDING OFF
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_TAX_BASIS] [char](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_RES_CATEGORY] [char](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_DECIMALS] [char](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_TAX_SEQ] [varchar](7) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_MANUAL] [char](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_TR_LC_RATE] [numeric](12, 5) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_TR_FC_RATE] [numeric](12, 5) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_TR_PL_RATE] [numeric](12, 5) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_TR_DIFF] [char](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_TR_UNIT_CHRG] [numeric](13, 4) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_TR_EXT_CHRG] [numeric](13, 4) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_TR_MIN_CHRG] [numeric](13, 4) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_TR_MAX_CHRG] [numeric](13, 4) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_PL_UNIT_CHRG] [numeric](13, 4) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_PL_EXT_CHRG] [numeric](13, 2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_PL_MIN_CHRG] [numeric](13, 2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_PL_MAX_CHRG] [numeric](13, 2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_TAX_RATE_TYPE] [char](1) NULL
SET ANSI_PADDING ON
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ORDER_FORM] [varchar](2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_FACTOR] [int] NULL
SET ANSI_PADDING OFF
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_MGMT_RPT_CODE] [varchar](6) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ROUND_CHRG] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_WHOLE_QTY] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_SET_QTY] [numeric](15, 4) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_SET_UNITS] [numeric](15, 4) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_SET_ROUNDING] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_SET_SUB] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_TIME_QTY] [numeric](13, 4) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_GL_DISTR_PCT] [numeric](7, 4) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_REG_SEQ] [int] NULL
SET ANSI_PADDING ON
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ALT_DESC] [varchar](255) NULL
SET ANSI_PADDING OFF
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_REG_ACCT] [varchar](8) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_DAILY] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_AVG_UNIT_CHRG] [varchar](1) NULL
SET ANSI_PADDING ON
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ALT_DESC2] [varchar](255) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_CONTRACT_SEQ] [int] NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ORIG_RATE] [numeric](13, 4) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_DISC_PCT] [decimal](17, 10) NULL
SET ANSI_PADDING OFF
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_DTL_EXIST] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ORDERED_ONLY] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_SHOW_STDATE] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_SHOW_STTIME] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_SHOW_ENDATE] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_SHOW_ENTIME] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_SHOW_RATE] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_SHOW_UNITS] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_BASE_RATE] [numeric](13, 4) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_COMMIT_QTY] [numeric](11, 2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_MM_QTY_USED] [varchar](2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_MM_CHRG_USED] [varchar](2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ITEM_TEXT_1] [varchar](50) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ITEM_NBR_1] [numeric](13, 3) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ITEM_NBR_2] [numeric](13, 3) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ITEM_NBR_3] [numeric](13, 3) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_PL_BASE_RATE] [numeric](13, 4) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_REV_DIST] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_COVER] [int] NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_RATE_TYPE] [varchar](2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_USE_SEASONAL] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_TAX_EI] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_TAXES] [numeric](13, 2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_FC_TAXES] [numeric](13, 2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_PL_TAXES] [numeric](13, 2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_FC_QTY] [numeric](13, 2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_LEAD_HRS] [numeric](6, 2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_STRIKE_HRS] [numeric](6, 2) NULL
SET ANSI_PADDING ON
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_CANCEL_USER_ID] [varchar](10) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ST_OFFSET_HRS] [numeric](7, 2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_EN_OFFSET_HRS] [numeric](7, 2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_MEMO_FLAG] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_MEMO_EXT_CHRG] [numeric](13, 4) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_MEMO_EXT_CHRG_PL] [numeric](13, 4) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_MEMO_EXT_CHRG_TR] [numeric](13, 4) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_MEMO_EXT_CHRG_FC] [numeric](13, 4) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_TIME_QTY_EDIT] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_SURCHARGE_PCT] [decimal](17, 10) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_INCL_EXT_CHRG] [numeric](13, 4) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_INCL_EXT_CHRG_FC] [numeric](13, 4) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_CARRIER] [varchar](6) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_SETUP_ID2] [varchar](8) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_SHIPPABLE] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_CHARGEABLE] [varchar](2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ITEM_NBR_ALLOW] [varchar](2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ITEM_NBR_START] [int] NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ITEM_NBR_END] [int] NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ITEM_SUPPLIER] [varchar](8) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_TRACK_ID] [varchar](40) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_REF_INV_NBR] [int] NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_NEW_ITEM_STS] [varchar](2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_MSTR_REG_ACCT_CODE] [varchar](8) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ALT_DESC3] [varchar](255) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ALT_DESC4] [varchar](255) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ALT_DESC5] [varchar](255) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_SETUP_ROLLUP] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_MM_COST_USED] [varchar](2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_AUTO_SHIP_RCD] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ITEM_FIXED] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ITEM_EST_TBD] [varchar](3) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ROLLUP_PL_UNIT_CHRG] [numeric](13, 4) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ROLLUP_PL_EXT_CHRG] [numeric](13, 2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_GL_ORD_REV_TRANS] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_DISCOUNT_FLAG] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_SETUP_RES_TYPE] [varchar](6) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_SETUP_RES_CODE] [varchar](12) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_PERS_SCHED_FLAG] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_PRINT_STAMP] [datetime] NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_SHOW_EXT_CHRG] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_PRINT_SEQ_NBR] [int] NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_PAY_LOCATION] [varchar](3) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_MAX_RM_NIGHTS] [int] NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_USE_TIER_COST] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_UNITS_SCHEME_CODE] [varchar](6) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_ROUND_TIME] [varchar](2) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_LEVEL] [int] NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_SETUP_PARENT_ORD_LINE] [int] NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_BADGE_PRT_STS] [varchar](1) NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_EVT_PROMO_SEQ] [int] NULL
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD [ER101_REG_TYPE] [varchar](12) NULL
/****** Object: Index [PK__ER101_ACCT_ORDER] Script Date: 04/15/2012 20:24:37 ******/
ALTER TABLE [dbo].[ER101_ACCT_ORDER_DTL] ADD CONSTRAINT [PK__ER101_ACCT_ORDER] PRIMARY KEY CLUSTERED
(
[ER101_ORD_NBR] ASC,
[ER101_ORD_LINE] ASC,
[ER101_ORG_CODE] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 50) ON [PRIMARY]
La tabla tiene un tamaño de 2,8 GB con un tamaño de índice de 3,9 GB.
fuente
Table Scan
indica un montón (sin índice agrupado), por lo que el primer paso sería agregar un índice agrupado bueno y rápido a su tabla. El segundo paso podría ser investigar si un índice no agrupado ener101_upd_date_iso
ayudaría (y no causaría otros inconvenientes de rendimiento)er101_upd_date_iso
columna, probablemente también pueda deshacerse de la operación "Ordenar" en su plan de ejecución y acelerar las cosas aún másRespuestas:
Respuesta simple: NO. No puede ayudar con las consultas ad hoc en una tabla de 238 columnas con un factor de relleno del 50% en el índice agrupado.
Respuesta detallada:
Como he dicho en otras respuestas sobre este tema, el diseño de índices es tanto arte como ciencia y hay tantos factores a considerar que hay pocas reglas estrictas, si es que hay alguna. Debe considerar: el volumen de operaciones DML frente a SELECT, el subsistema de disco, otros índices / activadores en la tabla, la distribución de datos dentro de la tabla, son consultas que usan condiciones SARGable WHERE y varias otras cosas que ni siquiera puedo recordar bien ahora.
Puedo decir que no se puede brindar ayuda para preguntas sobre este tema sin una comprensión de la Tabla en sí, sus índices, desencadenantes, etc. Ahora que ha publicado la definición de la tabla (todavía está esperando los índices, pero la definición de la tabla solo apunta a 99% del problema) Puedo ofrecer algunas sugerencias.
Primero, si la definición de la tabla es precisa (238 columnas, 50% de factor de relleno), entonces puede ignorar el resto de las respuestas / consejos aquí ;-). Lamento ser menos que político aquí, pero en serio, es una búsqueda inútil sin conocer los detalles. Y ahora que vemos la definición de la tabla, queda bastante más claro por qué una consulta simple tomaría tanto tiempo, incluso cuando las consultas de prueba (Actualización n. ° 1) se ejecutaron tan rápido.
El principal problema aquí (y en muchas situaciones de bajo rendimiento) es un mal modelado de datos. 238 columnas no está prohibido al igual que tener 999 índices no está prohibido, pero tampoco es generalmente muy prudente.
Recomendaciones:
ANSI_PADDING OFF
es perturbador, sin mencionar inconsistente dentro de la tabla debido a las diversas adiciones de columnas a lo largo del tiempo. No estoy seguro de si puede solucionarlo ahora, pero lo ideal sería que siempre lo hicieraANSI_PADDING ON
, o al menos tuviera la misma configuración en todas lasALTER TABLE
declaraciones.PRIMARY
ya que ahí es donde SQL SERVER almacena todos sus datos y metadatos sobre sus objetos. Usted crea su tabla e índice agrupado (ya que son los datos de la tabla)[Tables]
y todos los índices no agrupados en[Indexes]
WHERE
condición, considere moverlo a la columna principal del índice agrupado. Suponiendo que se usa con más frecuencia que "ER101_ORD_NBR". Si "ER101_ORD_NBR" se usa con más frecuencia, consérvelo. Simplemente parece, asumiendo que los nombres de campo significan "OrganizationCode" y "OrderNumber", que "OrgCode" es una mejor agrupación que puede tener múltiples "OrderNumbers" dentro de ella.CHAR(2)
lugar de,VARCHAR(2)
ya que guardará un byte en el encabezado de la fila que rastrea los tamaños de ancho variable y suma millones de filas.SELECT *
perjudicará el rendimiento. No solo debido a que requiere que SQL Server devuelva todas las columnas y, por lo tanto, es más probable que realice un análisis de índice agrupado independientemente de sus otros índices, sino que también le toma tiempo a SQL Server ir a la definición de la tabla y traducir*
a todos los nombres de columna. . Debería ser un poco más rápido especificar los nombres de las 238 columnas en laSELECT
lista, aunque eso no ayudará con el problema de Escaneo. Pero, ¿alguna vez realmente necesitaste las 238 columnas al mismo tiempo?¡Buena suerte!
ACTUALIZACIÓN
Para completar la pregunta "cómo mejorar el rendimiento en una tabla grande para consultas ad-hoc", debe tenerse en cuenta que, si bien no ayudará en este caso específico, SI alguien está usando SQL Server 2012 (o más reciente cuando llegue ese momento) y SI la tabla no se está actualizando, entonces usar Columnstore Indexes es una opción. Para obtener más detalles sobre esa nueva función, mire aquí: http://msdn.microsoft.com/en-us/library/gg492088.aspx (creo que se hicieron para que se puedan actualizar a partir de SQL Server 2014).
ACTUALIZACIÓN 2
Las consideraciones adicionales son:
INT
,BIGINT
,TINYINT
,SMALLINT
,CHAR
,NCHAR
,BINARY
,DATETIME
,SMALLDATETIME
,MONEY
, etc) y más de 50 % de las filas sonNULL
, luego considere habilitar laSPARSE
opción que estuvo disponible en SQL Server 2008. Consulte la página de MSDN para usar columnas dispersas para obtener más detalles.fuente
*
y sin que uno dudosaHay algunos problemas con esta consulta (y esto se aplica a todas las consultas).
Falta de índice
La falta de índice en la
er101_upd_date_iso
columna es lo más importante, como ya lo mencionó Oded .Sin un índice coincidente (cuya falta podría provocar un escaneo de la tabla), no hay posibilidad de ejecutar consultas rápidas en tablas grandes.
Si no puede agregar índices (por varias razones, entre ellas, no tiene sentido crear un índice solo para una consulta ad-hoc ), sugeriría algunas soluciones (que se pueden usar para consultas ad-hoc):
1. Utilice tablas temporales
Cree una tabla temporal en un subconjunto (filas y columnas) de datos que le interesen. La tabla temporal debe ser mucho más pequeña que la tabla de origen original, puede indexarse fácilmente (si es necesario) y puede almacenar en caché el subconjunto de datos que le interesan.
Para crear una tabla temporal, puede usar código (no probado) como:
-- copy records from last month to temporary table INSERT INTO #my_temporary_table SELECT * FROM er101_acct_order_dtl WITH (NOLOCK) WHERE er101_upd_date_iso > DATEADD(month, -1, GETDATE()) -- you can add any index you need on temp table CREATE INDEX idx_er101_upd_date_iso ON #my_temporary_table(er101_upd_date_iso) -- run other queries on temporary table (which can be indexed) SELECT TOP 100 * FROM #my_temporary_table ORDER BY er101_upd_date_iso DESC
Pros:
view
.Contras:
2. Expresión de tabla común: CTE
Personalmente, uso mucho CTE con consultas ad-hoc; me ayuda mucho a construir (y probar) una consulta pieza por pieza.
Vea el ejemplo a continuación (la consulta que comienza con
WITH
).Pros:
Contras:
3. Crea vistas
Similar a lo anterior, pero cree vistas en lugar de tablas temporales (si juega a menudo con las mismas consultas y tiene una versión de MS SQL que admite vistas indexadas.
Puede crear vistas o vistas indexadas en un subconjunto de datos que le interesan y ejecutar consultas en la vista, que deben contener solo un subconjunto interesante de datos mucho más pequeño que la tabla completa.
Pros:
Contras:
Seleccionar todas las columnas
Ejecutar star query (
SELECT * FROM
) en una tabla grande no es bueno ...Si tiene columnas grandes (como cadenas largas), se necesita mucho tiempo para leerlas del disco y pasarlas por la red.
Intentaría reemplazar
*
con los nombres de columna que realmente necesita.O, si necesita todas las columnas, intente reescribir la consulta en algo como (usando una expresión de datos común ):
;WITH recs AS ( SELECT TOP 100 id as rec_id -- select primary key only FROM er101_acct_order_dtl ORDER BY er101_upd_date_iso DESC ) SELECT er101_acct_order_dtl.* FROM recs JOIN er101_acct_order_dtl ON er101_acct_order_dtl.id = recs.rec_id ORDER BY er101_upd_date_iso DESC
Lecturas sucias
Lo último que podría acelerar la consulta ad-hoc es permitir lecturas sucias con sugerencia de tabla
WITH (NOLOCK)
.En lugar de la sugerencia, puede establecer el nivel de aislamiento de la transacción para leer sin compromiso:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
o establezca la configuración adecuada de SQL Management Studio.
Supongo que para consultas ad-hoc, las lecturas sucias son lo suficientemente buenas.
fuente
SELECT *
: obliga a SQL Server a utilizar el índice agrupado. Al menos debería. No veo ninguna razón real para un índice de cobertura no agrupado ... que cubra toda la tabla :)Está obteniendo un escaneo de tabla allí, lo que significa que no tiene un índice definido en
er101_upd_date_iso
, o si esa columna es parte de un índice existente, el índice no se puede usar (posiblemente no sea la columna del indexador principal).Agregar índices faltantes ayudará al rendimiento sin fin.
Eso no significa que se utilicen en esta consulta (y probablemente no lo sean).
Sugiero leer Finding the Causes of Poor Performance in SQL Server por Gail Shaw, parte 1 y parte 2 .
fuente
er101_upd_date_iso
es un varchar enorme, o un int, el rendimiento cambiará significativamente.La pregunta establece específicamente que se debe mejorar el rendimiento para las consultas ad-hoc y que no se pueden agregar índices. Entonces, tomando eso al pie de la letra, ¿qué se puede hacer para mejorar el rendimiento en cualquier mesa?
Dado que estamos considerando consultas ad-hoc, la cláusula WHERE y la cláusula ORDER BY pueden contener cualquier combinación de columnas. Esto significa que casi independientemente de los índices que se coloquen en la tabla, habrá algunas consultas que requerirán un escaneo de la tabla, como se ve arriba en el plan de consulta de una consulta de bajo rendimiento.
Teniendo esto en cuenta, supongamos que no hay ningún índice en la tabla, aparte de un índice agrupado en la clave principal. Ahora consideremos qué opciones tenemos para maximizar el rendimiento.
Desfragmentar la mesa
Siempre que tengamos un índice agrupado, podemos desfragmentar la tabla usando DBCC INDEXDEFRAG (obsoleto) o preferiblemente ALTER INDEX . Esto minimizará el número de lecturas de disco necesarias para escanear la tabla y mejorará la velocidad.
Utilice los discos más rápidos posibles. No dice qué discos está usando, pero si puede usar SSD.
Optimice tempdb. Coloque tempdb en los discos más rápidos posibles, nuevamente SSD. Vea este artículo de SO y este artículo de RedGate .
Como se indica en otras respuestas, el uso de una consulta más selectiva devolverá menos datos y, por lo tanto, debería ser más rápido.
Ahora consideremos lo que podemos hacer si se nos permite agregar índices.
Si nosotros no estábamos hablando de consultas ad-hoc, a continuación, nos gustaría añadir índices específicamente para el conjunto limitado de consultas entre en funcionamiento con la tabla. Dado que estamos discutiendo consultas ad-hoc , ¿qué se puede hacer para mejorar la velocidad la mayor parte del tiempo?
Editar
Ejecuté algunas pruebas en una tabla "grande" de 22 millones de filas. Mi tabla solo tiene seis columnas, pero contiene 4 GB de datos. Mi máquina es una computadora de escritorio respetable con 8 Gb de RAM y una CPU de cuatro núcleos y tiene un solo SSD Agility 3.
Eliminé todos los índices, excepto la clave principal en la columna Id.
Una consulta similar al problema dado en la pregunta toma 5 segundos si el servidor SQL se reinicia primero y 3 segundos después. El asesor de ajuste de la base de datos obviamente recomienda agregar un índice para mejorar esta consulta, con una mejora estimada de> 99%. Agregar un índice da como resultado un tiempo de consulta efectivamente cero.
Lo que también es interesante es que mi plan de consulta es idéntico al suyo (con el escaneo de índice agrupado), pero el escaneo de índice representa el 9% del costo de la consulta y la clasificación el 91% restante. Solo puedo asumir que su tabla contiene una enorme cantidad de datos y / o sus discos son muy lentos o están ubicados en una conexión de red muy lenta.
fuente
Incluso si tiene índices en algunas columnas que se utilizan en algunas consultas, el hecho de que su consulta 'ad-hoc' provoque un escaneo de tabla muestra que no tiene índices suficientes para permitir que esta consulta se complete de manera eficiente.
Para los rangos de fechas en particular, es difícil agregar buenos índices.
Con solo mirar su consulta, la base de datos tiene que ordenar todos los registros por la columna seleccionada para poder devolver los primeros n registros.
¿La base de datos también realiza un escaneo completo de la tabla sin la cláusula order by? ¿Tiene la tabla una clave principal? Sin una PK, la base de datos tendrá que trabajar más para realizar la clasificación.
fuente
select top 100 * from ER101_ACCT_ORDER_DTL
Un índice es un árbol B donde cada nodo hoja apunta a un 'grupo de filas' (llamado 'Página' en la terminología interna de SQL). Es decir, cuando el índice es un índice no agrupado.
El índice agrupado es un caso especial, en el que los nodos hoja tienen el 'grupo de filas' (en lugar de apuntar a ellos). es por eso que...
1) Solo puede haber un índice agrupado en la tabla.
esto también significa que toda la tabla se almacena como el índice agrupado, por eso comenzó a ver un escaneo de índice en lugar de un escaneo de tabla.
2) Una operación que utiliza un índice agrupado es generalmente más rápida que un índice no agrupado
Leer más en http://msdn.microsoft.com/en-us/library/ms177443.aspx
Para el problema que tiene, realmente debería considerar agregar esta columna a un índice, como dijo, agregar un nuevo índice (o una columna a un índice existente) aumenta los costos de INSERTAR / ACTUALIZAR. Pero podría ser posible eliminar algún índice infrautilizado (o una columna de un índice existente) para reemplazarlo con 'er101_upd_date_iso'.
Si los cambios de índice no son posibles, recomiendo agregar una estadística en la columna, puede arreglar las cosas cuando las columnas tienen alguna correlación con las columnas indexadas
http://msdn.microsoft.com/en-us/library/ms188038.aspx
Por cierto, obtendrá mucha más ayuda si puede publicar el esquema de tabla de ER101_ACCT_ORDER_DTL. y los índices existentes también ..., probablemente la consulta podría reescribirse para usar algunos de ellos.
fuente
Una de las razones por las que su prueba de 1M se ejecutó más rápido es probablemente porque las tablas temporales están completamente en la memoria y solo irían al disco si su servidor experimenta presión de memoria. Puede volver a diseñar su consulta para eliminar el orden, agregar un buen índice agrupado e índices de cobertura como se mencionó anteriormente, o consultar al DMV para verificar la presión de IO para ver si está relacionado con el hardware.
-- From Glen Barry -- Clear Wait Stats (consider clearing and running wait stats query again after a few minutes) -- DBCC SQLPERF('sys.dm_os_wait_stats', CLEAR); -- Check Task Counts to get an initial idea what the problem might be -- Avg Current Tasks Count, Avg Runnable Tasks Count, Avg Pending Disk IO Count across all schedulers -- Run several times in quick succession SELECT AVG(current_tasks_count) AS [Avg Task Count], AVG(runnable_tasks_count) AS [Avg Runnable Task Count], AVG(pending_disk_io_count) AS [Avg Pending DiskIO Count] FROM sys.dm_os_schedulers WITH (NOLOCK) WHERE scheduler_id < 255 OPTION (RECOMPILE); -- Sustained values above 10 suggest further investigation in that area -- High current_tasks_count is often an indication of locking/blocking problems -- High runnable_tasks_count is a good indication of CPU pressure -- High pending_disk_io_count is an indication of I/O pressure
fuente
Sé que dijiste que agregar índices no es una opción, pero esa sería la única opción para eliminar el escaneo de tablas que tienes. Cuando realiza un escaneo, SQL Server lee los 2 millones de filas de la tabla para completar su consulta.
este artículo proporciona más información, pero recuerde: Buscar = bueno, Escanear = malo.
En segundo lugar, ¿no puede eliminar la selección * y seleccionar solo las columnas que necesita? En tercer lugar, ¿ninguna cláusula "dónde"? Incluso si tiene un índice, dado que está leyendo todo, lo mejor que obtendrá es un escaneo de índice (que es mejor que un escaneo de tabla, pero no es una búsqueda, que es lo que debe buscar)
fuente
Sé que ha pasado bastante tiempo desde el principio ... Hay mucha sabiduría en todas estas respuestas. Una buena indexación es lo primero cuando se intenta mejorar una consulta. Bueno, casi el primero. Lo más importante (por así decirlo) es realizar cambios en el código para que sea eficiente. Entonces, después de todo lo dicho y hecho, si uno tiene una consulta sin WHERE, o cuando la condición WHERE no es lo suficientemente selectiva, solo hay una forma de obtener los datos: TABLE SCAN (INDEX SCAN). Si se necesitan todas las columnas de una tabla, entonces se utilizará TABLE SCAN, sin duda alguna. Esto puede ser un análisis de pila o un análisis de índice agrupado, según el tipo de organización de datos. La única forma de acelerar las cosas (si es posible) es asegurarse de que se utilicen tantos núcleos como sea posible para realizar el escaneo: OPCIÓN (MAXDOP 0). Estoy ignorando el tema del almacenamiento, por supuesto,
fuente