Popraw wydajność zapytań programu SQL Server w przypadku dużych tabel

85

Mam stosunkowo dużą tabelę (obecnie 2 miliony rekordów) i chciałbym wiedzieć, czy można poprawić wydajność zapytań ad-hoc. Kluczowe jest tutaj słowo ad-hoc . Dodawanie indeksów nie jest opcją (istnieją już indeksy w kolumnach, które są najczęściej zadawane).

Uruchomienie prostego zapytania w celu zwrócenia 100 ostatnio zaktualizowanych rekordów:

select top 100 * from ER101_ACCT_ORDER_DTL order by er101_upd_date_iso desc

Trwa kilka minut. Zobacz plan wykonania poniżej:

wprowadź opis obrazu tutaj

Dodatkowe szczegóły ze skanu tabeli:

wprowadź opis obrazu tutaj

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

Serwer jest dość wydajny (z pamięci 48 GB RAM, 24-rdzeniowy procesor) z systemem SQL Server 2008 R2 x64.

Aktualizacja

Znalazłem ten kod, aby utworzyć tabelę z 1 000 000 rekordów. Pomyślałem, że mógłbym wtedy uruchomić SELECT TOP 100 * FROM testEnvironment ORDER BY mailAddress DESCna kilku różnych serwerach, aby sprawdzić, czy moje prędkości dostępu do dysku na serwerze są słabe.

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;

Ale na trzech serwerach testowych zapytanie przebiegło niemal natychmiast. Czy ktoś może to wyjaśnić?

wprowadź opis obrazu tutaj

Zaktualizuj 2

Dziękuję za komentarze - proszę, nie przestawajcie ich przekazywać ... skłoniły mnie do zmiany indeksu klucza podstawowego z nieklastrowego na klastrowy z dość interesującymi (i nieoczekiwanymi?) Wynikami.

Nieklastrowy:

wprowadź opis obrazu tutaj

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

Klastrowany:

wprowadź opis obrazu tutaj

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

Jak to jest możliwe? Bez indeksu w kolumnie er101_upd_date_iso, jak można użyć skanowania indeksu klastrowego?

Zaktualizuj 3

Zgodnie z żądaniem - to jest skrypt tworzenia tabeli:

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]

Tabela ma rozmiar 2,8 GB, a rozmiar indeksu wynosi 3,9 GB.

Lee Tickett
źródło
1
Po najechaniu kursorem myszy na element planu pojawia się kilka wskazówek. Pokazują szacunkowy koszt we / wy i procesora. Najpierw zadbałbym o koszt I / O.
Grzegorz Gierlik
4
Table Scanwskazuje stertę (brak indeksu klastrowego) - więc pierwszym krokiem byłoby dodanie dobrego, szybkiego indeksu klastrowego do tabeli. Drugim krokiem może być zbadanie, czy indeks nieklastrowy na er101_upd_date_isopomógłby (i nie spowodowałby innych wad wydajności)
marc_s
1
@marc_s dzięki za to - zmieniłem indeks pk na klastrowy i spowodowało to istotną różnicę - czy możesz to dalej wyjaśnić? (spójrz na aktualizację 2)
Lee Tickett
2
Cóż, indeks klastrowy po prostu zmienia układ przechowywania tabeli. Indeks klastrowy zawiera rzeczywiste dane tabeli w węzłach na poziomie liścia - to znaczy: aby odczytać całą tabelę, SQL Server przeprowadza teraz skanowanie indeksu klastrowego (w zasadzie „skanowanie tabeli” po tabeli z indeksem klastrowym). Prawie zawsze będzie to trochę szybsze niż skanowanie tabeli na stercie (bez indeksu klastrowego). Jeśli teraz dodałeś indeks nieklastrowy do er101_upd_date_isokolumny, prawdopodobnie możesz również pozbyć się operacji „Sortuj” w swoim planie wykonania i jeszcze bardziej
przyspieszyć działanie
2
@LeeTickett, pokaż definicje tabeli i indeksu. Jest wiele czynników do rozważenia i wydaje się, że nikt o nie nie pyta (co mnie zaskakuje, ale może nie powinno). Mogę powiedzieć, że 2 miliony wierszy NIE jest dużymi i odpowiednio indeksowane tabele z ponad 200 milionami wierszy zwracają się szybciej niż to. Najprawdopodobniej indeks klastrowy (teraz, gdy go masz dzięki marc_s) jest złym wyborem, ale trudno go powiedzieć bez poznania szczegółów. NIE używaj partycjonowania, ale używaj SET STATISTICS IO ON i zaznacz Logical Reads w zakładce wiadomości. Jeśli zmiana zmniejsza odczyty logiczne, zbliżasz się.
Solomon Rutzky

Odpowiedzi:

59

Prosta odpowiedź: NIE. Nie można pomóc w zapytaniach ad hoc dotyczących tabeli 238 kolumnowej z 50% współczynnikiem wypełnienia w indeksie klastrowym.

Szczegółowa odpowiedź:

Jak już wspomniałem w innych odpowiedziach na ten temat, projektowanie indeksu to zarówno sztuka, jak i nauka i należy wziąć pod uwagę tak wiele czynników, że istnieje niewiele, jeśli w ogóle, sztywnych i szybkich reguł. Musisz wziąć pod uwagę: wielkość operacji DML vs SELECT, podsystem dyskowy, inne indeksy / wyzwalacze w tabeli, dystrybucja danych w tabeli, to zapytania korzystające z warunków SARGable WHERE i kilka innych rzeczy, których nawet nie pamiętam dobrze teraz.

Mogę powiedzieć, że nie można udzielić pomocy w przypadku pytań na ten temat bez zrozumienia samej tabeli, jej indeksów, wyzwalaczy itp. Teraz, gdy opublikowałeś definicję tabeli (wciąż czekam na indeksy, ale sama definicja tabeli wskazuje na 99% problemu) Mogę podać kilka sugestii.

Po pierwsze, jeśli definicja tabeli jest dokładna (238 kolumn, współczynnik wypełnienia 50%), możesz zignorować pozostałe odpowiedzi / porady tutaj ;-). Przepraszam, że jestem tu mniej niż polityczny, ale poważnie, to dziki pogoń bez znajomości szczegółów. A teraz, gdy widzimy definicję tabeli, staje się nieco bardziej jasne, dlaczego proste zapytanie miałoby trwać tak długo, nawet jeśli zapytania testowe (aktualizacja nr 1) były wykonywane tak szybko.

Głównym problemem tutaj (iw wielu sytuacjach o niskiej wydajności) jest złe modelowanie danych. 238 kolumn nie jest zabronione, tak jak posiadanie indeksów 999 nie jest zabronione, ale generalnie nie jest też zbyt mądre.

Zalecenia:

  1. Po pierwsze, ten stół naprawdę wymaga przebudowy. Jeśli jest to tabela hurtowni danych, być może, ale jeśli nie, to te pola naprawdę muszą zostać podzielone na kilka tabel, z których wszystkie mogą mieć tę samą PK. Będziesz mieć główną tabelę rekordów, a tabele podrzędne są po prostu zależnymi informacjami opartymi na powszechnie skojarzonych atrybutach, a PK tych tabel jest takie samo jak PK tabeli głównej, a zatem również FK do tabeli głównej. Między tabelami nadrzędnymi a wszystkimi tabelami podrzędnymi będzie relacja 1 do 1.
  2. Użycie ANSI_PADDING OFFjest niepokojące, nie wspominając o niespójności w tabeli z powodu różnych dodawanych kolumn w czasie. Nie jestem pewien, czy możesz to teraz naprawić, ale idealnie byłoby, gdybyś miał zawsze ANSI_PADDING ONlub przynajmniej miał takie samo ustawienie we wszystkich ALTER TABLEinstrukcjach.
  3. Rozważ utworzenie 2 dodatkowych grup plików: tabel i indeksów. Najlepiej nie umieszczać swoich rzeczy, PRIMARYponieważ jest to miejsce, w którym SQL SERVER przechowuje wszystkie swoje dane i metadane dotyczące twoich obiektów. Tworzysz tabelę i indeks klastrowany (ponieważ są to dane dla tabeli) [Tables]i wszystkie indeksy nieklastrowane na[Indexes]
  4. Zwiększ współczynnik wypełnienia z 50%. Ta niska liczba prawdopodobnie powoduje, że obszar indeksowania jest większy niż obszar danych. Wykonanie przebudowy indeksu spowoduje odtworzenie stron danych o maksymalnej wielkości 4 kB (z całkowitego rozmiaru strony 8 kB) używanych do przechowywania danych, dzięki czemu tabela jest rozłożona na dużym obszarze.
  5. Jeśli większość lub wszystkie zapytania mają warunek „ER101_ORG_CODE” WHERE, rozważ przeniesienie go do pierwszej kolumny indeksu klastrowego. Zakładając, że jest używany częściej niż „ER101_ORD_NBR”. Jeśli częściej używany jest „ER101_ORD_NBR”, zachowaj go. Po prostu wydaje się, zakładając, że nazwy pól oznaczają „OrganizationCode” i „OrderNumber”, że „OrgCode” jest lepszą grupą, która może zawierać wiele „OrderNumbers”.
  6. Drobny punkt, ale jeśli „ER101_ORG_CODE” ma zawsze 2 znaki, użyj CHAR(2)zamiast, VARCHAR(2)ponieważ spowoduje to zapisanie bajtu w nagłówku wiersza, który śledzi różne rozmiary i dodaje do milionów wierszy.
  7. Jak wspomnieli inni tutaj, używanie SELECT *zaszkodzi wydajności. Nie tylko dlatego, że wymaga od SQL Server zwrócenia wszystkich kolumn, a tym samym bardziej prawdopodobne jest wykonanie skanowania indeksu klastrowego niezależnie od innych indeksów, ale także przejście do definicji tabeli i przetłumaczenie *na wszystkie nazwy kolumn zajmuje SQL Serverowi trochę czasu. . Powinno być nieco szybsze określenie wszystkich 238 nazw kolumn na SELECTliście, ale to nie pomoże w problemie ze skanowaniem. Ale czy kiedykolwiek naprawdę potrzebujesz wszystkich 238 kolumn w tym samym czasie?

Powodzenia!

AKTUALIZACJA
W celu uzupełnienia pytania „jak poprawić wydajność dużej tabeli dla zapytań ad-hoc”, należy zauważyć, że chociaż nie pomoże to w tym konkretnym przypadku, JEŚLI ktoś używa SQL Server 2012 (lub nowszego kiedy nadejdzie ten czas) i JEŻELI tabela nie jest aktualizowana, można użyć indeksów magazynu kolumn. Aby uzyskać więcej informacji na temat tej nowej funkcji, spójrz tutaj: http://msdn.microsoft.com/en-us/library/gg492088.aspx (uważam, że można je było aktualizować począwszy od SQL Server 2014).

UPDATE 2
Dodatkowe uwagi:

  • Włącz kompresję indeksu klastrowanego. Ta opcja stała się dostępna w SQL Server 2008, ale jako funkcja dostępna tylko w wersji Enterprise Edition. Jednak począwszy od dodatku SP1 dla programu SQL Server 2016 , kompresja danych została udostępniona we wszystkich wersjach ! Zobacz stronę MSDN dotyczącą kompresji danych, aby uzyskać szczegółowe informacje na temat kompresji wierszy i stron.
  • Jeśli nie można użyć kompresji danych, lub jeśli nie zapewni wiele korzyści dla określonej tabeli, a następnie, jeśli masz kolumnę typu stałej długości ( INT, BIGINT, TINYINT, SMALLINT, CHAR, NCHAR, BINARY, DATETIME, SMALLDATETIME, MONEY, etc), a także ponad 50 % wierszy jest NULL, a następnie rozważ włączenie SPARSEopcji, która stała się dostępna w SQL Server 2008. Szczegółowe informacje można znaleźć na stronie MSDN w sekcji Użyj rzadkich kolumn .
Solomon Rutzky
źródło
Na punkcie 7 osobiście sądzę, że powinna ona być szybciej, aby dodać 238 nazw kolumn z metadanych niż przetworzyć je z tekstu zapytania, a następnie trzeba sprawdzić metadane i tak, aby zapewnić ich wszystkich exist.There są wystarczająco silne argumenty przeciwko *bez tej wątpliwej jednego
Martin Smith
53

Jest kilka problemów z tym zapytaniem (i dotyczy to każdego zapytania).

Brak indeksu

Brak indeksu w er101_upd_date_isokolumnie jest najważniejszy, o czym wspomniał już Oded .

Bez pasującego indeksu (którego brak mógłby spowodować skanowanie tabeli) nie ma szans na wykonanie szybkich zapytań na dużych tabelach.

Jeśli nie możesz dodać indeksów (z różnych powodów, w tym nie ma sensu tworzyć indeksu tylko dla jednego zapytania ad-hoc ), proponuję kilka obejść (które można zastosować do zapytań ad-hoc):

1. Użyj tabel tymczasowych

Utwórz tabelę tymczasową na podzbiorze (wierszach i kolumnach) danych, które Cię interesują. Tabela tymczasowa powinna być znacznie mniejsza od oryginalnej tabeli źródłowej, może być łatwo indeksowana (w razie potrzeby) i może buforować podzbiór danych, które Cię interesują.

Aby utworzyć tabelę tymczasową, możesz użyć kodu (nie testowano) takiego jak:

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

Plusy:

  • Łatwe do wykonania dla dowolnego podzbioru danych.
  • Łatwy w zarządzaniu - jest tymczasowy i jest stołowy .
  • Nie wpływa na ogólną wydajność systemu, np view.
  • Tabela tymczasowa może być indeksowana.
  • Nie musisz się tym przejmować - to tymczasowe :).

Cons:

  • To migawka danych - ale prawdopodobnie jest to wystarczające dla większości zapytań ad-hoc.

2. Wspólne wyrażenie tabeli - CTE

Osobiście często używam CTE przy zapytaniach ad-hoc - jest to bardzo pomocne przy budowaniu (i testowaniu) zapytania kawałek po kawałku.

Zobacz przykład poniżej (zapytanie zaczynające się od WITH).

Plusy:

  • Łatwe do zbudowania, zaczynając od dużego widoku, a następnie wybierając i filtrując to, czego naprawdę potrzebujesz.
  • Łatwe do przetestowania.

Cons:

  • Niektórzy ludzie nie lubią CDE - zapytania CDE wydają się być długie i trudne do zrozumienia.

3. Utwórz widoki

Podobnie jak powyżej, ale twórz widoki zamiast tabel tymczasowych (jeśli często grasz tymi samymi zapytaniami i masz wersję MS SQL obsługującą widoki indeksowane).

Możesz tworzyć widoki lub indeksowane widoki podzbioru danych, które Cię interesują, i uruchamiać zapytania na widoku - które powinny zawierać tylko interesujący podzbiór danych znacznie mniejszy niż cała tabela.

Plusy:

  • Proste do zrobienia.
  • Jest na bieżąco z danymi źródłowymi.

Cons:

  • Możliwe tylko dla określonego podzbioru danych.
  • Może być nieefektywne w przypadku dużych tabel z wysokim współczynnikiem aktualizacji.
  • Nie takie łatwe w zarządzaniu.
  • Może wpływać na ogólną wydajność systemu.
  • Nie jestem pewien, czy indeksowane widoki są dostępne w każdej wersji MS SQL.

Wybór wszystkich kolumn

Uruchamianie zapytania gwiazdkowego ( SELECT * FROM) na dużym stole nie jest dobre ...

Jeśli masz duże kolumny (na przykład długie łańcuchy), odczytanie ich z dysku i przekazanie ich przez sieć zajmuje dużo czasu.

Spróbowałbym zastąpić *nazwami kolumn, których naprawdę potrzebujesz.

Lub, jeśli potrzebujesz wszystkich kolumn, spróbuj przepisać zapytanie na coś takiego (używając wspólnego wyrażenia danych ):

;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 

Brudne czyta

Ostatnią rzeczą, która może przyspieszyć zapytanie ad-hoc, jest zezwolenie na brudne odczyty ze wskazówką dotyczącą tabeliWITH (NOLOCK) .

Zamiast podpowiedzi możesz ustawić poziom izolacji transakcji na odczyt niezatwierdzonych:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

lub ustaw odpowiednie ustawienie SQL Management Studio.

Zakładam, że dla zapytań ad-hoc brudne odczyty są wystarczająco dobre.

Grzegorz Gierlik
źródło
2
+1 za SELECT *- wymusza na SQL Server użycie indeksu klastrowego. A przynajmniej powinno. Nie widzę żadnego prawdziwego powodu dla nieklastrowego wskaźnika pokrycia ... obejmującego całą tabelę :)
ta.speot.is
4
Ta odpowiedź dotyczy tylko poprawy szybkości przykładowego zapytania, a NIE pytania, które brzmi: „Czy można poprawić wydajność zapytań ad-hoc”
Phil.
CDE to teraz CTE (Common Table Expression)
sqluser
12

Otrzymujesz tam skanowanie tabeli , co oznacza, że nie masz zdefiniowanego indeksuer101_upd_date_iso lub jeśli ta kolumna jest częścią istniejącego indeksu, indeks nie może być użyty (prawdopodobnie nie jest to główna kolumna indeksująca).

Dodanie brakujących indeksów poprawi wydajność bez końca.

istnieją już indeksy w kolumnach, które są najczęściej zadawane

Nie oznacza to, że są one używane w tym zapytaniu (i prawdopodobnie nie są).

Proponuję przeczytać Gail Shaw: Finding the Causes of Sied Performance in SQL Server by Gail Shaw, część 1 i część 2 .

Oded
źródło
Chodzi mi o to, że nie jest to jedna z najczęściej
pytanych
1
@LeeTickett - A jednak jest to jedyna kolumna, do której można ewentualnie dodać indeks, aby poprawić wydajność tego zapytania.
Oded
2
Nie ma czegoś takiego jak zoptymalizowane, niezindeksowane wyszukiwania. Używasz indeksu lub pełnego skanowania tabeli. Jeśli nie chcesz pełnych skanów tabeli, potrzebujesz indeksów. W zależności od Twojego profilu użytkowania samo dodanie indeksów może być wystarczająco tanie. Aha, i typ danych kolumny też ma znaczenie. Jeśli twój er101_upd_date_isojest ogromny varchar lub int, znacząco zmieni wydajność.
Cylindryczny
dzięki. dodałem komentarz do pytania. Rozumiem, że jest mało prawdopodobne, żebym mógł zoptymalizować zapytanie - ale wyobrażam sobie, że mogą istnieć sposoby na poprawę wydajności zapytań ad-hoc
Lee Tickett
Jestem uczniem, skąd wiemy, która kolumna wymaga indeksowania?
Wirus
7

Pytanie konkretnie stwierdza, że ​​wydajność należy poprawić w przypadku zapytań ad-hoc i że nie można dodawać indeksów. Biorąc to pod uwagę, co można zrobić, aby poprawić wydajność na dowolnym stole?

Ponieważ rozważamy zapytania ad hoc, klauzula WHERE i klauzula ORDER BY mogą zawierać dowolną kombinację kolumn. Oznacza to, że prawie niezależnie od tego, jakie indeksy zostaną umieszczone w tabeli, pojawią się zapytania wymagające skanowania tabeli, jak widać powyżej w planie zapytań dla słabo wykonującego się zapytania.

Biorąc to pod uwagę, załóżmy, że w tabeli nie ma żadnych indeksów poza indeksem klastrowym na kluczu podstawowym. Zastanówmy się teraz, jakie mamy opcje, aby zmaksymalizować wydajność.

  • Zdefragmentuj tabelę

    Dopóki mamy indeks klastrowy, możemy zdefragmentować tabelę za pomocą DBCC INDEXDEFRAG (przestarzałe) lub najlepiej ALTER INDEX . Zminimalizuje to liczbę odczytów dysku wymaganych do skanowania tabeli i poprawi szybkość.

  • Używaj najszybszych możliwych dysków. Nie mówisz, jakich dysków używasz, ale czy możesz używać dysków SSD.

  • Zoptymalizuj tempdb. Umieść tempdb na najszybszych możliwych dyskach, znowu na dyskach SSD. Zobacz ten artykuł SO i ten artykuł RedGate .

  • Jak stwierdzono w innych odpowiedziach, użycie bardziej wybiórczego zapytania zwróci mniej danych i dlatego powinno być szybsze.

Zastanówmy się teraz, co możemy zrobić, jeśli wolno nam dodawać indeksy.

Gdybyśmy nie mówili o zapytaniach ad hoc, dodalibyśmy indeksy specjalnie dla ograniczonego zestawu zapytań uruchamianych względem tabeli. Ponieważ omawiamy zapytania ad hoc , co można zrobić, aby w większości przypadków poprawić szybkość ?

  • Dodaj indeks jednej kolumny do każdej kolumny. Powinno to dać programowi SQL Server przynajmniej coś do pracy, aby poprawić szybkość większości zapytań, ale nie będzie optymalne.
  • Dodaj określone indeksy dla najczęściej używanych zapytań, aby były zoptymalizowane.
  • Dodaj dodatkowe, określone indeksy zgodnie z wymaganiami, monitorując zapytania o niskiej wydajności.

Edytować

Przeprowadziłem kilka testów na „dużej” tabeli zawierającej 22 miliony wierszy. Moja tabela ma tylko sześć kolumn, ale zawiera 4 GB danych. Moja maszyna to przyzwoity komputer stacjonarny z 8Gb RAM i czterordzeniowym procesorem oraz pojedynczy dysk SSD Agility 3.

Usunąłem wszystkie indeksy oprócz klucza podstawowego w kolumnie Id.

Zapytanie podobne do problemu zadanego w pytaniu zajmuje 5 sekund, jeśli serwer SQL jest restartowany jako pierwszy i 3 sekundy później. Doradca strojenia bazy danych oczywiście zaleca dodanie indeksu w celu ulepszenia tego zapytania, z szacowaną poprawą o> 99%. Dodanie indeksu powoduje, że czas zapytania wynosi efektywnie zero.

Interesujące jest również to, że mój plan zapytań jest identyczny z twoim (ze skanowaniem indeksu klastrowego), ale skanowanie indeksu stanowi 9% kosztu zapytania, a sortowanie pozostałe 91%. Mogę tylko założyć, że twoja tabela zawiera ogromną ilość danych i / lub twoje dyski są bardzo wolne lub znajdują się w bardzo wolnym połączeniu sieciowym.

Phil
źródło
2

Nawet jeśli masz indeksy w niektórych kolumnach, które są używane w niektórych zapytaniach, fakt, że zapytanie „ad-hoc” powoduje skanowanie tabeli, pokazuje, że nie masz wystarczających indeksów, aby umożliwić sprawne wykonanie tego zapytania.

W szczególności w przypadku zakresów dat trudno jest dodać dobre indeksy.

Po prostu patrząc na zapytanie, baza danych musi posortować wszystkie rekordy według wybranej kolumny, aby móc zwrócić pierwsze n rekordów.

Czy baza danych wykonuje również pełne skanowanie tabeli bez klauzuli order by? Czy tabela ma klucz podstawowy - bez PK db będzie musiał pracować ciężej, aby wykonać sortowanie?

Foamdino
źródło
Na stole znajduje się klucz podstawowy. Skan tabeli pojawia się również w planie wykonania podczas prostego wykonywaniaselect top 100 * from ER101_ACCT_ORDER_DTL
Lee Tickett,
2

Jak to jest możliwe? Bez indeksu w kolumnie er101_upd_date_iso, jak można użyć skanowania indeksu klastrowego?

Indeks to drzewo B, w którym każdy węzeł liścia wskazuje na „zbiór wierszy” (nazywany „stroną” w wewnętrznej terminologii SQL). To znaczy, gdy indeks jest indeksem nieklastrowym.

Indeks klastrowy to szczególny przypadek, w którym węzły liści mają „kilka wierszy” (zamiast wskazywać je). dlatego...

1) W tabeli może znajdować się tylko jeden indeks klastrowy.

oznacza to również, że cała tabela jest przechowywana jako indeks klastrowy, dlatego zacząłeś widzieć skanowanie indeksu zamiast skanowania tabeli.

2) Operacja korzystająca z indeksu klastrowego jest zazwyczaj szybsza niż w przypadku indeksu nieklastrowego

Przeczytaj więcej na http://msdn.microsoft.com/en-us/library/ms177443.aspx

W przypadku problemu, który masz, naprawdę powinieneś rozważyć dodanie tej kolumny do indeksu, jak powiedziałeś, dodanie nowego indeksu (lub kolumny do istniejącego indeksu) zwiększa koszty INSERT / UPDATE. Ale może być możliwe usunięcie niewykorzystanego indeksu (lub kolumny z istniejącego indeksu) i zastąpienie go „er101_upd_date_iso”.

Jeśli zmiany indeksu nie są możliwe, zalecam dodanie statystyk do kolumny, może to przyspieszyć, gdy kolumny mają pewną korelację z indeksowanymi kolumnami

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

BTW, uzyskasz znacznie większą pomoc, jeśli możesz opublikować schemat tabeli ER101_ACCT_ORDER_DTL. a także istniejące indeksy ... prawdopodobnie można by przepisać zapytanie, aby wykorzystać niektóre z nich.

shankar_pratap
źródło
+1 za odpowiedź. Jeden komentarz jednak, indeksy klastrowe nie zawsze są szybsze, niż można by odczytać (można to źle zrozumieć) z Twojej odpowiedzi.
Gisli
Myślę, że rozumiem różnicę między indeksem klastrowym / nieklastrowym, ale nadal nie widzę, jak można poprawić kwerendę kolumny, która nie jest częścią indeksu klastrowego, dzięki indeksowi klastrowemu w innych kolumnach?
Lee Tickett
W SQL Server indeks klastrowany zawiera wszystkie kolumny. Indeks klastrowy decyduje o sposobie przechowywania danych na dysku. Trochę trudno mi to wyjaśnić, ale jeśli myślisz o indeksach jako o drzewie, indeks nieklastrowy jest drzewem, a dolne liście zawierają informacje, które zdefiniowałeś jako indeks. W przypadku indeksu klastrowego dolne liście zawierają wszystkie kolumny w tabeli. Jest to zgodne z projektem programu SQL Server.
Gisli
Rozumiem. Ale pomyślałem, że gałęzie są oparte na kolumnach w indeksie klastrowym. Więc jeśli kolumna, o którą pytam, nie jest indeksem klastrowym, z pewnością każda gałąź / liść musi zostać przeskanowana?
Lee Tickett
1
Nie rozumiem tego. Moim najlepszym przypuszczeniem jest to, że gdy masz indeks nieklastrowy, został on przeskanowany, co spowodowało wiele losowych operacji we / wy. Kiedy tworzyłeś indeks klastrowy, pozbyłeś się tych losowych I / O? Ale to przypuszczenie, nie mogę znaleźć innego powodu takiego zachowania, ale nie jestem ekspertem.
Gisli
1

Jednym z powodów, dla których test 1M przebiegał szybciej, jest prawdopodobnie to, że tabele tymczasowe znajdują się w całości w pamięci i zostaną przeniesione na dysk tylko wtedy, gdy serwer odczuje presję pamięci. Możesz ponownie utworzyć zapytanie, aby usunąć zamówienie, dodać dobry indeks klastrowy i indeksy obejmujące, jak wspomniano wcześniej, lub zapytać DMV, aby sprawdzić ciśnienie we / wy, aby sprawdzić, czy jest związane ze sprzętem.

-- 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
źródło
mam nadzieję, że cała moja baza danych będzie w pamięci. czy istnieje sposób, aby to sprawdzić lub powiedzieć sql, które tabele mają przechowywać w pamięci? Byłem nieobecny przez kilka dni, ale kiedy wrócę, spróbuję
zapytać
Średnia liczba zadań i średnia liczba oczekujących dyskietek osiągnęły szczyt na poziomie 4. Nadal jestem ciekawy, czy mogę wymusić umieszczenie bazy danych w pamięci RAM.
Lee Tickett
0

Wiem, że powiedziałeś, że dodanie indeksów nie jest opcją, ale byłaby to jedyna opcja, aby wyeliminować skanowanie tabeli, którą masz. Podczas skanowania SQL Server odczytuje wszystkie 2 miliony wierszy w tabeli, aby spełnić zapytanie.

ten artykuł zawiera więcej informacji, ale pamiętaj: Szukaj = dobrze, Skanuj = źle.

Po drugie, czy nie możesz wyeliminować funkcji select * i wybrać tylko potrzebne kolumny? Po trzecie, brak klauzuli „gdzie”? Nawet jeśli masz indeks, ponieważ czytasz wszystko, co najlepsze, co otrzymasz, to skanowanie indeksu (które jest lepsze niż skanowanie tabeli, ale nie jest to wyszukiwanie, do czego powinieneś dążyć)

Diego
źródło
Nie jest prawdą, że Seek jest zawsze lepszy niż Scan. Czasami skanowanie jest bardziej wydajne. Gdyby tak nie było, M $ nie zawierałoby wskazówki dotyczącej zapytania FORCESCAN, począwszy od SQL Server 2008 R2. Zobacz tutaj, aby uzyskać więcej informacji: msdn.microsoft.com/en-us/library/ms181714(v=sql.105).aspx, a nawet tutaj, jeśli ktoś chce wymusić skanowanie (trzecia odpowiedź Adama Hainesa zawiera dobre informacje): społeczność .msdn.microsoft.com / Forums / pl-pl / transactsql / thread /…
Solomon Rutzky Kwietnia
1
Po pierwsze, wyszukiwania są dobre w przypadku zapytań typu punktowego. Po drugie, skanowanie jest dobre w przypadku zapytań o zakres, w przypadku których trzeba pobrać wiele danych. Systemy OLAP nie działałyby dobrze bez skanowań. Systemy OLTP nie działałyby dobrze bez wyszukiwań. Wszystko ma swoje miejsce w wielkim planie ...
darlove
0

Wiem, że minęło sporo czasu od początku ... We wszystkich tych odpowiedziach jest dużo mądrości. Dobre indeksowanie to pierwsza rzecz przy próbie ulepszenia zapytania. Cóż, prawie pierwszy. Przede wszystkim (że tak powiem) wprowadzanie zmian w kodzie, aby był wydajny. Tak więc, po tym wszystkim, co zostało powiedziane i zrobione, jeśli masz zapytanie bez WHERE lub gdy warunek WHERE nie jest wystarczająco selektywny, istnieje tylko jeden sposób na uzyskanie danych: TABLE SCAN (INDEX SCAN). Jeśli potrzebne są wszystkie kolumny z tabeli, to zostanie użyte TABLE SCAN - nie ma co do tego wątpliwości. Może to być skanowanie sterty lub skanowanie indeksu klastrowego, w zależności od typu organizacji danych. Jedynym ostatnim sposobem na przyspieszenie (jeśli to w ogóle możliwe) jest upewnienie się, że do skanowania zostanie użytych jak najwięcej rdzeni: OPTION (MAXDOP 0). Oczywiście pomijam temat przechowywania

kochanie
źródło