Повышение производительности запросов 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 server 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 05
4
Table Scanуказывает на кучу (без кластеризованного индекса), поэтому первым шагом будет добавление хорошего и быстрого кластеризованного индекса в вашу таблицу. Второй шаг может заключаться в том, чтобы выяснить er101_upd_date_iso, поможет ли некластеризованный индекс on (и не вызовет ли других проблем с производительностью)
marc_s
1
@marc_s, спасибо за это - я изменил индекс pk на кластеризованный, и это существенно изменило ситуацию - вы можете объяснить это дальше? (см. обновление 2)
Lee Tickett
2
Что ж, кластеризованный индекс просто меняет структуру хранения таблицы. Кластеризованный индекс содержит фактические данные таблицы в узлах конечного уровня, то есть: для чтения всей таблицы SQL Server теперь выполняет сканирование кластерного индекса (в основном «сканирование таблицы» по таблице с кластеризованным индексом). Это почти всегда будет немного быстрее, чем сканирование таблицы в куче (без кластерного индекса). Если вы теперь добавили некластеризованный индекс в er101_upd_date_isoстолбец, вы, вероятно, также сможете избавиться от операции «Сортировка» в своем плане выполнения и еще больше ускорить процесс
marc_s
2
@LeeTickett, покажите, пожалуйста, определения ваших таблиц и указателей. Есть много факторов, которые следует учитывать, и, кажется, никто о них не спрашивает (что меня удивляет, но, возможно, этого не следует). Я могу сказать вам, что 2 миллиона строк НЕ являются большими и правильно проиндексированные таблицы с более чем 200 миллионами строк возвращаются быстрее, чем это. Скорее всего, кластерный индекс (теперь, когда он у вас есть благодаря marc_s) - плохой выбор, но трудно сказать, не видя подробностей. НЕ используйте разделение, но НЕ используйте SET STATISTICS IO ON и отметьте Logical Reads на вкладке сообщений. Если изменение уменьшает количество логических чтений, вы приближаетесь.
Соломон Рутцки

Ответы:

59

Простой ответ: НЕТ. Вы не можете помочь в специальных запросах к таблице из 238 столбцов с коэффициентом заполнения 50% в кластеризованном индексе.

Подробный ответ:

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

Я могу сказать, что никакая помощь не может быть предоставлена ​​по вопросам по этой теме без понимания самой таблицы, ее индексов, триггеров и т. Д. Теперь, когда вы опубликовали определение таблицы (все еще ожидая индексов, но само определение таблицы указывает на 99% вопроса) Могу предложить несколько предложений.

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

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

Рекомендации:

  1. Во-первых, этот стол действительно нужно переделать. Если это таблица хранилища данных, то возможно, но если нет, тогда эти поля действительно нужно разбить на несколько таблиц, которые могут иметь одинаковый PK. У вас будет таблица основных записей, а дочерние таблицы - это просто зависимая информация, основанная на обычно связанных атрибутах, и PK этих таблиц такой же, как PK главной таблицы и, следовательно, также FK для главной таблицы. Между главной и всеми дочерними таблицами будет связь один-к-одному.
  2. Использование ANSI_PADDING OFFвызывает беспокойство, не говоря уже о непоследовательности в таблице из-за различных добавлений столбцов с течением времени. Не уверен, что вы можете исправить это сейчас, но в идеале вы всегда должны иметь ANSI_PADDING ONили, по крайней мере, иметь одинаковые настройки для всех ALTER TABLEоператоров.
  3. Рассмотрите возможность создания двух дополнительных файловых групп: таблиц и индексов. Лучше не вставлять свои вещи, PRIMARYпоскольку именно здесь SQL SERVER хранит все свои данные и метаданные о ваших объектах. Вы создаете свою таблицу и кластерный индекс (поскольку это данные для таблицы), [Tables]а все некластеризованные индексы - на[Indexes]
  4. Увеличьте коэффициент заполнения с 50%. Это низкое число, вероятно, объясняет, почему ваше пространство индекса больше, чем пространство данных. Выполнение перестроения индекса воссоздает страницы данных с максимальным размером 4 КБ (из общего размера страницы 8 КБ), используемым для ваших данных, поэтому ваша таблица будет разбросана по широкой области.
  5. Если большинство или все запросы содержат в условии «ER101_ORG_CODE» WHERE, рассмотрите возможность перемещения этого запроса в ведущий столбец кластеризованного индекса. Предполагая, что он используется чаще, чем "ER101_ORD_NBR". Если "ER101_ORD_NBR" используется чаще, оставьте его. Это просто кажется, если предположить, что имена полей означают "OrganizationCode" и "OrderNumber", что "OrgCode" - лучшая группа, которая может иметь несколько "OrderNumber" внутри.
  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, но только как функция Enterprise Edition. Однако, начиная с SQL Server 2016 SP1 , сжатие данных стало доступно во всех выпусках ! Подробную информацию о сжатии строк и страниц см. На странице 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 для SELECT *- заставляет SQL Server использовать кластерный индекс. По крайней мере, так должно быть. Я не вижу реальной причины для некластеризованного индекса покрытия ... покрывающего всю таблицу :)
ta.speot.is
4
Этот ответ касается только повышения скорости примера запроса, а НЕ вопроса, который заключается в том, «можно ли улучшить производительность для специальных запросов»
Фил,
CDE теперь CTE (Common Table Expression)
sqluser
12

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

Добавление недостающих индексов бесконечно повысит производительность.

в столбцах, которые запрашиваются чаще всего, уже есть индексы

Это не означает, что они используются в этом запросе (и, вероятно, нет).

Я предлагаю прочитать Гейл Шоу «Поиск причин низкой производительности в SQL Server», часть 1 и часть 2 .

Одед
источник
Я
хочу сказать,
1
@LeeTickett - И тем не менее, это единственный столбец, в который вы можете добавить индекс, чтобы повысить производительность этого запроса.
Oded
2
Нет такой вещи, как оптимизированный неиндексированный поиск. Вы либо используете индекс, либо полное сканирование таблицы. Если вам не нужно полное сканирование таблиц, вам нужны индексы. В зависимости от вашего профиля использования может быть достаточно дешево просто добавить индексы. Да, и тип данных столбца тоже имеет значение. Если у вас er101_upd_date_isoогромный varchar или int, производительность значительно изменится.
Cylindric
Спасибо. я добавил комментарий к вопросу. Я понимаю, что маловероятно, что я смогу оптимизировать запрос, но я полагаю, что могут быть способы улучшить производительность специальных запросов,
Ли Тикетт,
Я учусь, как мы узнаем, какой столбец нужно проиндексировать?
Virus
7

В вопросе конкретно указывается, что производительность необходимо повысить для специальных запросов и что индексы не могут быть добавлены. Итак, если принять это за чистую монету, что можно сделать для повышения производительности за любым столом?

Поскольку мы рассматриваем специальные запросы, предложения WHERE и ORDER BY могут содержать любую комбинацию столбцов. Это означает, что почти независимо от того, какие индексы размещены в таблице, будут некоторые запросы, требующие сканирования таблицы, как показано выше в плане запроса для неэффективного запроса.

Принимая это во внимание, предположим, что в таблице вообще нет индексов, кроме кластеризованного индекса по первичному ключу. Теперь давайте рассмотрим, какие у нас есть варианты, чтобы максимизировать производительность.

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

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

  • Используйте максимально быстрые диски. Вы не говорите, какие диски вы используете, но можете ли вы использовать SSD.

  • Оптимизируйте tempdb. Поместите tempdb на самые быстрые из возможных дисков, опять же на SSD. См. Эту статью SO и статью RedGate .

  • Как указано в других ответах, использование более избирательного запроса вернет меньше данных и, следовательно, должно быть быстрее.

Теперь давайте посмотрим, что мы можем сделать, если нам разрешат добавлять индексы.

Если бы мы не говорили о специальных запросах, мы бы добавили индексы специально для ограниченного набора запросов, выполняемых к таблице. Поскольку мы обсуждаем специальные запросы, что можно сделать для повышения скорости в большинстве случаев?

  • Добавьте один индекс столбца к каждому столбцу. Это должно дать SQL Server по крайней мере то, с чем можно работать, чтобы повысить скорость большинства запросов, но не будет оптимальным.
  • Добавьте специальные индексы для наиболее распространенных запросов, чтобы они были оптимизированы.
  • Добавьте дополнительные специальные индексы, если это необходимо для отслеживания неэффективных запросов.

редактировать

Я провел несколько тестов на «большой» таблице из 22 миллионов строк. В моей таблице всего шесть столбцов, но она содержит 4 ГБ данных. Моя машина представляет собой респектабельный настольный компьютер с 8 ГБ ОЗУ, четырехъядерным процессором и одним твердотельным накопителем Agility 3.

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

Запрос, аналогичный задаче, указанной в вопросе, занимает 5 секунд, если сначала перезапускается SQL-сервер, и 3 секунды после этого. Советчик по настройке базы данных, очевидно, рекомендует добавить индекс для улучшения этого запроса с предполагаемым улучшением> 99%. Добавление индекса приводит к фактически нулевому времени запроса.

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

Фил
источник
2

Даже если у вас есть индексы для некоторых столбцов, которые используются в некоторых запросах, тот факт, что ваш «специальный» запрос вызывает сканирование таблицы, показывает, что у вас недостаточно индексов для эффективного выполнения этого запроса.

В частности, для диапазонов дат сложно добавить хорошие индексы.

Просто глядя на ваш запрос, база данных должна отсортировать все записи по выбранному столбцу, чтобы иметь возможность вернуть первые n записей.

БД также выполняет полное сканирование таблицы без предложения order by? Есть ли у таблицы первичный ключ - без ПК БД придется усерднее работать, чтобы выполнить сортировку?

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

Как это возможно? Как можно использовать сканирование кластерного индекса без индекса в столбце er101_upd_date_iso?

Индекс - это B-Tree, где каждый листовой узел указывает на «группу строк» ​​(называемую «страницей» в внутренней терминологии SQL), то есть когда индекс является некластеризованным индексом.

Кластерный индекс - это особый случай, в котором листовые узлы имеют «группу строк» ​​(а не указывают на них). поэтому...

1) В таблице может быть только один кластерный индекс.

это также означает, что вся таблица хранится как кластеризованный индекс, поэтому вы начали видеть сканирование индекса, а не сканирование таблицы.

2) Операция, использующая кластерный индекс, обычно выполняется быстрее, чем некластеризованный индекс.

Подробнее см. Http://msdn.microsoft.com/en-us/library/ms177443.aspx

Если у вас возникла проблема, вам действительно следует подумать о добавлении этого столбца в индекс, поскольку вы сказали, что добавление нового индекса (или столбца к существующему индексу) увеличивает затраты на INSERT / UPDATE. Но, возможно, удастся удалить какой-то недостаточно используемый индекс (или столбец из существующего индекса), чтобы заменить его на «er101_upd_date_iso».

Если изменения индекса невозможны, я рекомендую добавить статистику по столбцу, это может ускорить работу, когда столбцы имеют некоторую корреляцию с индексированными столбцами.

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

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

shankar_pratap
источник
+1 за ответ. Одно замечание: кластерные индексы не всегда быстрее, чем можно было бы прочитать (это можно неправильно понять) из вашего ответа.
Gisli
Я думаю, что понимаю разницу между кластеризованным / некластеризованным индексом, но до сих пор не вижу, как можно улучшить запрос столбца, который не является частью кластеризованного индекса, за счет наличия кластерного индекса для других столбцов?
Ли Тикетт,
В SQL Server кластерный индекс содержит все столбцы. Кластерный индекс - это то, что решает, как данные хранятся на диске. Мне немного сложно объяснить, но если вы думаете об индексах как о дереве, некластеризованный индекс - это дерево, а нижние листья содержат информацию, которую вы определили как индекс. Для кластерного индекса нижние листья содержат все столбцы в таблице. Это сделано специально в SQL Server.
Gisli
Я понимаю. Но я думал, что ветви были основаны на столбцах в кластеризованном индексе. Итак, если столбец, который я запрашиваю, не является int, кластерный индекс, безусловно, необходимо сканировать каждую ветвь / лист?
Ли Тикетт,
1
Я этого не понимаю. Я предполагаю, что когда у вас был некластеризованный индекс, он был просканирован, что привело к множеству случайных операций ввода-вывода. Создав кластерный индекс, вы избавились от случайных операций ввода-вывода? Но это предположение, я не могу найти другой причины такого поведения, но я не эксперт.
Gisli
1

Одна из причин, по которой ваш тест 1M выполняется быстрее, вероятно, состоит в том, что временные таблицы полностью находятся в памяти и будут отправляться на диск только в том случае, если ваш сервер испытывает нехватку памяти. Вы можете либо переформулировать свой запрос, чтобы удалить порядок, добавить хороший кластерный индекс и покрывающий индекс (а), как упоминалось ранее, либо запросить 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
Ninghad
источник
Надеюсь, вся моя база данных будет в памяти. есть ли способ проверить это или сказать sql, какие таблицы хранить в памяти? Я отсутствовал несколько дней, но когда вернусь, попробую ответить на ваш вопрос
Ли Тикетт,
Среднее число задач и среднее число ожидающих обработки DiskIO достигло пика 4. Мне все еще любопытно попытаться заставить db работать в оперативной памяти.
Ли Тикетт,
0

Я знаю, что вы сказали, что добавление индексов - это не вариант, но это единственный вариант, который избавит вас от сканирования таблиц. Когда вы выполняете сканирование, SQL Server считывает все 2 миллиона строк в таблице для выполнения вашего запроса.

эта статья предоставляет дополнительную информацию, но помните: Seek = хорошо, Scan = плохо.

Во-вторых, нельзя ли удалить select * и выбрать только нужные столбцы? В-третьих, нет пункта «где»? Даже если у вас есть индекс, поскольку вы читаете все, лучшее, что вы получите, - это сканирование индекса (что лучше, чем сканирование таблицы, но это не поиск, к которому вы должны стремиться)

Диего
источник
Неправда, что поиск всегда лучше сканирования. Иногда сканирование действительно более эффективно. Если бы это было не так, M $ не включал бы подсказку запроса FORCESCAN, начиная с SQL Server 2008 R2. Подробнее см. Здесь: msdn.microsoft.com/en-us/library/ms181714(v=sql.105).aspx и даже здесь для тех, кто хочет принудительно сканировать (3-й ответ Адама Хейнса имеет хорошую информацию): social .msdn.microsoft.com / Forums / en-US / transactsql / thread /…
Соломон Руцки,
1
Во-первых, поиск хорош для точечных запросов. Во-вторых, сканирование подходит для запросов диапазона, в которых необходимо получить большой объем данных. Системы OLAP не работали бы без сканирования. Системы OLTP не работали бы хорошо без поиска. У всего есть свое место в великой схеме вещей ...
darlove
0

Я знаю, что с самого начала прошло довольно много времени ... Во всех этих ответах много мудрости. Хорошая индексация - это первое, что нужно при попытке улучшить запрос. Ну почти первое. Самым первым (так сказать) является внесение изменений в код, чтобы он был эффективным. Итак, после всего сказанного и сделанного, если у кого-то есть запрос без WHERE или если условие WHERE недостаточно избирательно, есть только один способ получить данные: TABLE SCAN (INDEX SCAN). Если нужны все столбцы из таблицы, то будет использоваться TABLE SCAN - никаких сомнений. Это может быть сканирование кучи или сканирование кластерного индекса, в зависимости от типа организации данных. Единственный последний способ ускорить процесс (если это вообще возможно) - убедиться, что для сканирования используется как можно больше ядер: OPTION (MAXDOP 0). Я, конечно, игнорирую тему хранения,

дорогая
источник