Améliorer les performances des requêtes SQL Server sur les grandes tables

85

J'ai une table relativement grande (actuellement 2 millions d'enregistrements) et j'aimerais savoir s'il est possible d'améliorer les performances pour les requêtes ad hoc. Le mot ad-hoc est ici clé. L'ajout d'index n'est pas une option (il existe déjà des index sur les colonnes qui sont le plus souvent interrogées).

Exécution d'une requête simple pour renvoyer les 100 derniers enregistrements mis à jour:

select top 100 * from ER101_ACCT_ORDER_DTL order by er101_upd_date_iso desc

Prend plusieurs minutes. Voir le plan d'exécution ci-dessous:

entrez la description de l'image ici

Détails supplémentaires de l'analyse de la table:

entrez la description de l'image ici

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

Le serveur est assez puissant (avec une mémoire de 48 Go de RAM, un processeur à 24 cœurs) exécutant SQL Server 2008 r2 x64.

Mise à jour

J'ai trouvé ce code pour créer une table avec 1 000 000 enregistrements. J'ai pensé que je pourrais alors courir SELECT TOP 100 * FROM testEnvironment ORDER BY mailAddress DESCsur quelques serveurs différents pour savoir si mes vitesses d'accès au disque étaient mauvaises sur le serveur.

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;

Mais sur les trois serveurs de test, la requête s'est exécutée presque instantanément. Quelqu'un peut-il expliquer cela?

entrez la description de l'image ici

Mise à jour 2

Merci pour les commentaires - continuez-les à venir ... ils m'ont amené à essayer de changer l'index de clé primaire de non-clusterisé à clusterisé avec des résultats plutôt intéressants (et inattendus?).

Non groupé:

entrez la description de l'image ici

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

Clustered:

entrez la description de l'image ici

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

Comment est-ce possible? Sans index dans la colonne er101_upd_date_iso, comment utiliser une analyse d'index clusterisé?

Mise à jour 3

Comme demandé, voici le script de création de table:

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 table a une taille de 2,8 Go avec une taille d'index de 3,9 Go.

Lee Tickett
la source
1
Il y a de petits indices lorsque vous passez le curseur de la souris sur l'élément de plan. Ils affichent une estimation du coût d'E / S et du processeur. Je m'occuperais d'abord du coût d'E / S.
Grzegorz Gierlik
4
Table Scanindique un tas (pas d'index clusterisé) - la première étape consiste donc à ajouter un bon index clusterisé rapide à votre table. Deuxième étape pourrait consister à vérifier si un index non clusterisé sur er101_upd_date_isoaiderions (et non la cause d' autres inconvénients de la performance)
marc_s
1
@marc_s merci pour cela - j'ai changé l'indice pk en cluster et cela a fait une différence substantielle - pouvez-vous l'expliquer davantage? (regardez la mise à jour 2)
Lee Tickett
2
Eh bien, l'index clusterisé modifie simplement la disposition de stockage de la table. L'index clusterisé contient les données de table réelles dans ses nœuds de niveau feuille - c'est-à-dire: pour lire la table entière, SQL Server effectue maintenant une analyse d'index cluster (essentiellement une «analyse de table» sur une table avec un index cluster). Cela sera presque toujours un peu plus rapide que de faire une analyse de table sur un tas (sans index clusterisé). Si vous avez maintenant ajouté un index non clusterisé sur la er101_upd_date_isocolonne, vous pouvez probablement aussi vous débarrasser de l'opération "Trier" dans votre plan d'exécution et accélérer encore plus les choses
marc_s
2
@LeeTickett, veuillez montrer vos définitions de table et d'index. Il y a de nombreux facteurs à considérer et personne ne semble les demander (ce qui me surprend mais peut-être que cela ne devrait pas). Je peux vous dire que 2 millions de lignes ne sont PAS grandes et les tables correctement indexées avec plus de 200 millions de lignes retournent plus rapidement que cela. L'index clusterisé (maintenant que vous en avez un grâce à marc_s) est probablement un mauvais choix mais difficile à dire sans voir les détails. N'utilisez PAS le partitionnement, mais utilisez SET STATISTICS IO ON et cochez Logical Reads dans l'onglet messages. Si un changement réduit les lectures logiques, vous vous rapprochez.
Solomon Rutzky

Réponses:

59

Réponse simple: NON. Vous ne pouvez pas aider les requêtes ad hoc sur une table de 238 colonnes avec un facteur de remplissage de 50% sur l'index clusterisé.

Réponse détaillée:

Comme je l'ai indiqué dans d'autres réponses sur ce sujet, la conception d'index est à la fois un art et une science et il y a tellement de facteurs à considérer qu'il y a peu, voire aucune, de règles strictes et rapides. Vous devez prendre en compte: le volume des opérations DML par rapport aux SELECT, le sous-système de disque, les autres index / déclencheurs sur la table, la distribution des données dans la table, sont des requêtes utilisant les conditions SARGable WHERE, et plusieurs autres choses dont je ne me souviens même pas correctement maintenant.

Je peux dire qu'aucune aide ne peut être donnée pour les questions sur ce sujet sans une compréhension de la table elle-même, de ses index, déclencheurs, etc. 99% du problème) Je peux offrir quelques suggestions.

Premièrement, si la définition du tableau est précise (238 colonnes, facteur de remplissage de 50%), vous pouvez ignorer à peu près le reste des réponses / conseils ici ;-). Désolé d'être moins que politique ici, mais sérieusement, c'est une chasse à l'oie sauvage sans connaître les détails. Et maintenant que nous voyons la définition de la table, il devient un peu plus clair de savoir pourquoi une simple requête prendrait si longtemps, même lorsque les requêtes de test (mise à jour n ° 1) ont été exécutées si rapidement.

Le principal problème ici (et dans de nombreuses situations de mauvaise performance) est une mauvaise modélisation des données. 238 colonnes n'est pas interdit, tout comme avoir 999 index n'est pas interdit, mais ce n'est généralement pas très sage.

Recommandations:

  1. Tout d'abord, cette table a vraiment besoin d'être remodelée. S'il s'agit d'une table d'entrepôt de données, peut-être, mais sinon, ces champs doivent vraiment être divisés en plusieurs tables qui peuvent toutes avoir le même PK. Vous auriez une table d'enregistrement maître et les tables enfants ne sont que des informations dépendantes basées sur des attributs communément associés et le PK de ces tables est le même que le PK de la table maître et donc également FK à la table maître. Il y aura une relation 1-à-1 entre le maître et toutes les tables enfants.
  2. L'utilisation de ANSI_PADDING OFFest dérangeante, sans parler d'incohérence dans le tableau en raison des divers ajouts de colonnes au fil du temps. Je ne sais pas si vous pouvez résoudre ce problème maintenant, mais idéalement, vous auriez toujours ANSI_PADDING ON, ou à tout le moins, le même paramètre dans toutes les ALTER TABLEinstructions.
  3. Envisagez de créer 2 groupes de fichiers supplémentaires: tables et index. Il est préférable de ne pas mettre vos affaires PRIMARYcar c'est là que SQL SERVER stocke toutes ses données et métadonnées sur vos objets. Vous créez votre table et votre index clusterisé (car il s'agit des données de la table) sur [Tables]et tous les index non groupés sur[Indexes]
  4. Augmentez le facteur de remplissage de 50%. Ce faible nombre explique probablement pourquoi votre espace d'index est plus grand que votre espace de données. Faire une reconstruction d'index recréera les pages de données avec un maximum de 4k (sur la taille de page totale de 8k) utilisées pour vos données afin que votre tableau soit réparti sur une large zone.
  5. Si la plupart ou toutes les requêtes ont "ER101_ORG_CODE" dans la WHEREcondition, envisagez de le déplacer vers la première colonne de l'index clusterisé. En supposant qu'il est utilisé plus souvent que "ER101_ORD_NBR". Si "ER101_ORD_NBR" est utilisé plus souvent, conservez-le. Il semble juste, en supposant que les noms de champ signifient «OrganizationCode» et «OrderNumber», que «OrgCode» est un meilleur groupement qui pourrait avoir plusieurs «OrderNumbers» à l'intérieur.
  6. Point mineur, mais si "ER101_ORG_CODE" est toujours 2 caractères, alors utilisez CHAR(2)au lieu de VARCHAR(2)car cela sauvera un octet dans l'en-tête de ligne qui suit les tailles de largeur variable et s'additionne sur des millions de lignes.
  7. Comme d'autres l'ont mentionné ici, l'utilisation SELECT *nuira aux performances. Non seulement parce que SQL Server doit renvoyer toutes les colonnes et donc être plus susceptible d'effectuer une analyse d'index en cluster quels que soient vos autres index, mais il faut également du temps à SQL Server pour accéder à la définition de la table et la traduire *dans tous les noms de colonne. . Il devrait être légèrement plus rapide de spécifier les 238 noms de colonnes de la SELECTliste, mais cela n'aidera pas le problème d'analyse. Mais avez-vous jamais vraiment besoin des 238 colonnes en même temps de toute façon?

Bonne chance!

MISE
À JOUR Par souci d'exhaustivité de la question «comment améliorer les performances sur une grande table pour les requêtes ad hoc», il convient de noter que même si cela n'aidera pas dans ce cas spécifique, SI quelqu'un utilise SQL Server 2012 (ou plus récent à ce moment-là) et SI la table n'est pas mise à jour, l'utilisation des index Columnstore est une option. Pour plus de détails sur cette nouvelle fonctionnalité, regardez ici: http://msdn.microsoft.com/en-us/library/gg492088.aspx (je crois que ceux-ci ont été mis à jour à partir de SQL Server 2014).

MISE À JOUR 2
Les considérations supplémentaires sont:

  • Activez la compression sur l'index clusterisé. Cette option est devenue disponible dans SQL Server 2008, mais en tant que fonctionnalité de l'édition Entreprise uniquement. Cependant, à partir de SQL Server 2016 SP1 , la compression de données était disponible dans toutes les éditions ! Veuillez consulter la page MSDN pour la compression de données pour plus de détails sur la compression de lignes et de pages.
  • Si vous ne pouvez pas utiliser la compression des données, ou bien si elle ne fournira pas grand intérêt pour une table particulière, alors si vous avez une colonne d'un type de longueur fixe ( INT, BIGINT, TINYINT, SMALLINT, CHAR, NCHAR, BINARY, DATETIME, SMALLDATETIME, MONEY, etc.) et plus de 50 % des lignes sont NULL, puis envisagez d'activer l' SPARSEoption qui est devenue disponible dans SQL Server 2008. Veuillez consulter la page MSDN pour utiliser des colonnes éparses pour plus de détails.
Solomon Rutzky
la source
Sur le point 7 personnellement , j'imagine qu'il devrait être plus rapide d'ajouter 238 noms de colonnes de métadonnées que parse - les du texte de la requête et alors vérifier les métadonnées de toute façon à assurer tous exist.There sont des arguments assez forts contre *sans qu'un douteux
Martin Smith
53

Il y a quelques problèmes avec cette requête (et cela s'applique à chaque requête).

Manque d'index

Le manque d'index sur la er101_upd_date_isocolonne est la chose la plus importante comme Oded l' a déjà mentionné.

Sans index correspondant (dont l'absence pourrait provoquer un scan de table), il n'y a aucune chance d'exécuter des requêtes rapides sur de grandes tables.

Si vous ne pouvez pas ajouter d'index (pour diverses raisons, y compris il ne sert à rien de créer un index pour une seule requête ad hoc ), je suggérerais quelques solutions de contournement (qui peuvent être utilisées pour les requêtes ad hoc):

1. Utilisez des tables temporaires

Créez une table temporaire sur un sous-ensemble (lignes et colonnes) de données qui vous intéresse. La table temporaire doit être beaucoup plus petite que la table source d'origine, peut être indexée facilement (si nécessaire) et peut mettre en cache un sous-ensemble de données qui vous intéresse.

Pour créer une table temporaire, vous pouvez utiliser du code (non testé) comme:

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

Avantages:

  • Facile à faire pour n'importe quel sous-ensemble de données.
  • Facile à gérer - c'est temporaire et c'est une table .
  • N'affecte pas les performances globales du système comme view.
  • La table temporaire peut être indexée.
  • Vous n'avez pas à vous en soucier - c'est temporaire :).

Les inconvénients:

  • C'est un instantané des données - mais c'est probablement suffisant pour la plupart des requêtes ad hoc.

2. Expression de table commune - CTE

Personnellement, j'utilise beaucoup CTE avec des requêtes ad hoc - cela aide beaucoup à construire (et tester) une requête pièce par pièce.

Voir l'exemple ci-dessous (la requête commençant par WITH).

Avantages:

  • Facile à construire à partir d'une grande vue , puis en sélectionnant et en filtrant ce dont vous avez vraiment besoin.
  • Facile à tester.

Les inconvénients:

  • Certaines personnes n'aiment pas CDE - les requêtes CDE semblent longues et difficiles à comprendre.

3. Créer des vues

Similaire à ci-dessus, mais créez des vues au lieu de tables temporaires (si vous jouez souvent avec les mêmes requêtes et que vous avez une version MS SQL qui prend en charge les vues indexées.

Vous pouvez créer des vues ou des vues indexées sur un sous-ensemble de données qui vous intéresse et exécuter des requêtes sur la vue - qui ne devraient contenir que des sous-ensembles intéressants de données beaucoup plus petits que la table entière.

Avantages:

  • Facile à faire.
  • Il est à jour avec les données sources.

Les inconvénients:

  • Possible uniquement pour un sous-ensemble défini de données.
  • Cela pourrait être inefficace pour les grandes tables avec un taux élevé de mises à jour.
  • Pas si facile à gérer.
  • Peut affecter les performances globales du système.
  • Je ne suis pas sûr que les vues indexées soient disponibles dans toutes les versions de MS SQL.

Sélection de toutes les colonnes

Lancer star query ( SELECT * FROM) sur une grande table n'est pas une bonne chose ...

Si vous avez de grandes colonnes (comme de longues chaînes), il faut beaucoup de temps pour les lire à partir du disque et passer par le réseau.

J'essaierais de remplacer *par des noms de colonnes dont vous avez vraiment besoin.

Ou, si vous avez besoin de toutes les colonnes, essayez de réécrire la requête en quelque chose comme (en utilisant une expression de données commune ):

;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 

Lecture sale

La dernière chose qui pourrait accélérer la requête ad hoc est d'autoriser les lectures sales avec une indication de tableWITH (NOLOCK) .

Au lieu d'un indice, vous pouvez définir le niveau d'isolation des transactions pour lire sans engagement:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

ou définissez le paramètre SQL Management Studio approprié.

Je suppose que pour les requêtes ad hoc, les lectures sales sont suffisantes.

Grzegorz Gierlik
la source
2
+1 pour SELECT *- il force SQL Server à utiliser l'index cluster. Du moins, ça devrait. Je ne vois aucune raison réelle pour un index de couverture non groupé ... couvrant l'ensemble du tableau :)
ta.speot.is
4
Cette réponse concerne uniquement l'amélioration de la vitesse de la requête d'exemple, PAS la question, qui est "est-il possible d'améliorer les performances pour les requêtes ad hoc"
Phil
CDE est maintenant CTE (Common Table Expression)
sqluser
12

Vous obtenez une analyse de table là-bas, ce qui signifie que vous n'avez pas d'index défini sur er101_upd_date_iso, ou si cette colonne fait partie d'un index existant, l'index ne peut pas être utilisé (ce n'est peut-être pas la colonne d'indexation principale).

L'ajout d'index manquants améliorera les performances sans fin.

il existe déjà des index sur les colonnes qui sont le plus souvent interrogées

Cela ne signifie pas qu'ils sont utilisés dans cette requête (et ils ne le sont probablement pas).

Je suggère de lire Trouver les causes des mauvaises performances dans SQL Server par Gail Shaw, partie 1 et partie 2 .

Oded
la source
mon point est que ce n'est pas l'une des colonnes qui est le plus souvent interrogée :)
Lee Tickett
1
@LeeTickett - Et pourtant, c'est la seule colonne sur laquelle vous pouvez éventuellement ajouter un index pour améliorer les performances de cette requête.
Oded le
2
Rien de tel que les recherches non indexées optimisées. Vous utilisez soit un index, soit une analyse complète de la table. Si vous ne voulez pas d'analyses de table complètes, vous avez besoin d'index. En fonction de votre profil d'utilisation, il peut être suffisamment bon marché pour simplement ajouter des index. Oh, et le type de données de la colonne compte aussi. Si vous êtes er101_upd_date_isoun énorme varchar, ou un int, cela changera considérablement les performances.
Cylindrique
Merci. J'ai ajouté un commentaire à la question. J'apprécie qu'il est peu probable que je puisse optimiser la requête - mais j'imagine qu'il peut y avoir des moyens d'améliorer les performances des requêtes ad hoc
Lee Tickett
Je suis un apprenant, comment savons-nous quelle colonne doit être indexée?
Virus
7

La question indique spécifiquement que les performances doivent être améliorées pour les requêtes ad hoc et que les index ne peuvent pas être ajoutés. Alors, en prenant cela pour argent comptant, que peut-on faire pour améliorer les performances sur n'importe quelle table?

Puisque nous considérons des requêtes ad hoc, la clause WHERE et la clause ORDER BY peuvent contenir n'importe quelle combinaison de colonnes. Cela signifie que presque quels que soient les index placés sur la table, certaines requêtes nécessitent une analyse de table, comme indiqué ci-dessus dans le plan de requête d'une requête peu performante.

En tenant compte de cela, supposons qu'il n'y ait aucun index sur la table à l'exception d'un index cluster sur la clé primaire. Examinons maintenant les options dont nous disposons pour maximiser les performances.

  • Défragmenter la table

    Tant que nous avons un index clusterisé, nous pouvons défragmenter la table en utilisant DBCC INDEXDEFRAG (obsolète) ou de préférence ALTER INDEX . Cela minimisera le nombre de lectures de disque nécessaires pour analyser la table et améliorera la vitesse.

  • Utilisez les disques les plus rapides possible. Vous ne dites pas quels disques vous utilisez mais si vous pouvez utiliser des SSD.

  • Optimiser tempdb. Mettez tempdb sur les disques les plus rapides possibles, encore une fois sur les SSD. Voir cet article SO et cet article RedGate .

  • Comme indiqué dans d'autres réponses, l'utilisation d'une requête plus sélective renverra moins de données et devrait donc être plus rapide.

Voyons maintenant ce que nous pouvons faire si nous sommes autorisés à ajouter des index.

Si nous ne parlions pas de requêtes ad hoc, nous ajouterions des index spécifiquement pour l'ensemble limité de requêtes exécutées sur la table. Puisque nous discutons des requêtes ad hoc , que peut-on faire pour améliorer la vitesse la plupart du temps?

  • Ajoutez un seul index de colonne à chaque colonne. Cela devrait donner à SQL Server au moins quelque chose avec lequel travailler pour améliorer la vitesse de la majorité des requêtes, mais ne sera pas optimal.
  • Ajoutez des index spécifiques pour les requêtes les plus courantes afin qu'elles soient optimisées.
  • Ajoutez des index spécifiques supplémentaires si nécessaire en surveillant les requêtes peu performantes.

Éditer

J'ai exécuté des tests sur une «grande» table de 22 millions de lignes. Ma table ne comporte que six colonnes mais contient 4 Go de données. Ma machine est un ordinateur de bureau respectable avec 8 Go de RAM et un processeur quad core et dispose d'un seul SSD Agility 3.

J'ai supprimé tous les index à l'exception de la clé primaire sur la colonne Id.

Une requête similaire au problème donné dans la question prend 5 secondes si le serveur SQL est redémarré en premier et 3 secondes par la suite. Le conseiller au réglage de la base de données recommande évidemment l'ajout d'un index pour améliorer cette requête, avec une amélioration estimée de> 99%. L'ajout d'un index entraîne un temps de requête de zéro.

Ce qui est également intéressant, c'est que mon plan de requête est identique au vôtre (avec l'analyse d'index clusterisé), mais l'analyse d'index représente 9% du coût de la requête et le tri les 91% restants. Je ne peux que supposer que votre table contient une énorme quantité de données et / ou que vos disques sont très lents ou situés sur une connexion réseau très lente.

Phil
la source
2

Même si vous avez des index sur certaines colonnes qui sont utilisés dans certaines requêtes, le fait que votre requête «ad hoc» provoque une analyse de table montre que vous ne disposez pas d'index suffisants pour permettre à cette requête de se terminer efficacement.

Pour les plages de dates en particulier, il est difficile d'ajouter de bons index.

En regardant simplement votre requête, la base de données doit trier tous les enregistrements par la colonne sélectionnée pour pouvoir renvoyer les n premiers enregistrements.

La base de données effectue-t-elle également une analyse complète de la table sans la clause order by? La table a-t-elle une clé primaire - sans PK, la base de données devra travailler plus dur pour effectuer le tri?

Foamdino
la source
Il y a une clé primaire sur la table. Un scan de table apparaît également dans le plan d'exécution lors de l'exécution simpleselect top 100 * from ER101_ACCT_ORDER_DTL
Lee Tickett
2

Comment est-ce possible? Sans index dans la colonne er101_upd_date_iso, comment utiliser une analyse d'index clusterisé?

Un index est un arbre B où chaque nœud feuille pointe vers un «groupe de lignes» (appelé «Page» dans la terminologie interne SQL), c'est-à-dire lorsque l'index est un index non clusterisé.

L'index clusterisé est un cas particulier, dans lequel les nœuds feuilles ont le «groupe de lignes» (plutôt que de pointer vers eux). c'est pourquoi...

1) Il ne peut y avoir qu'un seul index clusterisé sur la table.

cela signifie également que toute la table est stockée en tant qu'index clusterisé, c'est pourquoi vous avez commencé à voir une analyse d'index plutôt qu'une analyse de table.

2) Une opération qui utilise un index clusterisé est généralement plus rapide qu'un index non clusterisé

En savoir plus sur http://msdn.microsoft.com/en-us/library/ms177443.aspx

Pour le problème que vous rencontrez, vous devriez vraiment envisager d'ajouter cette colonne à un index, comme vous l'avez dit, l'ajout d'un nouvel index (ou d'une colonne à un index existant) augmente les coûts INSERT / UPDATE. Mais il peut être possible de supprimer un index sous-utilisé (ou une colonne d'un index existant) pour le remplacer par «er101_upd_date_iso».

Si les changements d'index ne sont pas possibles, je recommande d'ajouter une statistique sur la colonne, cela peut fixer les choses lorsque les colonnes ont une certaine corrélation avec les colonnes indexées

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

BTW, Vous obtiendrez beaucoup plus d'aide si vous pouvez publier le schéma de table de ER101_ACCT_ORDER_DTL. et les index existants aussi ..., la requête pourrait probablement être réécrite pour en utiliser certains.

shankar_pratap
la source
+1 pour la réponse. Un commentaire cependant, les index clusterisés ne sont pas toujours plus rapides que l'on pourrait le lire (il est possible de mal le comprendre) à partir de votre réponse.
Gisli
Je pense que je comprends la différence entre l'index clusterisé / non clusterisé mais je ne vois toujours pas comment l'interrogation d'une colonne qui ne fait pas partie de l'index clusterisé peut être améliorée en ayant un index clusterisé sur d'autres colonnes?
Lee Tickett
Dans SQL Server, l'index clusterisé contient toutes les colonnes. L'index cluster est ce qui décide de la manière dont les données sont stockées sur le disque. C'est un peu difficile pour moi d'expliquer, mais si vous considérez les index comme un arbre, l'index non clusterisé est un arbre et les feuilles du bas contiennent les informations que vous avez définies comme étant dans l'index. Pour un index clusterisé, les feuilles du bas contiennent toutes les colonnes de la table. C'est par conception dans SQL Server.
Gisli
Je comprends ça. Mais je pensais que les branches étaient basées sur les colonnes de l'index groupé. Donc, si la colonne que j'interroge n'est pas dans l'index clusterisé, chaque branche / feuille doit être analysée?
Lee Tickett
1
Je ne comprends pas. Ma meilleure hypothèse est que lorsque vous aviez un index non clusterisé, il était analysé, ce qui entraînait de nombreuses E / S aléatoires. Lorsque vous avez créé l'index clusterisé, vous vous êtes débarrassé de ces E / S aléatoires? Mais c'est une supposition, je ne trouve aucune autre raison à ce comportement mais je ne suis pas un expert.
Gisli
1

L'une des raisons pour lesquelles votre test 1M s'est exécuté plus rapidement est probablement parce que les tables temporaires sont entièrement en mémoire et n'iront sur le disque que si votre serveur subit une pression de mémoire. Vous pouvez soit recréer votre requête pour supprimer l'ordre par, ajouter un bon index clusterisé et des index couvrant comme mentionné précédemment, ou interroger le DMV pour vérifier la pression d'E / S pour voir si le matériel est lié.

-- 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
la source
j'espère que toute ma base de données serait en mémoire. y a-t-il un moyen de vérifier cela ou de dire à SQL quelles tables stocker en mémoire? Je suis absent depuis quelques jours, mais à mon retour, je vais essayer votre requête
Lee Tickett
Le nombre moyen de tâches et le nombre moyen de DiskIO en attente ont culminé à 4. Je suis toujours curieux d'essayer de forcer la base de données en RAM.
Lee Tickett
0

Je sais que vous avez dit que l'ajout d'index n'est pas une option, mais que ce serait la seule option pour éliminer l'analyse de table que vous avez. Lorsque vous effectuez une analyse, SQL Server lit les 2 millions de lignes de la table pour répondre à votre requête.

cet article fournit plus d'informations mais rappelez-vous: Seek = good, Scan = bad.

Deuxièmement, ne pouvez-vous pas éliminer le select * et ne sélectionner que les colonnes dont vous avez besoin? Troisièmement, pas de clause «où»? Même si vous avez un index, puisque vous lisez tout, le mieux que vous obtiendrez est un balayage d'index (ce qui est mieux qu'un balayage de table, mais ce n'est pas une recherche, ce que vous devriez viser)

Diego
la source
Il n'est pas vrai que Seek est toujours meilleur que Scan. Parfois, un scan est en fait plus efficace. Si ce n'était pas le cas, M $ n'aurait pas inclus d'indicateur de requête FORCESCAN à partir de SQL Server 2008 R2. Voir ici pour plus de détails: msdn.microsoft.com/en-us/library/ms181714(v=sql.105).aspx et même ici pour quelqu'un qui cherche à forcer un scan (la troisième réponse d'Adam Haines a de bonnes informations): social .msdn.microsoft.com / Forums / en-US / transactsql / thread /…
Solomon Rutzky
1
Eh bien, tout d'abord, les recherches sont bonnes pour les requêtes ponctuelles. Deuxièmement, les analyses sont bonnes pour les requêtes de plage où beaucoup de données doivent être récupérées. Les systèmes OLAP ne fonctionneraient pas bien sans analyses. Les systèmes OLTP ne fonctionneraient pas bien sans recherche. Tout a sa place dans le grand schéma des choses ...
chérie
0

Je sais que cela fait pas mal de temps depuis le début ... Il y a beaucoup de sagesse dans toutes ces réponses. Une bonne indexation est la première chose à faire lorsque vous essayez d'améliorer une requête. Eh bien, presque le premier. Le plus important (pour ainsi dire) consiste à apporter des modifications au code pour qu'il soit efficace. Donc, après que tout a été dit et fait, si l'on a une requête sans WHERE, ou lorsque la condition WHERE n'est pas assez sélective, il n'y a qu'une seule façon d'obtenir les données: TABLE SCAN (INDEX SCAN). Si l'on a besoin de toutes les colonnes d'une table, alors TABLE SCAN sera utilisé - pas de doute à ce sujet. Il peut s'agir d'une analyse de tas ou d'une analyse d'index cluster, selon le type d'organisation des données. La seule dernière façon d'accélérer les choses (si possible) est de s'assurer que le plus grand nombre possible de cœurs est utilisé pour effectuer l'analyse: OPTION (MAXDOP 0). J'ignore le sujet du stockage, bien sûr,

chérie
la source