J'ai un tableau relativement volumineux (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 étant la clé ici. L'ajout d'index n'est pas une option (il existe déjà des index sur les colonnes qui sont le plus souvent interrogés).
Exécuter 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:
Détails supplémentaires de l'analyse de la table:
SQL Server Execution Times:
CPU time = 3945 ms, elapsed time = 148524 ms.
Le serveur est assez puissant (mémoire vive de 48 Go, processeur 24 cœurs) exécutant SQL Server 2008 R2 x64.
Mettre à jour
J'ai trouvé ce code pour créer une table avec 1 000 000 enregistrements. Je pensais pouvoir alors exécuter SELECT TOP 100 * FROM testEnvironment ORDER BY mailAddress DESC
sur plusieurs serveurs différents pour savoir si la vitesse d'accès à mon disque était médiocre 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’exécutait presque instantanément. Quelqu'un peut-il expliquer cela?
Mise à jour 2
Merci pour les commentaires - continuez de les envoyer s'il vous plaît ... ils m'ont amené à essayer de changer l'index de clé primaire de non-cluster à cluster avec des résultats plutôt intéressants (et inattendus?).
Non clusterisé:
SQL Server Execution Times:
CPU time = 3634 ms, elapsed time = 154179 ms.
Clustered:
SQL Server Execution Times:
CPU time = 2650 ms, elapsed time = 52177 ms.
Comment est-ce possible? Sans index sur la colonne er101_upd_date_iso, comment utiliser une analyse d'index en cluster?
Mise à jour 3
Comme demandé, c'est 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 taille du tableau est de 2,8 Go, la taille de l'index étant de 3,9 Go.
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 dit dans d’autres réponses sur ce sujet, la conception d’index est à la fois un art et une science et il ya tellement de facteurs à prendre en compte qu’il existe peu de règles strictes, voire aucune. Vous devez prendre en compte le volume d'opérations DML par rapport aux SELECT, le sous-système de disque, les autres index/déclencheurs de la table, la distribution des données dans la table, les requêtes utilisant les conditions SARGable WHERE et plusieurs autres choses dont je ne me souviens même pas bien. à présent.
Je peux dire que les questions sur ce sujet ne peuvent être résolues sans une compréhension de la table elle-même, de ses index, de ses déclencheurs, etc. Maintenant que vous avez posté la définition de la table (vous attendez toujours les index mais la définition de la table à elle seule pointe vers 99% de la question) je peux offrir quelques suggestions.
Premièrement, si la définition de la table est précise (238 colonnes, facteur de remplissage de 50%), vous pouvez alors pratiquement ignorer le reste des réponses/conseils ici ;-). Désolé d'être moins que politique ici, mais sérieusement, c'est une poursuite de 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 prend autant de temps, même lorsque les requêtes de test (mise à jour n ° 1) ont été exécutées aussi rapidement.
Le problème principal ici (et dans de nombreuses situations de performances médiocres) 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:
ANSI_PADDING OFF
est dérangeante, sans parler des incohérences dans la table en raison des divers ajouts de colonnes au fil du temps. Vous ne savez pas si vous pouvez résoudre ce problème maintenant, mais idéalement, vous devriez toujours avoir ANSI_PADDING ON
, ou tout au moins le même paramètre dans toutes les instructions ALTER TABLE
.PRIMARY
, car 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 cluster (car il s'agit des données de la table) sur [Tables]
et tous les index non clusterisés sur [Indexes]
.WHERE
, envisagez de le déplacer vers la colonne de tête de l'index en cluster. En supposant qu'il soit utilisé plus souvent que "ER101_ORD_NBR". Si "ER101_ORD_NBR" est utilisé plus souvent, conservez-le. Il semble simplement, en supposant que les noms de champ signifient "CodeOrganisation" et "NuméroCommande", que "CodeOrg" soit un meilleur groupement pouvant contenir plusieurs "NuméroCommande".CHAR(2)
au lieu de VARCHAR(2)
, car il enregistre un octet dans l’en-tête de la ligne qui suit les tailles de largeur variable et s’ajoute à des millions de lignes.SELECT *
nuira aux performances. Non seulement parce que SQL Server doit renvoyer toutes les colonnes et donc être plus susceptible de faire une analyse d'index en cluster, quels que soient vos autres index, il faut également du temps à SQL Server pour accéder à la définition de la table et traduire *
en tous les noms de colonne. Il devrait être légèrement plus rapide de spécifier tous les 238 noms de colonnes dans la liste SELECT
bien que cela n'aidera pas le problème de Scan. Mais avez-vous toujours besoin des 238 colonnes en même temps?Bonne chance!
UPDATE
Par souci d’exhaustivité à la question "Comment améliorer les performances sur une table volumineuse pour des requêtes ad-hoc", il convient de noter que cela n’aidera en rien ce cas particulier, SI quelqu'un utilise SQL Server 2012 (ou plus tard si cela arrive) et SI la table n'est pas mise à jour, alors l'utilisation de Columnstore Indexes est une option. Pour plus de détails sur cette nouvelle fonctionnalité, regardez ici: http://msdn.Microsoft.com/en-us/library/gg492088.aspx (Je pense que ces modifications ont été créées pour pouvoir être mises à jour à partir de SQL Server. 2014).
UPDATE 2
Autres considérations à prendre en compte:
INT
, BIGINT
, TINYINT
, SMALLINT
, CHAR
, NCHAR
, BINARY
, DATETIME
, SMALLDATETIME
, MONEY
, etc) et bien plus de 50 % des lignes sont NULL
, puis envisagez d'activer l'option SPARSE
qui est devenue disponible dans SQL Server 2008. Veuillez consulter la page MSDN pour tiliser des colonnes éparpillées pour plus de détails.Il y a quelques problèmes avec cette requête (et cela s'applique à chaque requête).
L'absence d'index sur la colonne er101_upd_date_iso
est la chose la plus importante car Oded a déjà été mentionné.
Sans la correspondance d'index (ce qui pourrait causer une analyse 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, dont , il est inutile de créer un index pour une seule requête ad-hoc). Je suggérerais donc quelques solutions de contournement (pouvant être utilisées pour des requêtes ad-hoc):
Créez une table temporaire sur un sous-ensemble (lignes et colonnes) de données qui vous intéressent ..__ La table temporaire doit être beaucoup plus petite que la table source originale, peut être indexée facilement (si nécessaire) et peut cached sous-ensemble de données qui tu es intéressé par.
Pour créer une table temporaire, vous pouvez utiliser un 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:
view
.Les inconvénients:
Personnellement, j'utilise CTE beaucoup 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:
Les inconvénients:
Semblable à ce qui précède, mais créez des vues au lieu de tables temporaires (si vous jouez souvent avec les mêmes requêtes et que vous avez la 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 une vue - qui ne devrait contenir qu'un sous-ensemble de données intéressant, beaucoup plus petit que la table entière.
Avantages:
Les inconvénients:
Lancer star query (SELECT * FROM
) sur une grande table n'est pas une bonne chose ...
Si vous avez de grandes colonnes (telles que des chaînes longues), il faut beaucoup de temps pour les lire à partir du disque et les 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 sur quelque chose comme (en utilisant expression commune de données):
;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
La dernière chose qui pourrait accélérer la requête ad hoc est d’autoriser les lectures dirty avec indice de table WITH (NOLOCK)
.
Au lieu d’un indice, vous pouvez définir le niveau d’isolation de la transaction 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, dirty reads est suffisant.
Vous obtenez un analyse de table} ici, 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 principale de l'indexeur).
L'ajout d'index manquants aidera les performances sans fin.
il y a 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 Recherche des causes des performances médiocres dans SQL Server par Gail Shaw, partie 1 et partie 2 .
La question précise que les performances doivent être améliorées pour les requêtes ad-hoc et que les index ne peuvent pas être ajoutés. Donc, à première vue, que peut-on faire pour améliorer les performances d'une table?
Puisque nous examinons des requêtes ad hoc, les clauses WHERE et ORDER BY peuvent contenir n’importe quelle combinaison de colonnes. Cela signifie que, indépendamment des index placés sur la table, certaines requêtes nécessiteront une analyse de la table, comme indiqué ci-dessus dans le plan de requête d'une requête peu performante.
En prenant cela en compte, supposons qu’il n’y ait aucun index sur la table, à part un index clusterisé sur la clé primaire. Examinons maintenant quelles options nous avons pour optimiser 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 sur disque nécessaires pour analyser la table et améliorera la vitesse.
Utilisez les disques les plus rapides possibles. Vous ne dites pas quels disques vous utilisez, mais si vous pouvez utiliser des disques SSD.
Optimiser tempdb. Placez tempdb sur les disques les plus rapides possibles, à nouveau les SSD. Voir ceci article SO et ceci article de 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.
Examinons 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 en cours d'exécution sur la table. Puisque nous discutons de requêtes ad-hoc , que peut-on faire pour améliorer la vitesse la plupart du temps?
Modifier
J'ai effectué 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 bureau respectable avec 8 Go RAM, un processeur quad core et un seul SSD Agility 3.
J'ai supprimé tous les index en dehors de la clé primaire de la colonne Id.
Une requête similaire à celle indiquée dans la question prend 5 secondes si le serveur SQL est redémarré en premier et 3 secondes plus tard. Le conseiller d'optimisation 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 à> 99%. L'ajout d'un index entraîne un temps de requête égal à 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 en cluster), mais l'analyse d'index représente 9% du coût de la requête et le tri des 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.
Comment est-ce possible? Sans index sur la colonne er101_upd_date_iso, comment utiliser une analyse d'index en cluster?
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 terminaux ont le "groupe de lignes" (plutôt que de les pointer). c'est pourquoi...
1) Il ne peut y avoir qu'un seul index clusterisé sur la table.
cela signifie également que la table entière est stockée en tant qu'index cluster, c'est pourquoi vous avez commencé à voir une analyse d'index plutôt qu'une analyse de table.
2) Une opération utilisant 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
En fonction de votre problème, vous devriez vraiment envisager d’ajouter cette colonne à un index, car vous avez dit que l’ajout d’un nouvel index (ou d’une colonne à un index existant) augmentait les coûts INSERT/UPDATE. Mais il pourrait ê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 modifications d'index ne sont pas possibles, je recommande d'ajouter une statistique sur la colonne, cela peut accélérer 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 aurez beaucoup plus d’aide si vous pouvez publier le schéma de table de ER101_ACCT_ORDER_DTL . Ainsi que les index existants aussi ..., la requête pourrait probablement être réécrite pour en utiliser quelques-uns.
Même si vous avez des index sur certaines colonnes utilisées dans certaines requêtes, le fait que votre requête "ad-hoc" provoque une analyse de table montre que vous ne disposez pas d'un nombre suffisant d'index 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 de 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 fort pour effectuer le tri?
Une des raisons pour lesquelles votre test 1M a fonctionné plus rapidement est probablement due au fait que les tables temporaires sont entièrement en mémoire et ne sont enregistrées sur disque que si votre serveur est soumis à une pression mémoire. Vous pouvez reformuler votre requête pour supprimer la commande en ajoutant un bon index clusterisé et un ou plusieurs index couvrant comme indiqué précédemment, ou interroger le DMV pour vérifier la pression de IO afin de déterminer 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
Je sais que ça fait un bon bout 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 pour améliorer une requête. Eh bien, presque le premier. La première chose à faire (pour ainsi dire) consiste à modifier le code pour qu'il soit efficace. Donc, une fois que tout est dit et fait, si on a une requête sans WHERE, ou si la condition WHERE n'est pas assez sélective, il n'y a qu'un seul moyen d'obtenir les données: TABLE SCAN (INDEX SCAN). Si vous avez besoin de toutes les colonnes d'une table, alors TABLE SCAN sera utilisé - aucune question à ce sujet. Cela peut être une analyse de tas ou une analyse d'index en cluster, selon le type d'organisation des données. Le dernier moyen d'accélérer les choses (dans la mesure du possible) est de s'assurer que le plus grand nombre de cœurs possible est utilisé pour effectuer l'analyse: OPTION (MAXDOP 0). J'ignore le sujet du stockage, bien sûr, mais vous devez vous assurer que vous avez une mémoire RAM illimitée, ce qui va sans dire :)
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.
this article fournit plus d'informations mais rappelez-vous: Seek = good, Scan = bad.
Deuxièmement, ne pouvez-vous pas éliminer les éléments select * et ne sélectionner que les colonnes dont vous avez besoin? Troisièmement, aucune clause "où"? Même si vous avez un index, puisque vous lisez tout, le mieux que vous obtiendrez est un scan d'index (ce qui est meilleur qu'un scan de table, mais ce n'est pas une recherche, c'est ce que vous devriez viser)