У меня относительно большая таблица (в настоящее время 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 ГБ.
источник
Table Scan
указывает на кучу (без кластеризованного индекса), поэтому первым шагом будет добавление хорошего и быстрого кластеризованного индекса в вашу таблицу. Второй шаг может заключаться в том, чтобы выяснитьer101_upd_date_iso
, поможет ли некластеризованный индекс on (и не вызовет ли других проблем с производительностью)er101_upd_date_iso
столбец, вы, вероятно, также сможете избавиться от операции «Сортировка» в своем плане выполнения и еще больше ускорить процессОтветы:
Простой ответ: НЕТ. Вы не можете помочь в специальных запросах к таблице из 238 столбцов с коэффициентом заполнения 50% в кластеризованном индексе.
Подробный ответ:
Как я уже говорил в других ответах на эту тему, дизайн индекса - это одновременно искусство и наука, и необходимо учитывать так много факторов, что существует несколько жестких правил, если таковые имеются. Вы должны учитывать: объем операций DML по сравнению с SELECT, дисковая подсистема, другие индексы / триггеры в таблице, распределение данных в таблице, запросы с использованием условий SARGable WHERE и несколько других вещей, которые я даже не могу вспомнить правильно сейчас же.
Я могу сказать, что никакая помощь не может быть предоставлена по вопросам по этой теме без понимания самой таблицы, ее индексов, триггеров и т. Д. Теперь, когда вы опубликовали определение таблицы (все еще ожидая индексов, но само определение таблицы указывает на 99% вопроса) Могу предложить несколько предложений.
Во-первых, если определение таблицы является точным (238 столбцов, коэффициент заполнения 50%), вы можете в значительной степени игнорировать остальные ответы / советы здесь ;-). Извините за то, что веду здесь менее чем политическую деятельность, но если серьезно, это дикая погоня за гусем, не зная подробностей. И теперь, когда мы видим определение таблицы, становится немного яснее, почему простой запрос занимает так много времени, даже когда тестовые запросы (Обновление №1) выполнялись так быстро.
Основная проблема здесь (и во многих ситуациях с низкой производительностью) - плохое моделирование данных. 238 столбцов не запрещены, так же как и наличие 999 индексов не запрещено, но также, как правило, не очень разумно.
Рекомендации:
ANSI_PADDING OFF
вызывает беспокойство, не говоря уже о непоследовательности в таблице из-за различных добавлений столбцов с течением времени. Не уверен, что вы можете исправить это сейчас, но в идеале вы всегда должны иметьANSI_PADDING ON
или, по крайней мере, иметь одинаковые настройки для всехALTER TABLE
операторов.PRIMARY
поскольку именно здесь SQL SERVER хранит все свои данные и метаданные о ваших объектах. Вы создаете свою таблицу и кластерный индекс (поскольку это данные для таблицы),[Tables]
а все некластеризованные индексы - на[Indexes]
WHERE
, рассмотрите возможность перемещения этого запроса в ведущий столбец кластеризованного индекса. Предполагая, что он используется чаще, чем "ER101_ORD_NBR". Если "ER101_ORD_NBR" используется чаще, оставьте его. Это просто кажется, если предположить, что имена полей означают "OrganizationCode" и "OrderNumber", что "OrgCode" - лучшая группа, которая может иметь несколько "OrderNumber" внутри.CHAR(2)
вместо этого,VARCHAR(2)
поскольку он сохранит байт в заголовке строки, который отслеживает размеры переменной ширины и складывает более миллионов строк.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
Дополнительные соображения:
INT
,BIGINT
,TINYINT
,SMALLINT
,CHAR
,NCHAR
,BINARY
,DATETIME
,SMALLDATETIME
,MONEY
и т.д.) , а также более чем 50 % строк естьNULL
, затем рассмотрите возможность включенияSPARSE
опции, которая стала доступной в SQL Server 2008. Подробные сведения см. на странице MSDN для использования разреженных столбцов .источник
*
без этого сомнительногоУ этого запроса есть несколько проблем (и это относится ко всем запросам).
Отсутствие индекса
Как уже упоминал Одед,
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
).Плюсы:
Минусы:
3. Создайте представления
Аналогично предыдущему, но создавайте представления вместо временных таблиц (если вы часто играете с одними и теми же запросами и у вас есть версия 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.
Я полагаю, что для специальных запросов достаточно грязного чтения .
источник
SELECT *
- заставляет SQL Server использовать кластерный индекс. По крайней мере, так должно быть. Я не вижу реальной причины для некластеризованного индекса покрытия ... покрывающего всю таблицу :)Там вы получаете сканирование таблицы , что означает, что у вас нет индекса, определенного на
er101_upd_date_iso
, или , если столбец является частью существующего индекса, индекс не может быть использован (возможно , это не основной столбец индексатор).Добавление недостающих индексов бесконечно повысит производительность.
Это не означает, что они используются в этом запросе (и, вероятно, нет).
Я предлагаю прочитать Гейл Шоу «Поиск причин низкой производительности в SQL Server», часть 1 и часть 2 .
источник
er101_upd_date_iso
огромный varchar или int, производительность значительно изменится.В вопросе конкретно указывается, что производительность необходимо повысить для специальных запросов и что индексы не могут быть добавлены. Итак, если принять это за чистую монету, что можно сделать для повышения производительности за любым столом?
Поскольку мы рассматриваем специальные запросы, предложения WHERE и ORDER BY могут содержать любую комбинацию столбцов. Это означает, что почти независимо от того, какие индексы размещены в таблице, будут некоторые запросы, требующие сканирования таблицы, как показано выше в плане запроса для неэффективного запроса.
Принимая это во внимание, предположим, что в таблице вообще нет индексов, кроме кластеризованного индекса по первичному ключу. Теперь давайте рассмотрим, какие у нас есть варианты, чтобы максимизировать производительность.
Дефрагментируйте таблицу
Пока у нас есть кластерный индекс, мы можем дефрагментировать таблицу, используя DBCC INDEXDEFRAG (устарело) или, предпочтительно, ALTER INDEX . Это минимизирует количество операций чтения с диска, необходимых для сканирования таблицы, и увеличит скорость.
Используйте максимально быстрые диски. Вы не говорите, какие диски вы используете, но можете ли вы использовать SSD.
Оптимизируйте tempdb. Поместите tempdb на самые быстрые из возможных дисков, опять же на SSD. См. Эту статью SO и статью RedGate .
Как указано в других ответах, использование более избирательного запроса вернет меньше данных и, следовательно, должно быть быстрее.
Теперь давайте посмотрим, что мы можем сделать, если нам разрешат добавлять индексы.
Если бы мы не говорили о специальных запросах, мы бы добавили индексы специально для ограниченного набора запросов, выполняемых к таблице. Поскольку мы обсуждаем специальные запросы, что можно сделать для повышения скорости в большинстве случаев?
редактировать
Я провел несколько тестов на «большой» таблице из 22 миллионов строк. В моей таблице всего шесть столбцов, но она содержит 4 ГБ данных. Моя машина представляет собой респектабельный настольный компьютер с 8 ГБ ОЗУ, четырехъядерным процессором и одним твердотельным накопителем Agility 3.
Я удалил все индексы, кроме первичного ключа, в столбце Id.
Запрос, аналогичный задаче, указанной в вопросе, занимает 5 секунд, если сначала перезапускается SQL-сервер, и 3 секунды после этого. Советчик по настройке базы данных, очевидно, рекомендует добавить индекс для улучшения этого запроса с предполагаемым улучшением> 99%. Добавление индекса приводит к фактически нулевому времени запроса.
Также интересно то, что мой план запроса идентичен вашему (со сканированием кластерного индекса), но на сканирование индекса приходится 9% стоимости запроса, а на сортировку - остальные 91%. Я могу только предположить, что ваша таблица содержит огромное количество данных и / или ваши диски очень медленные или расположены через очень медленное сетевое соединение.
источник
Даже если у вас есть индексы для некоторых столбцов, которые используются в некоторых запросах, тот факт, что ваш «специальный» запрос вызывает сканирование таблицы, показывает, что у вас недостаточно индексов для эффективного выполнения этого запроса.
В частности, для диапазонов дат сложно добавить хорошие индексы.
Просто глядя на ваш запрос, база данных должна отсортировать все записи по выбранному столбцу, чтобы иметь возможность вернуть первые n записей.
БД также выполняет полное сканирование таблицы без предложения order by? Есть ли у таблицы первичный ключ - без ПК БД придется усерднее работать, чтобы выполнить сортировку?
источник
select top 100 * from ER101_ACCT_ORDER_DTL
Индекс - это 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. и существующие индексы тоже ... возможно, запрос можно было бы переписать, чтобы использовать некоторые из них.
источник
Одна из причин, по которой ваш тест 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
источник
Я знаю, что вы сказали, что добавление индексов - это не вариант, но это единственный вариант, который избавит вас от сканирования таблиц. Когда вы выполняете сканирование, SQL Server считывает все 2 миллиона строк в таблице для выполнения вашего запроса.
эта статья предоставляет дополнительную информацию, но помните: Seek = хорошо, Scan = плохо.
Во-вторых, нельзя ли удалить select * и выбрать только нужные столбцы? В-третьих, нет пункта «где»? Даже если у вас есть индекс, поскольку вы читаете все, лучшее, что вы получите, - это сканирование индекса (что лучше, чем сканирование таблицы, но это не поиск, к которому вы должны стремиться)
источник
Я знаю, что с самого начала прошло довольно много времени ... Во всех этих ответах много мудрости. Хорошая индексация - это первое, что нужно при попытке улучшить запрос. Ну почти первое. Самым первым (так сказать) является внесение изменений в код, чтобы он был эффективным. Итак, после всего сказанного и сделанного, если у кого-то есть запрос без WHERE или если условие WHERE недостаточно избирательно, есть только один способ получить данные: TABLE SCAN (INDEX SCAN). Если нужны все столбцы из таблицы, то будет использоваться TABLE SCAN - никаких сомнений. Это может быть сканирование кучи или сканирование кластерного индекса, в зависимости от типа организации данных. Единственный последний способ ускорить процесс (если это вообще возможно) - убедиться, что для сканирования используется как можно больше ядер: OPTION (MAXDOP 0). Я, конечно, игнорирую тему хранения,
источник