Migliora le prestazioni delle query di SQL Server su tabelle di grandi dimensioni


85

Ho una tabella relativamente grande (attualmente 2 milioni di record) e vorrei sapere se è possibile migliorare le prestazioni per le query ad-hoc. La parola ad-hoc è la chiave qui. L'aggiunta di indici non è un'opzione (ci sono già indici sulle colonne che vengono interrogati più comunemente).

Esecuzione di una semplice query per restituire i 100 record aggiornati più di recente:

select top 100 * from ER101_ACCT_ORDER_DTL order by er101_upd_date_iso desc

Richiede diversi minuti. Vedi piano di esecuzione di seguito:

inserisci qui la descrizione dell'immagine

Dettagli aggiuntivi dalla scansione della tabella:

inserisci qui la descrizione dell'immagine

SQL Server Execution Times:
  CPU time = 3945 ms,  elapsed time = 148524 ms.

Il server è piuttosto potente (dalla memoria 48 GB di RAM, 24 core del processore) che esegue sql server 2008 r2 x64.

Aggiornare

Ho trovato questo codice per creare una tabella con 1.000.000 di record. Ho pensato di poter quindi eseguire SELECT TOP 100 * FROM testEnvironment ORDER BY mailAddress DESCsu alcuni server diversi per scoprire se la mia velocità di accesso al disco era scarsa sul server.

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;

Ma sui tre server di prova la query è stata eseguita quasi istantaneamente. Qualcuno può spiegarlo?

inserisci qui la descrizione dell'immagine

Aggiorna 2

Grazie per i commenti, per favore continuate a inviarli ... mi hanno portato a provare a cambiare l'indice della chiave primaria da non cluster a cluster con risultati piuttosto interessanti (e inaspettati?).

Non in cluster:

inserisci qui la descrizione dell'immagine

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

Raggruppato:

inserisci qui la descrizione dell'immagine

SQL Server Execution Times:
  CPU time = 2650 ms,  elapsed time = 52177 ms.

Com'è possibile? Senza un indice sulla colonna er101_upd_date_iso come può essere utilizzata una scansione dell'indice cluster?

Aggiorna 3

Come richiesto, questo è lo script di creazione della tabella:

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 dimensione della tabella è di 2,8 GB con la dimensione dell'indice pari a 3,9 GB.


1
Ci sono piccoli suggerimenti quando passi il cursore del mouse sull'elemento del piano. Mostrano I / O stimati e il costo della CPU. All'inizio mi occuperei dei costi di I / O.
Grzegorz Gierlik

4
Table Scanindica un heap (nessun indice cluster), quindi il primo passaggio sarebbe aggiungere un indice cluster valido e veloce alla tabella. Il secondo passaggio potrebbe essere quello di indagare se un indice non cluster su er101_upd_date_isopotrebbe aiutare (e non causare altri svantaggi delle prestazioni)
marc_s

1
@marc_s grazie per questo- ho cambiato l'indice pk in cluster e ha fatto una differenza sostanziale- puoi spiegarlo ulteriormente? (guarda l'aggiornamento 2)
Lee Tickett

2
Ebbene, l'indice cluster cambia solo il layout di archiviazione della tabella. L'indice cluster contiene i dati effettivi della tabella nei nodi a livello foglia, ovvero: per leggere l'intera tabella, SQL Server esegue ora una scansione dell'indice cluster (fondamentalmente una "scansione della tabella" su una tabella con un indice cluster). Quasi sempre sarà un po 'più veloce che eseguire una scansione della tabella su un heap (senza indice cluster). Se ora hai aggiunto un indice non cluster alla er101_upd_date_isocolonna, probabilmente puoi anche sbarazzarti dell'operazione "Ordina" nel tuo piano di esecuzione e velocizzare ulteriormente le cose
marc_s

2
@LeeTickett, mostra le definizioni di tabella e indice. Ci sono molti fattori da considerare e nessuno sembra chiederli (il che mi sorprende ma forse non dovrebbe). Posso dirti che 2 milioni di righe NON sono grandi e tabelle correttamente indicizzate con oltre 200 milioni di righe restituiscono più velocemente di questo. Molto probabilmente l'indice cluster (ora che ne hai uno grazie a marc_s) è una scelta sbagliata ma difficile da dire senza vedere le specifiche. NON utilizzare il partizionamento ma utilizzare SET STATISTICS IO ON e selezionare Letture logiche nella scheda dei messaggi. Se una modifica riduce le letture logiche, ti stai avvicinando.
Solomon Rutzky

Risposte:


59

Risposta semplice: NO. Non è possibile evitare query ad hoc su una tabella a 238 colonne con un fattore di riempimento del 50% sull'indice cluster.

Risposta dettagliata:

Come ho affermato in altre risposte su questo argomento, il design dell'Indice è sia Arte che Scienza e ci sono così tanti fattori da considerare che ci sono poche, se non nessuna, regole rigide e veloci. È necessario considerare: il volume delle operazioni DML rispetto a SELECT, sottosistema del disco, altri indici / trigger sulla tabella, distribuzione dei dati all'interno della tabella, sono query che utilizzano le condizioni SARGable WHERE e molte altre cose che non riesco nemmeno a ricordare bene adesso.

Posso dire che non è possibile fornire alcun aiuto per domande su questo argomento senza una comprensione della tabella stessa, dei suoi indici, trigger, ecc. Ora che hai pubblicato la definizione della tabella (ancora in attesa sugli indici ma la definizione della tabella da sola punta a 99% del problema) Posso offrire alcuni suggerimenti.

Innanzitutto, se la definizione della tabella è accurata (238 colonne, fattore di riempimento del 50%), puoi praticamente ignorare il resto delle risposte / consigli qui ;-). Mi dispiace essere tutt'altro che politico qui, ma seriamente, è un inseguimento selvaggio senza conoscere i dettagli. E ora che vediamo la definizione della tabella diventa un po 'più chiaro il motivo per cui una semplice query richiederebbe così tanto tempo, anche quando le query di prova (aggiornamento n. 1) sono state eseguite così rapidamente.

Il problema principale qui (e in molte situazioni con prestazioni scadenti) è una cattiva modellazione dei dati. 238 colonne non è proibito così come non è proibito avere 999 indici, ma generalmente non è molto saggio.

Raccomandazioni:

  1. Innanzitutto, questo tavolo ha davvero bisogno di essere ristrutturato. Se questa è una tabella del data warehouse, allora forse, ma in caso contrario, questi campi devono essere suddivisi in più tabelle che possono avere tutte la stessa PK. Avresti una tabella dei record principali e le tabelle figlie sono solo informazioni dipendenti in base agli attributi comunemente associati e il PK di quelle tabelle è lo stesso del PK della tabella principale e quindi anche FK per la tabella principale. Ci sarà una relazione 1 a 1 tra le tabelle principali e tutte le tabelle figlie.
  2. L'utilizzo di ANSI_PADDING OFFè inquietante, per non parlare incoerente all'interno della tabella a causa delle varie aggiunte di colonne nel tempo. Non sono sicuro che tu possa risolverlo ora, ma idealmente avresti sempre ANSI_PADDING ON, o almeno avresti la stessa impostazione in tutte le ALTER TABLEdichiarazioni.
  3. Valuta la possibilità di creare 2 gruppi di file aggiuntivi: tabelle e indici. È meglio non inserire le tue cose PRIMARYperché è lì che SQL SERVER memorizza tutti i suoi dati e metadati sui tuoi oggetti. Crei la tabella e l'indice cluster (poiché sono i dati per la tabella) [Tables]e tutti gli indici non cluster su[Indexes]
  4. Aumenta il fattore di riempimento dal 50%. Questo numero basso è probabilmente il motivo per cui lo spazio dell'indice è più grande dello spazio dati. L'esecuzione di una ricostruzione dell'indice ricreerà le pagine di dati con un massimo di 4k (su un totale di 8.000 dimensioni della pagina) utilizzate per i dati in modo che la tabella sia distribuita su un'ampia area.
  5. Se la maggior parte o tutte le query contengono "ER101_ORG_CODE" nella WHEREcondizione, valuta la possibilità di spostarlo nella colonna iniziale dell'indice cluster. Supponendo che venga utilizzato più spesso di "ER101_ORD_NBR". Se "ER101_ORD_NBR" viene utilizzato più spesso, mantenerlo. Sembra solo, supponendo che i nomi dei campi significhino "OrganizationCode" e "OrderNumber", che "OrgCode" sia un raggruppamento migliore che potrebbe contenere più "OrderNumbers" al suo interno.
  6. Punto minore, ma se "ER101_ORG_CODE" è sempre di 2 caratteri, usa CHAR(2)invece di VARCHAR(2)come salverà un byte nell'intestazione di riga che tiene traccia delle dimensioni della larghezza variabile e si somma su milioni di righe.
  7. Come altri qui hanno già detto, l'uso SELECT *danneggerà le prestazioni. Non solo perché richiede a SQL Server di restituire tutte le colonne e quindi è più probabile che esegua una scansione dell'indice cluster indipendentemente dagli altri indici, ma richiede anche tempo a SQL Server per andare alla definizione della tabella e tradurre *in tutti i nomi delle colonne . Dovrebbe essere leggermente più veloce specificare tutti i nomi di 238 colonne SELECTnell'elenco, anche se ciò non aiuterà il problema di scansione. Ma hai mai davvero bisogno di tutte le 238 colonne contemporaneamente?

In bocca al lupo!

AGGIORNAMENTO
Per completezza alla domanda "come migliorare le prestazioni su una tabella di grandi dimensioni per query ad-hoc", va notato che sebbene non sia di aiuto per questo caso specifico, SE qualcuno sta utilizzando SQL Server 2012 (o più recente quando arriva quel momento) e SE la tabella non viene aggiornata, è possibile utilizzare gli indici Columnstore. Per maggiori dettagli su questa nuova funzionalità, guarda qui: http://msdn.microsoft.com/en-us/library/gg492088.aspx (credo che questi siano stati creati per essere aggiornabili a partire da SQL Server 2014).

UPDATE 2
Considerazioni aggiuntive sono:

  • Abilita la compressione sull'indice cluster. Questa opzione è diventata disponibile in SQL Server 2008, ma solo come funzionalità di Enterprise Edition. Tuttavia, a partire da SQL Server 2016 SP1 , la compressione dei dati è stata resa disponibile in tutte le edizioni ! Vedere la pagina MSDN per la compressione dei dati per i dettagli sulla compressione di righe e pagine.
  • Se non è possibile utilizzare la compressione dei dati, o se non sarà molto benefica per una particolare tabella, quindi se avete una colonna di un tipo di lunghezza fissa ( INT, BIGINT, TINYINT, SMALLINT, CHAR, NCHAR, BINARY, DATETIME, SMALLDATETIME, MONEY, ecc) e ben oltre il 50 % delle righe lo sono NULL, quindi valutare la SPARSEpossibilità di abilitare l' opzione che è diventata disponibile in SQL Server 2008. Per i dettagli, vedere la pagina MSDN relativa all'utilizzo di colonne sparse .

Sul punto 7 personalmente immagino che dovrebbe essere più veloce aggiungere 238 nomi di colonne dai metadati piuttosto che analizzarli dal testo della query e quindi dover controllare comunque i metadati per assicurarsi che esistano tutti.Ci sono argomenti abbastanza forti contro *senza quello dubbio
Martin Smith,

53

Ci sono alcuni problemi con questa query (e questo vale per ogni query).

Mancanza di indice

La mancanza di indice sulla er101_upd_date_isocolonna è la cosa più importante come ha già detto Oded .

Senza l'indice corrispondente (la cui mancanza potrebbe causare la scansione della tabella) non è possibile eseguire query veloci su tabelle di grandi dimensioni.

Se non è possibile aggiungere indici (per vari motivi, tra cui non ha senso creare un indice per una sola query ad hoc ), suggerirei alcune soluzioni alternative (che possono essere utilizzate per query ad-hoc):

1. Utilizzare tabelle temporanee

Crea una tabella temporanea su un sottoinsieme (righe e colonne) di dati a cui sei interessato. La tabella temporanea dovrebbe essere molto più piccola della tabella di origine originale, può essere indicizzata facilmente (se necessario) e può memorizzare nella cache un sottoinsieme di dati a cui sei interessato.

Per creare una tabella temporanea puoi usare codice (non testato) come:

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

Professionisti:

  • Facile da eseguire per qualsiasi sottoinsieme di dati.
  • Facile da gestire: è temporaneo ed è un tavolo .
  • Non influisce sulle prestazioni complessive del sistema come view.
  • La tabella temporanea può essere indicizzata.
  • Non devi preoccupartene - è temporaneo :).

Contro:

  • È un'istantanea dei dati, ma probabilmente è sufficiente per la maggior parte delle query ad hoc.

2. Espressione di tabella comune - CTE

Personalmente uso molto CTE con le query ad-hoc: aiuta molto a costruire (e testare) una query pezzo per pezzo.

Vedere l'esempio di seguito (la query che inizia con WITH).

Professionisti:

  • Facile da costruire partendo da una vista grande e quindi selezionando e filtrando ciò di cui hai veramente bisogno.
  • Facile da testare.

Contro:

  • Ad alcune persone non piacciono i CDE: le query CDE sembrano essere lunghe e difficili da capire.

3. Creare visualizzazioni

Simile a sopra, ma crea viste invece di tabelle temporanee (se giochi spesso con le stesse query e hai la versione MS SQL che supporta le viste indicizzate.

Puoi creare viste o viste indicizzate su un sottoinsieme di dati che ti interessa ed eseguire query sulla visualizzazione, che dovrebbe contenere solo un sottoinsieme interessante di dati molto più piccoli dell'intera tabella.

Professionisti:

  • Facile da fare.
  • È aggiornato con i dati di origine.

Contro:

  • Possibile solo per un sottoinsieme di dati definito.
  • Potrebbe essere inefficiente per tabelle di grandi dimensioni con un alto tasso di aggiornamenti.
  • Non così facile da gestire.
  • Può influire sulle prestazioni complessive del sistema.
  • Non sono sicuro che le viste indicizzate siano disponibili in ogni versione di MS SQL.

Selezione di tutte le colonne

L'esecuzione di star query ( SELECT * FROM) su un tavolo grande non è una buona cosa ...

Se hai colonne di grandi dimensioni (come stringhe lunghe) ci vuole molto tempo per leggerle dal disco e passare dalla rete.

Proverei a sostituire *con i nomi delle colonne di cui hai veramente bisogno.

Oppure, se hai bisogno di tutte le colonne, prova a riscrivere la query in qualcosa di simile (usando un'espressione di dati comune ):

;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 

Letture sporche

L'ultima cosa che potrebbe velocizzare la query ad-hoc è consentire letture sporche con suggerimenti di tabellaWITH (NOLOCK) .

Invece di un suggerimento, puoi impostare il livello di isolamento della transazione per leggere senza limite:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

o impostare la corretta impostazione di SQL Management Studio.

Presumo che per le query ad-hoc le letture sporche siano abbastanza buone.


2
+1 per SELECT *: forza SQL Server a utilizzare l'indice cluster. Almeno, dovrebbe. Non vedo alcun motivo reale per un indice di copertura non cluster ... che copre l'intera tabella :)
ta.speot.is

4
Questa risposta si limita a migliorare la velocità della query di esempio, NON la domanda, che è "è possibile migliorare le prestazioni per le query ad hoc"
Phil

CDE ora è CTE (Common Table Expression)
sqluser

12

Qui stai ricevendo una scansione della tabella , il che significa che non hai un indice definito er101_upd_date_isoo se quella colonna fa parte di un indice esistente, l'indice non può essere utilizzato (forse non è la colonna dell'indicizzatore principale).

L'aggiunta di indici mancanti aiuterà le prestazioni senza fine.

ci sono già indici sulle colonne che vengono interrogati più comunemente

Ciò non significa che vengano utilizzati in questa query (e probabilmente non lo sono).

Suggerisco di leggere Trovare le cause delle scarse prestazioni in SQL Server di Gail Shaw, parte 1 e parte 2 .


il punto è che questa non è una delle colonne interrogate più comunemente :)
Lee Tickett

1
@LeeTickett - Eppure, questa è l'unica colonna a cui puoi eventualmente aggiungere un indice per migliorare le prestazioni di questa query.
Oded

2
Nessuna cosa come le ricerche non indicizzate ottimizzate. Stai utilizzando un indice o una scansione completa della tabella. Se non vuoi scansioni complete della tabella, hai bisogno degli indici. A seconda del profilo di utilizzo, potrebbe essere abbastanza economico aggiungere solo gli indici. Oh, e anche il tipo di dati della colonna è importante. Se il tuo er101_upd_date_isoè un enorme varchar, o un int, cambierà significativamente le prestazioni.
Cilindrico

Grazie. ho aggiunto un commento alla domanda. Apprezzo che sia improbabile che io possa ottimizzare la query, ma immagino che ci possano essere modi in cui posso migliorare le prestazioni delle query ad hoc
Lee Tickett

Sono uno studente, come sappiamo che quale colonna deve essere indicizzata?
Virus

7

La domanda afferma specificamente che le prestazioni devono essere migliorate per le query ad-hoc e che gli indici non possono essere aggiunti. Quindi, prendendo questo per valore nominale, cosa si può fare per migliorare le prestazioni su qualsiasi tavolo?

Poiché stiamo prendendo in considerazione query ad-hoc, la clausola WHERE e la clausola ORDER BY possono contenere qualsiasi combinazione di colonne. Ciò significa che quasi indipendentemente dagli indici inseriti nella tabella, ci saranno alcune query che richiedono una scansione della tabella, come visto sopra nel piano di query di una query con prestazioni scadenti.

Tenendo conto di ciò, supponiamo che non ci siano affatto indici sulla tabella a parte un indice cluster sulla chiave primaria. Consideriamo ora quali opzioni abbiamo per massimizzare le prestazioni.

  • Deframmenta la tabella

    Finché abbiamo un indice cluster, possiamo deframmentare la tabella utilizzando DBCC INDEXDEFRAG (deprecato) o preferibilmente ALTER INDEX . Ciò ridurrà al minimo il numero di letture del disco necessarie per eseguire la scansione della tabella e migliorerà la velocità.

  • Usa i dischi più veloci possibile. Non dici quali dischi stai usando ma se puoi usare gli SSD.

  • Ottimizza tempdb. Metti tempdb sui dischi più veloci possibile, ancora una volta SSD. Vedi questo articolo SO e questo articolo RedGate .

  • Come affermato in altre risposte, l'utilizzo di una query più selettiva restituirà meno dati e dovrebbe essere quindi più veloce.

Consideriamo ora cosa possiamo fare se ci è consentito aggiungere indici.

Se noi non stavamo parlando di query ad hoc, quindi aggiungiamo noi indici appositamente per la serie limitata di query in fase di esecuzione contro il tavolo. Dato che stiamo discutendo di query ad hoc , cosa si può fare per migliorare la velocità la maggior parte delle volte?

  • Aggiungi un singolo indice di colonna a ogni colonna. Ciò dovrebbe fornire a SQL Server almeno qualcosa con cui lavorare per migliorare la velocità per la maggior parte delle query, ma non sarà ottimale.
  • Aggiungi indici specifici per le query più comuni in modo che siano ottimizzate.
  • Aggiungere ulteriori indici specifici come richiesto monitorando le query con prestazioni scadenti.

modificare

Ho eseguito alcuni test su una tabella "grande" di 22 milioni di righe. La mia tabella ha solo sei colonne ma contiene 4 GB di dati. La mia macchina è un desktop rispettabile con 8 Gb di RAM e una CPU quad core e ha un singolo SSD Agility 3.

Ho rimosso tutti gli indici tranne la chiave primaria nella colonna Id.

Una query simile al problema indicato nella domanda richiede 5 secondi se il server SQL viene riavviato prima e 3 secondi successivamente. Il consulente per l'ottimizzazione del database consiglia ovviamente di aggiungere un indice per migliorare questa query, con un miglioramento stimato del> 99%. L'aggiunta di un indice risulta in un tempo di query effettivamente pari a zero.

Ciò che è anche interessante è che il mio piano di query è identico al tuo (con la scansione dell'indice cluster), ma la scansione dell'indice rappresenta il 9% del costo della query e l'ordinamento il restante 91%. Posso solo presumere che la tua tabella contenga un'enorme quantità di dati e / o che i tuoi dischi siano molto lenti o si trovino su una connessione di rete molto lenta.


2

Anche se su alcune colonne sono presenti indici utilizzati in alcune query, il fatto che la query "ad hoc" provochi una scansione della tabella mostra che non si dispone di indici sufficienti per consentire il completamento efficiente di questa query.

Per gli intervalli di date in particolare è difficile aggiungere buoni indici.

Basta guardare la tua query, il db deve ordinare tutti i record in base alla colonna selezionata per poter restituire i primi n record.

Il db esegue anche una scansione completa della tabella senza la clausola order by? La tabella ha una chiave primaria - senza un PK, il db dovrà lavorare di più per eseguire l'ordinamento?


C'è una chiave primaria sul tavolo. Una scansione della tabella appare anche nel piano di esecuzione durante la semplice esecuzioneselect top 100 * from ER101_ACCT_ORDER_DTL
Lee Tickett

2

Com'è possibile? Senza un indice sulla colonna er101_upd_date_iso come può essere utilizzata una scansione dell'indice cluster?

Un indice è un B-Tree in cui ogni nodo foglia punta a un "gruppo di righe" (chiamato "Pagina" nella terminologia interna di SQL), ovvero quando l'indice è un indice non cluster.

L'indice cluster è un caso speciale, in cui i nodi foglia hanno il "gruppo di righe" (anziché puntare a esse). è per questo...

1) Può esserci un solo indice cluster sulla tabella.

questo significa anche che l'intera tabella è archiviata come indice cluster, ecco perché hai iniziato a vedere la scansione dell'indice piuttosto che una scansione della tabella.

2) Un'operazione che utilizza un indice cluster è generalmente più veloce di un indice non cluster

Ulteriori informazioni su http://msdn.microsoft.com/en-us/library/ms177443.aspx

Per il problema che hai, dovresti davvero considerare l'aggiunta di questa colonna a un indice, come hai detto l'aggiunta di un nuovo indice (o una colonna a un indice esistente) aumenta i costi di INSERT / UPDATE. Ma potrebbe essere possibile rimuovere un indice sottoutilizzato (o una colonna da un indice esistente) da sostituire con "er101_upd_date_iso".

Se le modifiche dell'indice non sono possibili, consiglio di aggiungere una statistica sulla colonna, può velocizzare le cose quando le colonne hanno una correlazione con le colonne indicizzate

http://msdn.microsoft.com/en-us/library/ms188038.aspx

BTW, otterrai molto più aiuto se puoi pubblicare lo schema della tabella di ER101_ACCT_ORDER_DTL. e anche gli indici esistenti ..., probabilmente la query potrebbe essere riscritta per usarne alcuni.


+1 per la risposta. Un commento però, gli indici cluster non sono sempre più veloci come si potrebbe leggere (è possibile fraintenderlo) dalla tua risposta.
Gisli

Penso di aver compreso la differenza tra l'indice cluster / non cluster ma non vedo ancora come sia possibile migliorare l'interrogazione di una colonna che non fa parte dell'indice cluster avendo un indice cluster su altre colonne?
Lee Tickett

In SQL Server l'indice cluster contiene tutte le colonne. L'indice cluster è ciò che decide come i dati vengono archiviati sul disco. È un po 'difficile da spiegare, ma se pensi agli indici come a un albero, l'indice non cluster è un albero e le foglie in basso contengono le informazioni che hai definito nell'indice. Per un indice cluster, le foglie inferiori contengono tutte le colonne della tabella. Ciò è di progettazione in SQL Server.
Gisli

Lo capisco. Ma ho pensato che i rami fossero basati sulle colonne nell'indice cluster. Quindi, se la colonna che sto interrogando non è nell'indice cluster, sicuramente ogni ramo / foglia deve essere scansionato?
Lee Tickett

1
Non capisco. La mia ipotesi migliore è che quando avevi un indice non cluster, è stato scansionato risultando in molti I / O casuali. Quando hai creato l'indice cluster ti sei sbarazzato di quegli I / O casuali? Ma questa è una supposizione, non riesco a trovare nessun altro motivo per questo comportamento ma non sono un esperto.
Gisli

1

Uno dei motivi per cui il test 1M è stato eseguito più velocemente è probabilmente perché le tabelle temporanee sono interamente in memoria e andrebbero su disco solo se il tuo server subisce una pressione della memoria. Puoi rielaborare la tua query per rimuovere l'ordine, aggiungere un buon indice cluster e coprire gli indici come accennato in precedenza o interrogare il DMV per verificare la pressione IO per vedere se è correlato all'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

spero che il mio intero database sia in memoria. c'è un modo per verificarlo o per dire a sql quali tabelle memorizzare in memoria? Sono stato via per alcuni giorni ma quando torno proverò la tua
domanda

Il conteggio medio delle attività e il conteggio medio in sospeso di DiskIO hanno raggiunto il picco a 4. Sono ancora curioso di provare a forzare il db nella ram.
Lee Tickett

0

So che hai detto che l'aggiunta di indici non è un'opzione, ma sarebbe l'unica opzione per eliminare la scansione della tabella che hai. Quando si esegue una scansione, SQL Server legge tutti i 2 milioni di righe sulla tabella per soddisfare la query.

Questo articolo fornisce maggiori informazioni ma ricorda: Seek = good, Scan = bad.

Secondo, non puoi eliminare la selezione * e selezionare solo le colonne che ti servono? Terzo, nessuna clausola "dove"? Anche se hai un indice, dal momento che stai leggendo tutto il meglio che otterrai è una scansione dell'indice (che è meglio di una scansione della tabella, ma non è una ricerca, che è ciò a cui dovresti mirare)


Non è vero che Seek è sempre meglio di Scan. A volte una scansione è effettivamente più efficiente. In caso contrario, M $ non avrebbe incluso un suggerimento per la query FORCESCAN a partire da SQL Server 2008 R2. Vedi qui per maggiori dettagli: msdn.microsoft.com/en-us/library/ms181714(v=sql.105).aspx e anche qui per qualcuno che cerca di forzare una scansione (la terza risposta di Adam Haines ha buone informazioni): social .msdn.microsoft.com / Forums / it-IT / transactsql / thread /…
Solomon Rutzky

1
Bene, prima di tutto, le ricerche sono utili per le query puntuali. In secondo luogo, le scansioni sono utili per le query di intervallo in cui è necessario recuperare molti dati. I sistemi OLAP non funzionerebbero bene senza scansioni. I sistemi OLTP non funzionerebbero bene senza le ricerche. Tutto ha il suo posto nel grande schema delle cose ...
darlove

0

So che è passato molto tempo dall'inizio ... C'è molta saggezza in tutte queste risposte. Una buona indicizzazione è la prima cosa quando si cerca di migliorare una query. Bene, quasi il primo. Il primo (per così dire) è apportare modifiche al codice in modo che sia efficiente. Quindi, dopo che tutto è stato detto e fatto, se si ha una query senza WHERE, o quando la condizione WHERE non è abbastanza selettiva, c'è solo un modo per ottenere i dati: TABLE SCAN (INDEX SCAN). Se sono necessarie tutte le colonne di una tabella, verrà utilizzato TABLE SCAN, senza dubbio. Potrebbe trattarsi di una scansione dell'heap o di una scansione dell'indice cluster, a seconda del tipo di organizzazione dei dati. L'unico ultimo modo per velocizzare le cose (se possibile), è assicurarsi che vengano utilizzati quanti più core possibile per eseguire la scansione: OPTION (MAXDOP 0). Ignoro l'argomento della conservazione, ovviamente,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.