Melhore o desempenho de consulta do SQL Server em tabelas grandes

85

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:

insira a descrição da imagem aqui

Detalhes adicionais da verificação da tabela:

insira a descrição da imagem aqui

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 DESCem 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?

insira a descrição da imagem aqui

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:

insira a descrição da imagem aqui

SQL Server Execution Times:
  CPU time = 3634 ms,  elapsed time = 154179 ms.

Agrupado:

insira a descrição da imagem aqui

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.

Lee Tickett
fonte
1
Há pequenas dicas quando você passa o cursor do mouse sobre o item de plano. Eles mostram o custo estimado de E / S e CPU. Eu cuidaria do custo de E / S no início.
Grzegorz Gierlik,
4
Table Scanindica 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 clusterizado er101_upd_date_isoajudaria (e não causaria outras desvantagens de desempenho)
marc_s
1
@marc_s obrigado por isso- eu mudei o índice pk para clusterizado e ele fez uma diferença substancial- você pode explicar melhor? (veja a atualização 2)
Lee Tickett,
2
Bem, o índice clusterizado apenas altera o layout de armazenamento da tabela. O índice clusterizado contém os dados reais da tabela em seus nós de nível folha - ou seja: para ler a tabela inteira, o SQL Server agora está fazendo uma varredura de índice clusterizado (basicamente uma "varredura de tabela" sobre uma tabela com um índice clusterizado). Isso quase sempre será um pouco mais rápido do que fazer uma varredura de tabela em um heap (sem índice clusterizado). Se agora você adicionou um índice não clusterizado na er101_upd_date_isocoluna, provavelmente também pode se livrar da operação "Classificar" em seu plano de execução e acelerar ainda mais as coisas
marc_s
2
@LeeTickett, mostre suas definições de Tabela e Índice. Há muitos fatores a serem considerados e ninguém parece estar pedindo por eles (o que me surpreende, mas talvez não devesse). Posso dizer que 2 milhões de linhas NÃO são grandes e tabelas indexadas corretamente com mais de 200 milhões de linhas retornam mais rápido do que isso. Provavelmente o índice clusterizado (agora que você tem um, graças a marc_s) é uma escolha ruim, mas difícil de dizer sem ver os detalhes. NÃO use particionamento, mas use SET STATISTICS IO ON e marque Logical Reads na guia de mensagens. Se uma mudança reduz as leituras lógicas, você está chegando perto.
Solomon Rutzky,

Respostas:

59

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:

  1. Primeiro, essa mesa realmente precisa ser remodelada. Se esta for uma tabela de data warehouse, então talvez, mas se não, esses campos realmente precisam ser divididos em várias tabelas que podem ter o mesmo PK. Você teria uma tabela de registro mestre e as tabelas filho são apenas informações dependentes com base em atributos comumente associados e o PK dessas tabelas é o mesmo que o PK da tabela mestre e, portanto, também FK para a tabela mestre. Haverá uma relação de 1 para 1 entre as tabelas mestre e todas as tabelas filho.
  2. O uso de 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 faria ANSI_PADDING ON, ou pelo menos teria a mesma configuração em todas as ALTER TABLEinstruções.
  3. Considere a criação de 2 grupos de arquivos adicionais: tabelas e índices. É melhor não colocar suas coisas, PRIMARYpois é 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]
  4. Aumente o fator de preenchimento de 50%. Esse número baixo provavelmente é o motivo pelo qual seu espaço de índice é maior do que seu espaço de dados. Fazer uma reconstrução do índice recriará as páginas de dados com um máximo de 4k (do tamanho total da página 8k) usado para seus dados, de forma que sua tabela seja espalhada por uma área ampla.
  5. Se a maioria ou todas as consultas tiverem "ER101_ORG_CODE" na WHEREcondiçã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.
  6. Ponto menor, mas se "ER101_ORG_CODE" tiver sempre 2 caracteres, use em 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.
  7. Como outros aqui mencionaram, o uso 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 na SELECTlista, 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:

  • Ative a compactação no índice agrupado. Essa opção tornou-se disponível no SQL Server 2008, mas como um recurso exclusivo da Enterprise Edition. No entanto, a partir do SQL Server 2016 SP1 , a compactação de dados foi disponibilizada em todas as edições ! Consulte a página MSDN sobre compactação de dados para obter detalhes sobre compactação de linha e página.
  • Se você não pode usar compressão de dados, ou se ele não fornecer muito benefício para uma tabela específica, em seguida, se você tiver uma coluna de um tipo de comprimento fixo ( INT, BIGINT, TINYINT, SMALLINT, CHAR, NCHAR, BINARY, DATETIME, SMALLDATETIME, MONEY, etc) e bem mais de 50 % das linhas são NULL, então considere habilitar a SPARSEopção que se tornou disponível no SQL Server 2008. Consulte a página MSDN sobre Usar colunas esparsas para obter detalhes.
Solomon Rutzky
fonte
No ponto 7, pessoalmente, imagino que deveria ser mais rápido adicionar 238 nomes de coluna de metadados do que analisá-los fora do texto da consulta e, em seguida, ter que verificar os metadados de qualquer maneira para garantir que todos existam. Existem argumentos fortes o suficiente contra, *sem aquele duvidoso
Martin Smith de
53

Existem alguns problemas com esta consulta (e isso se aplica a todas as consultas).

Falta de índice

A falta de índice na er101_upd_date_isocoluna é 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:

  • Fácil de fazer para qualquer subconjunto de dados.
  • Fácil de gerenciar - é temporário e está à mesa .
  • Não afeta o desempenho geral do sistema como view.
  • A tabela temporária pode ser indexada.
  • Você não precisa se preocupar com isso - é temporário :).

Contras:

  • É um instantâneo de dados - mas provavelmente é bom o suficiente para a maioria das consultas ad-hoc.

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:

  • Fácil de construir a partir de uma visão ampla e depois selecionar e filtrar o que realmente você precisa.
  • Fácil de testar.

Contras:

  • Algumas pessoas não gostam de CDE - as consultas CDE parecem ser longas e difíceis de entender.

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:

  • Fácil de fazer.
  • Está atualizado com os dados de origem.

Contras:

  • Possível apenas para um subconjunto de dados definido.
  • Pode ser ineficiente para tabelas grandes com alta taxa de atualizações.
  • Não é tão fácil de gerenciar.
  • Pode afetar o desempenho geral do sistema.
  • Não tenho certeza se as visualizações indexadas estão disponíveis em todas as versões do MS SQL.

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 tabelaWITH (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.

Grzegorz Gierlik
fonte
2
+1 para 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 :)
ta.speot.is
4
Esta resposta aborda apenas a melhoria da velocidade da consulta de exemplo, NÃO a pergunta, que é "é possível melhorar o desempenho para consultas ad-hoc"
Fil
CDE agora é CTE (Common Table Expression)
sqluser
12

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.

já existem índices nas colunas que são consultados mais comumente

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 .

Oded
fonte
meu ponto é que esta não é uma das colunas mais consultadas :)
Lee Tickett
1
@LeeTickett - E ainda, esta é a única coluna na qual você pode adicionar um índice para melhorar o desempenho desta consulta.
Oded
2
Não existem pesquisas otimizadas não indexadas. Você está usando um índice ou uma verificação completa da tabela. Se você não quiser varreduras completas de tabela, precisará de índices. Dependendo do seu perfil de uso, pode ser barato o suficiente para apenas adicionar índices. Ah, e o tipo de dados da coluna também é importante. Se seu er101_upd_date_isofor um varchar enorme, ou um int, mudará o desempenho significativamente.
Cilíndrico de
obrigado. Eu adicionei um comentário à pergunta. Eu entendo que é improvável que eu possa otimizar a consulta, mas imagino que pode haver maneiras de melhorar o desempenho de consultas ad hoc
Lee Tickett
Eu sou um aprendiz, como sabemos qual coluna precisa de indexação?
Vírus de
7

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?

  • Adicione um único índice de coluna a cada coluna. Isso deve dar ao SQL Server pelo menos algo com que trabalhar para melhorar a velocidade da maioria das consultas, mas não será o ideal.
  • Adicione índices específicos para as consultas mais comuns para que sejam otimizados.
  • Adicione outros índices específicos, conforme necessário, monitorando as consultas de baixo desempenho.

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.

Phil
fonte
2

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?

espumadino
fonte
Existe uma chave primária na mesa. Uma varredura de tabela também aparece no plano de execução ao simplesmente executarselect top 100 * from ER101_ACCT_ORDER_DTL
Lee Tickett,
2

Como isso é possível? Sem um índice na coluna er101_upd_date_iso, como uma varredura de índice clusterizado pode ser usada?

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.

shankar_pratap
fonte
1 para a resposta. Um comentário, porém, os índices clusterizados nem sempre são mais rápidos do que se pode ler (é possível interpretar mal) em sua resposta.
Gisli
Eu acho que entendo a diferença entre o índice clusterizado / não clusterizado, mas ainda não vejo como consultar uma coluna que não faz parte do índice clusterizado pode ser melhorada por ter um índice clusterizado em outras colunas?
Lee Tickett,
No SQL Server, o índice clusterizado contém todas as colunas. O índice clusterizado é o que decide como os dados são armazenados no disco. É um pouco difícil para mim explicar, mas se você pensar nos índices como uma árvore, o índice não clusterizado é uma árvore e as folhas de baixo contêm as informações que você definiu para estar no índice. Para um índice agrupado, as folhas inferiores contêm todas as colunas da tabela. Isso ocorre por design no SQL Server.
Gisli
Entendi. Mas eu pensei que os ramos eram baseados nas colunas do índice agrupado. Portanto, se a coluna que estou consultando não estiver no índice clusterizado, certamente todas as ramificações / folhas precisam ser verificadas?
Lee Tickett
1
Eu não entendo isso. Meu melhor palpite é que, quando você tinha um índice não clusterizado, ele era varrido, resultando em uma grande quantidade de E / S aleatória. Quando você criou o índice clusterizado, você se livrou dessas E / Ss aleatórias? Mas isso é um palpite, não consigo encontrar nenhum outro motivo para esse comportamento, mas não sou especialista.
Gisli
1

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
Ninghad
fonte
Espero que todo o meu banco de dados esteja na memória. existe uma maneira de verificar isso ou de dizer ao sql quais tabelas armazenar na memória? Eu estive ausente por alguns dias, mas quando eu voltar, tentarei sua consulta
Lee Tickett
A contagem média de tarefas e a contagem média pendente de DiskIO atingiram o pico 4. Ainda estou curioso em tentar forçar o banco de dados na memória ram.
Lee Tickett
0

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)

Diego
fonte
Não é verdade que Seek é sempre melhor do que Scan. Às vezes, uma varredura é realmente mais eficiente. Se este não fosse o caso, M $ não teria incluído uma dica de consulta FORCESCAN começando no SQL Server 2008 R2. Veja aqui para mais detalhes: msdn.microsoft.com/en-us/library/ms181714(v=sql.105).aspx e até mesmo aqui para quem quer forçar uma varredura (3ª resposta de Adam Haines tem boas informações): social .msdn.microsoft.com / Forums / en-US / transactsql / thread /…
Solomon Rutzky,
1
Bem, em primeiro lugar, as buscas são boas para consultas pontuais. Em segundo lugar, as varreduras são boas para consultas de intervalo em que muitos dados devem ser recuperados. Os sistemas OLAP não funcionariam bem sem varreduras. Os sistemas OLTP não funcionariam bem sem buscas. Tudo tem seu lugar no grande esquema das coisas ...
darlove
0

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,

querida
fonte