Покращення продуктивності запитів SQL Server у великих таблицях


85

У мене є відносно велика таблиця (на даний момент 2 мільйони записів), і я хотів би знати, чи можливо покращити ефективність для спеціальних запитів. Слово ad hoc тут є ключовим. Додавання індексів не є можливим (у стовпцях вже є індекси, які найчастіше запитуються).

Запуск простого запиту для повернення 100 останніх оновлених записів:

select top 100 * from ER101_ACCT_ORDER_DTL order by er101_upd_date_iso desc

Триває кілька хвилин. Дивіться план виконання нижче:

введіть тут опис зображення

Додаткові деталі зі сканування таблиці:

введіть тут опис зображення

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

Сервер досить потужний (із 48 ГБ оперативної пам'яті, 24-ядерний процесор), що працює на сервері sql 2008 r2 x64.

Оновлення

Я знайшов цей код для створення таблиці з 1 000 000 записів. Я думав, що потім зможу запустити SELECT TOP 100 * FROM testEnvironment ORDER BY mailAddress DESCна декількох різних серверах, щоб з’ясувати, чи низька швидкість доступу до диска на сервері.

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;

Але на трьох тестових серверах запит виконувався майже миттєво. Хто-небудь може це пояснити?

введіть тут опис зображення

Оновлення 2

Дякую за коментарі - будь ласка, продовжуйте їх надходити ... вони змусили мене спробувати змінити індекс первинного ключа з некластеризованого на кластеризований з досить цікавими (і несподіваними?) Результатами.

Некластеризовані:

введіть тут опис зображення

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

Кластеризовані:

введіть тут опис зображення

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

Як це можливо? Без індексу в стовпці er101_upd_date_iso, як можна використовувати кластерне сканування індексу?

Оновлення 3

За запитом - це сценарій створення таблиці:

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]

Розмір таблиці - 2,8 ГБ, розмір індексу - 3,9 ГБ.


1
Існує невелика підказка, коли наводити курсор миші на елемент плану. Вони показують приблизні витрати вводу-виводу та процесора. Спочатку я б подбав про вартість вводу-виводу.
Grzegorz Gierlik

4
Table Scanвказує купу (відсутність кластерного індексу) - отже, першим кроком буде додавання хорошого, швидкого кластерного індексу до вашої таблиці. Другим кроком може бути дослідження, чи er101_upd_date_isoдопоможе некластерований індекс on (і не спричинить інших недоліків продуктивності)
marc_s

1
@marc_s дякую за це - я змінив індекс pk на кластеризований, і це суттєво змінило - чи можете ви пояснити це далі? (див. оновлення 2)
Лі Тікетт

2
Ну, кластерний індекс просто змінює макет сховища таблиці. Кластеризований індекс містить фактичні дані таблиці у вузлах рівня листа - тобто: щоб прочитати всю таблицю, SQL Server тепер виконує сканування кластерного індексу (в основному "сканування таблиці" над таблицею з кластеризованим індексом). Це майже завжди буде трохи швидше, ніж виконувати сканування таблиці в купі (без кластерного індексу). Якщо ви зараз додали некластеризований індекс до er101_upd_date_isoстовпця, ви, мабуть, також можете позбутися операції "Сортування" у своєму плані виконання і ще більше
пришвидшити

2
@LeeTickett, будь ласка, покажіть визначення таблиць та покажчиків. Є багато факторів, які слід враховувати, і, здається, ніхто про них не просить (що мене дивує, але, можливо, не повинно). Я можу сказати вам, що 2 мільйони рядків - це НЕ великі, і правильно проіндексовані таблиці з 200 мільйонами + рядків повертаються швидше, ніж це. Швидше за все кластерний індекс (тепер, коли у вас є такий завдяки marc_s) - поганий вибір, але важко сказати, не бачачи специфіки. НЕ використовуйте розділення, а ВИКОРИСТАЙТЕ ВСТАНОВЛЕННЯ СТАТИСТИКИ І перевірте логічне читання на вкладці повідомлень. Якщо зміна зменшує логічні показники, ви стаєте ближчими.
Соломон Руцкі,

Відповіді:


59

Проста відповідь: НІ. Ви не можете допомогти спеціальним запитам у таблиці стовпців 238 із коефіцієнтом заповнення 50% в кластерному індексі.

Детальна відповідь:

Як я вже зазначав в інших відповідях на цю тему, дизайн індексу - це і мистецтво, і наука, і існує так багато факторів, щоб врахувати, що існує мало жорстких правил, якщо вони взагалі існують. Вам потрібно врахувати: обсяг DML-операцій проти SELECT, дискова підсистема, інші індекси / тригери в таблиці, розподіл даних у таблиці - це запити з використанням умов SARGable WHERE та кілька інших речей, які я навіть не пам’ятаю правильно зараз.

Я можу сказати, що жодна допомога не може бути надана для питань на цю тему без розуміння самої таблиці, її індексів, тригерів тощо. Тепер, коли ви опублікували визначення таблиці (все ще чекаємо на індекси, але лише визначення таблиці вказує на 99% випуску) Я можу запропонувати кілька пропозицій.

По-перше, якщо визначення таблиці є точним (238 стовпців, коефіцієнт заповнення 50%), ви можете майже проігнорувати решту відповідей / порад тут ;-). Вибачте, що я тут менш політичний, але серйозно, це дика погоня за гусями, не знаючи особливостей. І тепер, коли ми бачимо визначення таблиці, стає дещо зрозумілішим, чому простий запит зайняв би так багато часу, навіть коли тестові запити (Оновлення №1) працювали так швидко.

Основною проблемою тут (і у багатьох ситуаціях з низькою продуктивністю) є погане моделювання даних. 238 стовпців не заборонено, як і заборонено мати 999 індексів, але це, як правило, не дуже розумно.

Рекомендації:

  1. По-перше, цю таблицю дійсно потрібно переробити. Якщо це таблиця сховища даних, то, можливо, але якщо ні, то ці поля дійсно потрібно розбити на кілька таблиць, які всі можуть мати однаковий PK. У вас була б головна таблиця записів, а дочірні таблиці - це лише залежна інформація на основі загально пов’язаних атрибутів, і ПК цих таблиць такий самий, як і ПК головної таблиці, а отже, і FK до головної таблиці. Між головним та всіма дочірніми таблицями буде встановлено взаємозв'язок 1: 1.
  2. Використання ANSI_PADDING OFFзаважає, не кажучи вже про непослідовність у таблиці через різні доповнення стовпців з часом. Не впевнений, чи можете ви це виправити зараз, але в ідеалі ви завжди мали б ANSI_PADDING ON, або, принаймні, мали б однакові налаштування у всіх ALTER TABLEтвердженнях.
  3. Спробуйте створити 2 додаткові групи файлів: таблиці та індекси. Краще не розміщувати свої речі, PRIMARYоскільки саме там SQL SERVER зберігає всі свої дані та метадані про ваші об’єкти. Ви створюєте свою таблицю та кластерний індекс (оскільки це дані для таблиці) на [Tables]та всі некластеризовані індекси на[Indexes]
  4. Збільште коефіцієнт заповнення з 50%. Це низьке число, ймовірно, чому ваш індексний простір більше, ніж ваш простір даних. Виконуючи переробку індексу, ви зможете відтворити сторінки даних із максимум 4k (із загального розміру сторінки 8k), що використовується для ваших даних, тому ваша таблиця буде розподілена на широкій ділянці.
  5. Якщо більшість або всі запити мають умову "ER101_ORG_CODE" WHERE, розгляньте можливість переміщення цього до першого стовпця кластерного індексу. Припускаючи, що він використовується частіше, ніж "ER101_ORD_NBR". Якщо "ER101_ORD_NBR" використовується частіше, залиште його. Просто здається, якщо припустити, що імена полів означають "OrganizationCode" та "OrderNumber", що "OrgCode" є кращою групою, яка може мати в собі декілька "OrderNumbers".
  6. Другорядна точка, але якщо "ER101_ORG_CODE" завжди має 2 символи, тоді використовуйте CHAR(2)замість, VARCHAR(2)оскільки це збереже байт у заголовку рядка, який відстежує розміри змінної ширини та складає понад мільйони рядків.
  7. Як зазначали інші тут, використання SELECT *зашкодить продуктивності. Мало того, що SQL Server вимагає повернення всіх стовпців і, отже, більша ймовірність кластерного сканування індексу, незалежно від інших ваших індексів, але також потрібен час SQL Server для переходу до визначення таблиці та перекладу *на всі імена стовпців . Це має бути трохи швидше, щоб вказати всі 238 імен стовпців у SELECTсписку, хоча це не допоможе проблемі сканування. Але чи справді вам коли-небудь потрібні всі 238 стовпців одночасно?

Удачі!

ОНОВЛЕННЯ
Для повноти запитання "як поліпшити продуктивність великої таблиці для спеціальних запитів", слід зазначити, що, хоча це не допоможе у цьому конкретному випадку, ЯКЩО хтось використовує SQL Server 2012 (або новішу версію) коли цей час настане) і якщо таблиця не оновлюється, то використання індексів Columnstore - це варіант. Детальніше про цю нову функцію дивіться тут: http://msdn.microsoft.com/en-us/library/gg492088.aspx (я вважаю, що вони були оновлені, починаючи з SQL Server 2014).

ОНОВЛЕННЯ 2
Додатковими міркуваннями є:

  • Увімкнути стиснення для кластерного індексу. Цей параметр став доступним у SQL Server 2008, але як функція лише для корпоративного видання. Однак, станом на SQL Server 2016 з пакетом оновлень 1 , стиснення даних стало доступним у всіх виданнях ! Детальніше про стиснення рядків та сторінок див. На сторінці MSDN щодо стиснення даних .
  • Якщо ви не можете використовувати стиснення даних, або якщо він не забезпечить багато користі для конкретної таблиці, а потім , якщо у вас є стовпець типу фіксованою довжини ( INT, BIGINT, TINYINT, SMALLINT, CHAR, NCHAR, BINARY, DATETIME, SMALLDATETIME, MONEYі т.д.) , а також більш ніж 50 % рядків NULL, то розгляньте можливість увімкнення SPARSEопції, яка стала доступною в SQL Server 2008. Детальніше див. сторінку MSDN щодо Використання розріджених стовпців .

Щодо пункту 7, я вважаю, що швидше додати 238 назв стовпців з метаданих, ніж аналізувати їх із тексту запиту, а потім доведеться перевірити метадані, щоб переконатися, що всі вони існують. Є досить вагомі аргументи проти *без цього сумнівного
Мартін Сміт,

53

З цим запитом є кілька проблем (і це стосується кожного запиту).

Відсутність індексу

Відсутність індексу в er101_upd_date_isoколонці - це найважливіше, про що вже говорив Одед .

Без відповідного індексу (відсутність якого може спричинити сканування таблиці) немає шансів швидко запускати запити на великих таблицях.

Якщо ви не можете додати індекси (з різних причин, у тому числі немає сенсу створювати індекс лише для одного спеціального запиту ), я б запропонував кілька обхідних шляхів (які можна використовувати для спеціальних запитів):

1. Використовуйте тимчасові таблиці

Створіть тимчасову таблицю на підмножині (рядки та стовпці) даних, які вас цікавлять. Тимчасова таблиця повинна бути набагато меншою від вихідної таблиці джерела, може бути легко проіндексована (за потреби) та може кешувати підмножину даних, які вас цікавлять.

Для створення тимчасової таблиці ви можете використовувати код (не перевірений), наприклад:

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

Плюси:

  • Це легко зробити для будь-якої підмножини даних.
  • Легко в управлінні - це тимчасово і це стіл .
  • Не впливає на загальну продуктивність системи, як view.
  • Тимчасову таблицю можна проіндексувати.
  • Вам не потрібно про це дбати - це тимчасово :).

Мінуси:

  • Це моментальний знімок даних - але, мабуть, це достатньо для більшості спеціальних запитів.

2. Загальний вираз таблиці - CTE

Особисто я багато використовую CTE для спеціальних запитів - це дуже допомагає при побудові (та тестуванні) запиту поштучно.

Див. Приклад нижче (запит починається з WITH).

Плюси:

  • Легко будувати, починаючи з великого перегляду, а потім вибирати та фільтрувати те, що дійсно потрібно.
  • Легко перевірити.

Мінуси:

  • Деякі люди не люблять CDE - запити CDE здаються довгими та важкими для розуміння.

3. Створюйте подання

Подібно до наведеного вище, але створюйте подання замість тимчасових таблиць (якщо ви часто граєте з однаковими запитами і у вас є версія MS SQL, яка підтримує індексовані подання.

Ви можете створювати подання або індексовані подання для підмножини даних, які вас цікавлять, і запускати запити щодо подання - які повинні містити лише цікаві підмножини даних, набагато менші за всю таблицю.

Плюси:

  • Це легко зробити.
  • Він в курсі вихідних даних.

Мінуси:

  • Можливо лише для визначеної підмножини даних.
  • Може бути неефективним для великих таблиць із високою швидкістю оновлень.
  • Не так просто в управлінні.
  • Може вплинути на загальну продуктивність системи.
  • Я не впевнений, що індексовані подання доступні у кожній версії MS SQL.

Виділення всіх стовпців

Запуск запиту зірки ( SELECT * FROM) на великому столі - це не дуже добре ...

Якщо у вас великі стовпці (як довгі рядки), потрібно багато часу, щоб прочитати їх з диска та пройти по мережі.

Я спробував би замінити *іменами стовпців, які вам дійсно потрібні.

Або, якщо вам потрібні всі стовпці, спробуйте переписати запит на щось на зразок (використовуючи загальний вираз даних ):

;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 

Брудні читає

Останнє, що може пришвидшити спеціальний запит, - це дозволити брудні читання з підказкою таблиціWITH (NOLOCK) .

Замість підказки ви можете встановити рівень ізоляції транзакцій для читання незавершеного:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

або встановіть належні налаштування SQL Management Studio.

Я вважаю, що для спеціальних запитів брудне читання досить добре.


2
+1 for SELECT *- це змушує SQL Server використовувати кластерний індекс. Принаймні, так і повинно. Я не бачу жодної реальної причини для
некластерного

4
Ця відповідь стосується лише підвищення швидкості прикладу запиту, НЕ питання, яке полягає в тому, "чи можна покращити ефективність для спеціальних запитів"
Філ

CDE тепер є CTE (Common Table Expression)
sqluser

12

Ви отримуєте там сканування таблиці , це означає, що у вас не визначено індексer101_upd_date_iso , або якщо цей стовпець є частиною існуючого індексу, індекс не можна використовувати (можливо, це не основний стовпець індексатора).

Додавання відсутніх індексів допоможе підвищити ефективність роботи.

у стовпцях вже є індекси, які запитуються найчастіше

Це не означає, що вони використовуються в цьому запиті (і, мабуть, ні).

Я пропоную прочитати Знаходити причини низької продуктивності в SQL Server Гейл Шоу, частина 1 і частина 2 .


моя думка полягає в тому, що це не одна із колон, яку запитують найчастіше :)
Lee Tickett

1
@LeeTickett - І все ж, це єдиний стовпець, до якого ви можете додати індекс, щоб покращити ефективність цього запиту.
Отримано

2
Немає такого поняття, як оптимізовані неіндексовані пошуки. Ви використовуєте або індекс, або повне сканування таблиці. Якщо ви не хочете повного сканування таблиць, вам потрібні індекси. Залежно від вашого профілю використання, може бути досить дешево, щоб просто додавати індекси. Ох, і тип даних стовпця теж має значення. Якщо ваш er101_upd_date_isoвеличезний varchar або int, це помітно змінить продуктивність.
Cylindric

Дякую. я додав коментар до запитання. я ціную, що навряд чи я зможу оптимізувати запит, але я думаю, що можуть існувати способи покращення ефективності спеціальних запитів
Лі Тікетт,

Я навчаюсь, звідки ми знаємо, який стовпець потребує індексації?
Вірус

7

Питання конкретно зазначає, що продуктивність повинна бути покращена для спеціальних запитів, і що індекси не можна додавати. Отже, приймаючи це за номінальну вартість, що можна зробити для підвищення продуктивності на будь-якому столі?

Оскільки ми розглядаємо спеціальні запити, речення WHERE та речення ORDER BY можуть містити будь-яку комбінацію стовпців. Це означає, що майже незалежно від того, які індекси розміщені в таблиці, існуватимуть деякі запити, які потребують сканування таблиці, як це видно вище в плані запиту погано виконуваного запиту.

Беручи це до уваги, припустимо, що в таблиці взагалі немає індексів, крім кластерного індексу первинного ключа. А тепер давайте розглянемо, які варіанти ми маємо для максимізації продуктивності.

  • Дефрагментуйте таблицю

    Поки ми маємо кластерний індекс, ми можемо дефрагментувати таблицю за допомогою DBCC INDEXDEFRAG (застарілого) або, бажано, ALTER INDEX . Це дозволить мінімізувати кількість читань дисків, необхідних для сканування таблиці, та покращить швидкість.

  • Використовуйте максимально швидкі диски. Ви не говорите, які диски ви використовуєте, але якщо ви можете використовувати твердотільні накопичувачі.

  • Оптимізуйте tempdb. Помістіть tempdb на максимально швидкі диски, знову ж твердотільні накопичувачі. Див. Цю статтю SO та цю статтю RedGate .

  • Як зазначено в інших відповідях, використання більш вибіркового запиту поверне менше даних, а отже, має бути швидшим.

А тепер давайте розглянемо, що ми можемо зробити, якщо нам дозволено додавати індекси.

Якби ми не говорили про спеціальні запити, тоді ми додали б індекси спеціально для обмеженого набору запитів, що виконуються щодо таблиці. Оскільки ми обговорюємо спеціальні запити, що можна зробити для підвищення швидкості більшу частину часу?

  • Додайте один індекс стовпця до кожного стовпця. Це має дати SQL Server щонайменше з чим працювати, щоб покращити швидкість для більшості запитів, але не буде оптимальним.
  • Додайте конкретні індекси для найпоширеніших запитів, щоб вони були оптимізовані.
  • Додайте додаткові конкретні індекси відповідно до вимог моніторингу запитів з низькою ефективністю.

Редагувати

Я провів кілька тестів на "великій" таблиці з 22 мільйонами рядків. У моїй таблиці лише шість стовпців, але вона містить 4 Гб даних. Моя машина - це поважний робочий стіл з 8 Гб оперативної пам’яті та чотирьохядерним процесором і має один твердотільний накопичувач Agility 3.

Я видалив усі індекси, крім первинного ключа в стовпці Id.

Подібний запит до проблемного, заданого у питанні, займає 5 секунд, якщо SQL-сервер перезавантажується першим і 3 секунди згодом. Радник з налаштування бази даних, очевидно, рекомендує додати індекс, щоб покращити цей запит, з оціночним поліпшенням> 99%. Додавання індексу призводить до фактично нульового часу запиту.

Що також цікаво, так це те, що мій план запитів ідентичний вашому (із кластерним скануванням індексу), але сканування індексу становить 9% вартості запиту, а сортування - решту 91%. Я можу лише припустити, що ваша таблиця містить величезну кількість даних та / або ваші диски дуже повільні або розміщені через дуже повільне підключення до мережі.


2

Навіть якщо у вас є індекси в деяких стовпцях, які використовуються в деяких запитах, той факт, що ваш "спеціальний" запит викликає сканування таблиці, свідчить про те, що у вас недостатньо індексів, щоб цей запит міг ефективно виконуватися.

Для діапазонів дат, зокрема, важко додати хороші показники.

Просто дивлячись на ваш запит, db повинен відсортувати всі записи за вибраним стовпцем, щоб мати можливість повернути перші n записів.

Чи виконує db також повне сканування таблиці без пункту порядку? Чи є в таблиці первинний ключ - без ПК, db доведеться більше працювати, щоб виконати сортування?


На столі є первинний ключ. Сканування таблиці також відображається в плані виконання під час простого виконанняselect top 100 * from ER101_ACCT_ORDER_DTL
Лі Тікетт,

2

Як це можливо? Без індексу в стовпці er101_upd_date_iso, як можна використовувати кластерне сканування індексу?

Індекс - це B-дерево, де кожен вузол листа вказує на "купу рядків" (у внутрішній термінології SQL називається "Сторінка"), тобто коли індекс є некластеризованим індексом.

Кластерний індекс - це особливий випадок, коли листові вузли мають "купу рядків" (а не вказують на них). ось чому...

1) У таблиці може бути лише один кластерний індекс.

це також означає, що вся таблиця зберігається як кластерний індекс, саме тому ви почали бачити сканування індексу, а не сканування таблиці.

2) Операція, яка використовує кластерний індекс, як правило, швидша, ніж некластеризований індекс

Детальніше читайте за адресою http://msdn.microsoft.com/en-us/library/ms177443.aspx

Що стосується вашої проблеми, вам дійсно слід подумати про додавання цього стовпця до індексу, оскільки ви сказали, що додавання нового індексу (або стовпця до існуючого індексу) збільшує витрати на ВСТАВКУ / ОНОВЛЕННЯ. Але можливо можливо видалити якийсь недостатньо використаний індекс (або стовпець із існуючого індексу), щоб замінити на 'er101_upd_date_iso'.

Якщо зміни індексу неможливі, я рекомендую додати статистику до стовпця, це може пришвидшити ситуацію, коли стовпці мають певну кореляцію з індексованими стовпцями

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

До речі, ви отримаєте набагато більше допомоги, якщо зможете опублікувати схему таблиці ER101_ACCT_ORDER_DTL. та існуючі індекси теж ..., можливо, запит можна переписати, щоб використати деякі з них.


+1 за відповідь. Хоча один коментар, кластерні індекси не завжди швидші, оскільки можна прочитати (можливо, не зрозуміти це) з вашої відповіді.
Гіслі

Думаю, я розумію різницю між кластерним / некластеризованим індексом, але все ще не бачу, як можна покращити запит стовпця, який не є частиною кластерного індексу, маючи кластерний індекс для інших стовпців?
Lee Tickett

У SQL Server кластерний індекс містить усі стовпці. Кластерний індекс визначає спосіб збереження даних на диску. Мені трохи важко пояснити, але якщо ви думаєте про індекси як про дерево, некластерований індекс є деревом, а в нижньому листі міститься інформація, яку ви визначили як індекс. Для кластерного індексу нижнє листя містить усі стовпці таблиці. Це задумано в SQL Server.
Gisli

Я зрозумів це. Але я думав, що гілки базуються на стовпцях кластерного індексу. Отже, якщо стовпець, який я запитую, не входить, він кластеризований індекс, безумовно, потрібно відсканувати кожну гілку / лист?
Lee Tickett

1
Я цього не розумію. Я найкраще здогадуюсь, що коли у вас був некластерний індекс, він був відсканований, що призвело до великої кількості випадкових входів / виходів. Коли ви створили кластерний індекс, ви позбулися цих випадкових входів / виходів? Але це припущення, я не можу знайти жодної іншої причини такої поведінки, але я не фахівець.
Gisli

1

Однією з причин, чому ваш тест 1М пройшов швидше, швидше за все є те, що тимчасові таблиці повністю знаходяться в пам'яті і будуть надходити на диск, лише якщо ваш сервер відчуває тиск в пам'яті. Ви можете повторно створити свій запит, щоб видалити замовлення, додати хороший кластерний індекс та індекс (и) покриття, як уже згадувалося раніше, або зробити запит у DMV, щоб перевірити тиск вводу-виводу, щоб перевірити, чи це пов'язано з обладнанням.

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

Я сподіваюся, вся моя база даних буде в пам'яті. чи є спосіб це перевірити або сказати sql, які таблиці зберігати в пам'яті? мене не було протягом кількох днів, але коли я повернусь, я спробую ваш запит
Lee Tickett

Середній підрахунок завдань та середній рівень очікуваного підрахунку DiskIO досяг піку 4. Мені все ще цікаво намагатись змусити db увійти в таран.
Lee Tickett

0

Я знаю, що ви сказали, що додавання індексів - це не варіант, але це єдиний варіант усунути сканування таблиці, яке у вас є. Коли ви виконуєте сканування, SQL Server зчитує всі 2 мільйони рядків таблиці, щоб виконати ваш запит.

Ця стаття містить більше інформації, але пам’ятайте: Шукати = добре, Сканувати = погано.

По-друге, ви не можете усунути виділення * та вибрати лише ті стовпці, які вам потрібні? По-третє, відсутність речення "де"? Навіть якщо у вас є індекс, оскільки ви читаєте все, що найкраще ви отримаєте, це сканування індексу (що краще, ніж сканування таблиці, але це не пошук, саме до цього вам слід прагнути)


Неправда, що пошук завжди кращий за сканування. Іноді сканування насправді є більш ефективним. Якби це було не так, тоді M $ не включав би підказку запиту FORCESCAN, починаючи з SQL Server 2008 R2. Детальніше див. Тут: msdn.microsoft.com/en-us/library/ms181714(v=sql.105).aspx і навіть тут для тих, хто хоче примусити сканувати (3-а відповідь Адама Хейнса має гарну інформацію): соціальна .msdn.microsoft.com / Forums / en-US / transactsql / thread /…
Соломон Руцкі,

1
Ну, насамперед, пошуки корисні для точкових запитів. По-друге, сканування добре для запитів діапазону, де потрібно отримати багато даних. Системи OLAP не працювали б добре без сканування. Системи OLTP не працювали б добре без пошуків. Все має своє місце у великій схемі речей ...
дарлове

0

Я знаю, що минуло досить багато часу з початку ... У всіх цих відповідях є багато мудрості. Хороша індексація - це перше, що потрібно зробити, щоб покращити запит. Ну, майже перший. Найперше (так би мовити) вносить зміни в код, щоб він був ефективним. Отже, після того, як все сказано і зроблено, якщо у когось є запит, де немає WHERE, або коли умова WHERE недостатньо вибіркова, існує лише один спосіб отримати дані: TABLE SCAN (INDEX SCAN). Якщо потрібні всі стовпці з таблиці, буде використано ТАБЛИЧНЕ СКАНУВАННЯ - про це немає сумнівів. Це може бути сканування купи або кластерне сканування індексу, залежно від типу організації даних. Єдиним останнім способом пришвидшити процес (якщо це можливо) є переконатися, що для сканування використовується якомога більше ядер: OPTION (MAXDOP 0). Я ігнорую тему зберігання, звичайно,

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.