Tenho uma tabela relativamente grande (atualmente 2 milhões de registros) e gostaria de saber se é possível melhorar o desempenho para consultas ad-hoc. A palavra ad-hoc é a chave aqui. Adicionar índices não é uma opção (já existem índices nas colunas que são consultados com mais freqüência).
Executar uma consulta simples para retornar os 100 registros atualizados mais recentemente:
select top 100 * from ER101_ACCT_ORDER_DTL order by er101_upd_date_iso desc
Demora vários minutos. Veja o plano de execução abaixo:
Detalhes adicionais da verificação da tabela:
SQL Server Execution Times:
CPU time = 3945 ms, elapsed time = 148524 ms.
O servidor é muito poderoso (de memória RAM de 48 GB, processador de 24 núcleos) rodando sql server 2008 r2 x64.
Atualizar
Eu encontrei este código para criar uma tabela com 1.000.000 de registros. Pensei que poderia rodar SELECT TOP 100 * FROM testEnvironment ORDER BY mailAddress DESC
em alguns servidores diferentes para descobrir se a velocidade de acesso ao meu disco era baixa no 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;
Mas nos três servidores de teste a consulta foi executada quase que instantaneamente. Alguém pode explicar isso?
Atualização 2
Obrigado pelos comentários - por favor, continuem vindo ... eles me levaram a tentar alterar o índice de chave primária de não agrupado para agrupado com resultados bastante interessantes (e inesperados?).
Não 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.
Como isso é possível? Sem um índice na coluna er101_upd_date_iso, como uma varredura de índice clusterizado pode ser usada?
Atualização 3
Conforme solicitado, este é o script de criação de tabela:
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]
O tamanho da tabela é de 2,8 GB com o tamanho do índice de 3,9 GB.
fonte
Table Scan
indica um heap (sem índice clusterizado) - portanto, a primeira etapa seria adicionar um índice clusterizado bom e rápido à sua tabela. A segunda etapa pode ser investigar se um índice não clusterizadoer101_upd_date_iso
ajudaria (e não causaria outras desvantagens de desempenho)er101_upd_date_iso
coluna, provavelmente também pode se livrar da operação "Classificar" em seu plano de execução e acelerar ainda mais as coisasRespostas:
Resposta simples: NÃO. Você não pode ajudar em consultas ad hoc em uma tabela de 238 colunas com um fator de preenchimento de 50% no índice agrupado.
Resposta detalhada:
Como afirmei em outras respostas sobre este tópico, o design do índice é arte e ciência e há tantos fatores a serem considerados que existem poucas, se houver, regras rígidas e rápidas. Você precisa considerar: o volume de operações DML vs SELECTs, subsistema de disco, outros índices / gatilhos na tabela, distribuição de dados dentro da tabela, são consultas usando condições SARGable WHERE e várias outras coisas que eu nem consigo lembrar direito agora.
Posso dizer que nenhuma ajuda pode ser dada para perguntas sobre este tópico sem uma compreensão da própria Tabela, seus índices, gatilhos, etc. Agora que você postou a definição da tabela (ainda aguardando os Índices, mas a definição da Tabela sozinha aponta para 99% do problema) Posso oferecer algumas sugestões.
Primeiro, se a definição da tabela for precisa (238 colunas, fator de preenchimento de 50%), você pode ignorar o resto das respostas / conselhos aqui ;-). Desculpe ser menos do que político aqui, mas sério, é uma caça ao ganso selvagem sem saber os detalhes. E agora que vemos a definição da tabela, fica um pouco mais claro por que uma consulta simples demoraria tanto, mesmo quando as consultas de teste (atualização # 1) eram executadas tão rapidamente.
O principal problema aqui (e em muitas situações de baixo desempenho) é a modelagem de dados ruim. 238 colunas não é proibido, assim como ter 999 índices não é proibido, mas também geralmente não é muito sábio.
Recomendações:
ANSI_PADDING OFF
é perturbador, sem falar que é inconsistente na tabela devido às várias adições de colunas ao longo do tempo. Não tenho certeza se você pode consertar isso agora, mas idealmente você sempre fariaANSI_PADDING ON
, ou pelo menos teria a mesma configuração em todas asALTER TABLE
instruções.PRIMARY
pois é onde o SQL SERVER armazena todos os seus dados e metadados sobre seus objetos. Você cria sua Tabela e Índice de Cluster (já que são os dados da tabela) em[Tables]
e todos os índices Não Clusterizados em[Indexes]
WHERE
condição, considere mover isso para a coluna inicial do índice agrupado. Supondo que seja usado com mais frequência do que "ER101_ORD_NBR". Se "ER101_ORD_NBR" for usado com mais frequência, mantenha-o. Parece apenas, assumindo que os nomes dos campos significam "Código da Organização" e "Número do Pedido", que "Código da Organização" é um agrupamento melhor que pode ter vários "Números do Pedido" dentro dele.CHAR(2)
vez deVARCHAR(2)
pois isso salvará um byte no cabeçalho da linha que rastreia tamanhos de largura variável e soma milhões de linhas.SELECT *
prejudicará o desempenho. Não apenas porque exige que o SQL Server retorne todas as colunas e, portanto, seja mais provável que faça uma varredura de índice agrupado, independentemente de seus outros índices, mas também leva tempo do SQL Server para ir para a definição da tabela e traduzir*
em todos os nomes de coluna . Deve ser um pouco mais rápido especificar todos os 238 nomes de coluna naSELECT
lista, embora isso não ajude no problema de verificação. Mas você realmente precisa de todas as 238 colunas ao mesmo tempo?Boa sorte!
ATUALIZAÇÃO
Para fins de completude da pergunta "como melhorar o desempenho em uma grande mesa para consultas ad-hoc", deve-se observar que embora não ajude neste caso específico, SE alguém estiver usando o SQL Server 2012 (ou mais recente quando esse momento chegar) e SE a tabela não estiver sendo atualizada, o uso de índices Columnstore é uma opção. Para obter mais detalhes sobre esse novo recurso, veja aqui: http://msdn.microsoft.com/en-us/library/gg492088.aspx (acredito que eles foram feitos para serem atualizáveis a partir do SQL Server 2014).
ATUALIZAÇÃO 2
As considerações adicionais são:
INT
,BIGINT
,TINYINT
,SMALLINT
,CHAR
,NCHAR
,BINARY
,DATETIME
,SMALLDATETIME
,MONEY
, etc) e bem mais de 50 % das linhas sãoNULL
, então considere habilitar aSPARSE
opção que se tornou disponível no SQL Server 2008. Consulte a página MSDN sobre Usar colunas esparsas para obter detalhes.fonte
*
sem aquele duvidosoExistem alguns problemas com esta consulta (e isso se aplica a todas as consultas).
Falta de índice
A falta de índice na
er101_upd_date_iso
coluna é a coisa mais importante, como Oded já mencionou.Sem índice correspondente (cuja falta pode causar a varredura da tabela), não há chance de executar consultas rápidas em tabelas grandes.
Se você não pode adicionar índices (por vários motivos, incluindo não há nenhum ponto em criar índice para apenas uma consulta ad-hoc ), eu sugeriria algumas soluções alternativas (que podem ser usadas para consultas ad-hoc):
1. Use tabelas temporárias
Crie uma tabela temporária no subconjunto (linhas e colunas) de dados de seu interesse. A tabela temporária deve ser muito menor que a tabela de origem original, pode ser indexada facilmente (se necessário) e pode armazenar em cache o subconjunto de dados nos quais você está interessado.
Para criar uma tabela temporária, você pode usar um código (não testado) 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
Prós:
view
.Contras:
2. Expressão de tabela comum - CTE
Pessoalmente, eu uso muito o CTE com consultas ad-hoc - ele ajuda muito na construção (e teste) de uma consulta peça por peça.
Veja o exemplo abaixo (a consulta começando com
WITH
).Prós:
Contras:
3. Crie visualizações
Semelhante ao anterior, mas cria visualizações em vez de tabelas temporárias (se você joga frequentemente com as mesmas consultas e tem a versão MS SQL que suporta visualizações indexadas.
Você pode criar visualizações ou visualizações indexadas no subconjunto de dados de seu interesse e executar consultas na visualização - que deve conter apenas subconjuntos interessantes de dados muito menores do que a tabela inteira.
Prós:
Contras:
Selecionando todas as colunas
Executar a consulta estrela (
SELECT * FROM
) em uma mesa grande não é bom ...Se você tiver colunas grandes (como strings longas), leva muito tempo para lê-las do disco e passar pela rede.
Eu tentaria substituir os
*
nomes das colunas de que você realmente precisa.Ou, se precisar de todas as colunas, tente reescrever a consulta para algo como (usando uma expressão de dados comum ):
;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
Leituras sujas
A última coisa que poderia acelerar a consulta ad-hoc é permitir leituras sujas com dica de tabela
WITH (NOLOCK)
.Em vez de dica, você pode definir o nível de isolamento da transação para ler não confirmado:
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
ou definir a configuração adequada do SQL Management Studio.
Suponho que, para consultas ad-hoc, leituras sujas são boas o suficiente.
fonte
SELECT *
- força o SQL Server a usar o índice clusterizado. Pelo menos, deveria. Não vejo nenhum motivo real para um índice de cobertura não agrupado ... cobrindo toda a tabela :)Você está recebendo uma varredura de tabela lá, o que significa que você não tem um índice definido
er101_upd_date_iso
, ou se essa coluna faz parte de um índice existente, o índice não pode ser usado (possivelmente não é a coluna do indexador principal).Adicionar índices ausentes ajudará no desempenho sem fim.
Isso não significa que sejam usados nesta consulta (e provavelmente não são).
Sugiro ler Finding the Causes of Poor Performance in SQL Server de Gail Shaw, parte 1 e parte 2 .
fonte
er101_upd_date_iso
for um varchar enorme, ou um int, mudará o desempenho significativamente.A pergunta afirma especificamente que o desempenho precisa ser melhorado para consultas ad-hoc e que os índices não podem ser adicionados. Então, levando isso em consideração, o que pode ser feito para melhorar o desempenho em qualquer mesa?
Como estamos considerando consultas ad-hoc, a cláusula WHERE e a cláusula ORDER BY podem conter qualquer combinação de colunas. Isso significa que, quase independentemente de quais índices são colocados na tabela, haverá algumas consultas que requerem uma varredura da tabela, como visto acima no plano de consulta de uma consulta de baixo desempenho.
Levando isso em consideração, vamos supor que não haja nenhum índice na tabela além de um índice clusterizado na chave primária. Agora, vamos considerar quais opções temos para maximizar o desempenho.
Desfragmente a mesa
Contanto que tenhamos um índice clusterizado, podemos desfragmentar a tabela usando DBCC INDEXDEFRAG (obsoleto) ou preferencialmente ALTER INDEX . Isso minimizará o número de leituras de disco necessárias para verificar a tabela e aumentará a velocidade.
Use os discos mais rápidos possíveis. Você não diz quais discos está usando, mas se pode usar SSDs.
Otimize o tempdb. Coloque o tempdb nos discos mais rápidos possíveis, novamente SSDs. Veja este artigo SO e este artigo RedGate .
Conforme declarado em outras respostas, usar uma consulta mais seletiva retornará menos dados e, portanto, deve ser mais rápido.
Agora vamos considerar o que podemos fazer se tivermos permissão para adicionar índices.
Se nós não estavam falando de consultas ad-hoc, então poderíamos adicionar índices especificamente para o conjunto limitado de consultas a ser executado contra a mesa. Já que estamos discutindo consultas ad-hoc , o que pode ser feito para melhorar a velocidade na maioria das vezes?
Editar
Eu executei alguns testes em uma tabela 'grande' de 22 milhões de linhas. Minha tabela tem apenas seis colunas, mas contém 4 GB de dados. Minha máquina é um desktop respeitável com 8 Gb de RAM e uma CPU quad core e tem um único Agility 3 SSD.
Removi todos os índices, exceto a chave primária na coluna Id.
Uma consulta semelhante ao problema fornecido na pergunta leva 5 segundos se o servidor SQL for reiniciado primeiro e 3 segundos depois. O orientador de ajuste de banco de dados obviamente recomenda adicionar um índice para melhorar esta consulta, com uma melhoria estimada de> 99%. Adicionar um índice resulta em um tempo de consulta efetivamente zero.
O que também é interessante é que meu plano de consulta é idêntico ao seu (com a varredura de índice clusterizado), mas a varredura de índice é responsável por 9% do custo da consulta e a classificação pelos 91% restantes. Só posso assumir que sua tabela contém uma enorme quantidade de dados e / ou seus discos são muito lentos ou estão localizados em uma conexão de rede muito lenta.
fonte
Mesmo se você tiver índices em algumas colunas que são usados em algumas consultas, o fato de sua consulta 'ad-hoc' causar uma varredura na tabela mostra que você não tem índices suficientes para permitir que essa consulta seja concluída com eficiência.
Para intervalos de datas em particular, é difícil adicionar bons índices.
Apenas olhando para sua consulta, o banco de dados tem que ordenar todos os registros pela coluna selecionada para poder retornar os primeiros n registros.
O banco de dados também faz uma varredura completa da tabela sem a cláusula order by? A tabela tem uma chave primária - sem um PK, o banco de dados terá que trabalhar mais para realizar a classificação?
fonte
select top 100 * from ER101_ACCT_ORDER_DTL
Um índice é uma árvore B em que cada nó folha aponta para um 'grupo de linhas' (chamado de 'Página' na terminologia interna SQL), ou seja, quando o índice é um índice não agrupado.
O índice agrupado é um caso especial, no qual os nós folha possuem o 'grupo de linhas' (em vez de apontar para eles). é por isso que...
1) Só pode haver um índice clusterizado na tabela.
isso também significa que toda a tabela é armazenada como o índice clusterizado, por isso você começou a ver a varredura de índice em vez de uma varredura de tabela.
2) Uma operação que utiliza índice clusterizado é geralmente mais rápida do que um índice não clusterizado
Leia mais em http://msdn.microsoft.com/en-us/library/ms177443.aspx
Para o problema que você tem, você realmente deve considerar adicionar esta coluna a um índice, como você disse adicionar um novo índice (ou uma coluna a um índice existente) aumenta os custos de INSERT / UPDATE. Mas pode ser possível remover algum índice subutilizado (ou uma coluna de um índice existente) para substituir por 'er101_upd_date_iso'.
Se as mudanças de índice não forem possíveis, eu recomendo adicionar uma estatística na coluna, isso pode tornar as coisas mais rápidas quando as colunas têm alguma correlação com colunas indexadas
http://msdn.microsoft.com/en-us/library/ms188038.aspx
BTW, você obterá muito mais ajuda se puder postar o esquema da tabela de ER101_ACCT_ORDER_DTL. e os índices existentes também ..., provavelmente a consulta poderia ser reescrita para usar alguns deles.
fonte
Um dos motivos pelos quais seu teste de 1M foi executado mais rapidamente é provavelmente porque as tabelas temporárias estão inteiramente na memória e só iriam para o disco se o servidor tivesse pressão de memória. Você pode refazer sua consulta para remover a ordem, adicionar um bom índice clusterizado e índice (s) de cobertura conforme mencionado anteriormente ou consultar o DMV para verificar a pressão de E / S para ver se o hardware está relacionado.
-- 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
fonte
Eu sei que você disse que adicionar índices não é uma opção, mas que seria a única opção para eliminar a varredura de tabela que você tem. Quando você faz uma varredura, o SQL Server lê todas as 2 milhões de linhas da tabela para preencher sua consulta.
esta artigo fornece mais informações, mas lembre-se: Seek = good, Scan = bad.
Em segundo lugar, você não pode eliminar o select * e selecionar apenas as colunas de que precisa? Terceiro, nenhuma cláusula "onde"? Mesmo se você tiver um índice, já que está lendo tudo, o melhor que obterá é uma varredura de índice (que é melhor do que uma varredura de tabela, mas não é uma busca, que é o que você deve buscar)
fonte
Eu sei que já faz um bom tempo desde o início ... Há muita sabedoria em todas essas respostas. Uma boa indexação é a primeira coisa ao tentar melhorar uma consulta. Bem, quase o primeiro. O mais importante (por assim dizer) é fazer alterações no código para que seja eficiente. Então, depois de tudo dito e feito, se alguém tiver uma consulta sem WHERE, ou quando a condição WHERE não for seletiva o suficiente, só há uma maneira de obter os dados: TABLE SCAN (INDEX SCAN). Se for necessário todas as colunas de uma tabela, então TABLE SCAN será usado - sem dúvida. Isso pode ser uma varredura de heap ou varredura de índice clusterizado, dependendo do tipo de organização de dados. A única forma de acelerar as coisas (se possível) é garantir que o máximo possível de núcleos seja usado para fazer a varredura: OPTION (MAXDOP 0). Estou ignorando o assunto do armazenamento, é claro,
fonte