Verbessern Sie die Leistung von SQL Server-Abfragen in großen Tabellen


85

Ich habe eine relativ große Tabelle (derzeit 2 Millionen Datensätze) und möchte wissen, ob es möglich ist, die Leistung für Ad-hoc-Abfragen zu verbessern. Das Wort Ad-hoc ist hier der Schlüssel. Das Hinzufügen von Indizes ist keine Option (es gibt bereits Indizes für die Spalten, die am häufigsten abgefragt werden).

Ausführen einer einfachen Abfrage, um die 100 zuletzt aktualisierten Datensätze zurückzugeben:

select top 100 * from ER101_ACCT_ORDER_DTL order by er101_upd_date_iso desc

Dauert einige Minuten. Siehe Ausführungsplan unten:

Geben Sie hier die Bildbeschreibung ein

Zusätzliches Detail aus dem Tabellenscan:

Geben Sie hier die Bildbeschreibung ein

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

Der Server ist ziemlich leistungsfähig (vom Speicher 48 GB RAM, 24-Kern-Prozessor), auf dem SQL Server 2008 R2 x64 ausgeführt wird.

Aktualisieren

Ich habe diesen Code gefunden, um eine Tabelle mit 1.000.000 Datensätzen zu erstellen. Ich dachte, ich könnte dann SELECT TOP 100 * FROM testEnvironment ORDER BY mailAddress DESCauf einigen verschiedenen Servern laufen , um herauszufinden, ob meine Datenträgerzugriffsgeschwindigkeiten auf dem Server schlecht waren.

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;

Auf den drei Testservern wurde die Abfrage jedoch fast sofort ausgeführt. Kann jemand das erklären?

Geben Sie hier die Bildbeschreibung ein

Update 2

Vielen Dank für die Kommentare - bitte halten Sie sie auf dem Laufenden ... Sie haben mich dazu veranlasst, den Primärschlüsselindex von nicht gruppiert in gruppiert zu ändern, mit ziemlich interessanten (und unerwarteten?) Ergebnissen.

Nicht geclustert:

Geben Sie hier die Bildbeschreibung ein

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

Clustered:

Geben Sie hier die Bildbeschreibung ein

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

Wie ist das möglich? Wie kann ein Clustered-Index-Scan ohne Index in der Spalte er101_upd_date_iso verwendet werden?

Update 3

Wie angefordert, ist dies das Skript zum Erstellen einer Tabelle:

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]

Die Tabelle hat eine Größe von 2,8 GB und eine Indexgröße von 3,9 GB.


1
Es gibt kleine Hinweise, wenn Sie den Mauszeiger über das Planelement bewegen. Sie zeigen die geschätzten E / A- und CPU-Kosten. Ich würde mich zuerst um die E / A-Kosten kümmern.
Grzegorz Gierlik

4
Table ScanGibt einen Heap an (kein Clustered-Index). Der erste Schritt besteht also darin , Ihrer Tabelle einen guten, schnellen Clustered-Index hinzuzufügen . Der zweite Schritt könnte darin bestehen, zu untersuchen, ob ein nicht gruppierter Index für er101_upd_date_isohilfreich ist (und keine anderen Leistungsnachteile verursacht)
marc_s

1
@marc_s danke dafür - ich habe den pk-Index in Clustered geändert und er hat einen wesentlichen finanziellen Unterschied gemacht - können Sie dies weiter erklären? (siehe Update 2)
Lee Tickett

2
Nun, der Clustered-Index ändert nur das Speicherlayout der Tabelle. Der Clustered-Index enthält die tatsächlichen Tabellendaten in seinen Knoten auf Blattebene. Das heißt: Um die gesamte Tabelle zu lesen, führt SQL Server jetzt einen Clustered-Index-Scan durch (im Grunde ein "Tabellenscan" über einer Tabelle mit einem Clustered-Index). Das ist fast immer viel schneller als ein Tabellenscan auf einem Heap (ohne Clustered-Index). Wenn Sie jetzt einen nicht gruppierten Index für die er101_upd_date_isoSpalte hinzugefügt haben , können Sie wahrscheinlich auch die Operation "Sortieren" in Ihrem Ausführungsplan
entfernen

2
@LeeTickett, zeigen Sie bitte Ihre Tabellen- und Indexdefinitionen. Es gibt viele Faktoren zu berücksichtigen und niemand scheint nach ihnen zu fragen (was mich überrascht, aber vielleicht sollte es nicht). Ich kann Ihnen sagen, dass 2 Millionen Zeilen NICHT groß sind und ordnungsgemäß indizierte Tabellen mit mehr als 200 Millionen Zeilen schneller zurückkehren. Höchstwahrscheinlich ist der Clustered-Index (jetzt, wo Sie dank marc_s einen haben) eine schlechte Wahl, aber schwer zu sagen, ohne die Einzelheiten zu sehen. Verwenden Sie KEINE Partitionierung, sondern SET STATISTICS IO ON und aktivieren Sie Logical Reads auf der Registerkarte Nachrichten. Wenn eine Änderung die logischen Lesevorgänge verringert, kommen Sie näher.
Solomon Rutzky

Antworten:


59

Einfache Antwort: NEIN. Sie können Ad-hoc-Abfragen für eine 238-Spaltentabelle mit einem Füllfaktor von 50% für den Clustered-Index nicht unterstützen.

Detaillierte Antwort:

Wie ich in anderen Antworten zu diesem Thema ausgeführt habe, ist Indexdesign sowohl Kunst als auch Wissenschaft, und es gibt so viele Faktoren zu berücksichtigen, dass es nur wenige, wenn überhaupt, feste Regeln gibt. Sie müssen Folgendes berücksichtigen: Das Volumen der DML-Operationen im Vergleich zu SELECTs, das Festplattensubsystem, andere Indizes / Trigger in der Tabelle, die Verteilung der Daten in der Tabelle sind Abfragen unter Verwendung von SARGable WHERE-Bedingungen und einige andere Dinge, an die ich mich nicht einmal richtig erinnern kann jetzt.

Ich kann sagen, dass bei Fragen zu diesem Thema keine Hilfe gegeben werden kann, ohne die Tabelle selbst, ihre Indizes, Trigger usw. zu verstehen. Nachdem Sie die Tabellendefinition veröffentlicht haben (warten Sie immer noch auf die Indizes, aber nur die Tabellendefinition zeigt darauf 99% der Ausgabe) Ich kann einige Vorschläge machen.

Erstens, wenn die Tabellendefinition korrekt ist (238 Spalten, 50% Füllfaktor), können Sie den Rest der Antworten / Ratschläge hier so gut wie ignorieren ;-). Es tut mir leid, hier weniger als politisch zu sein, aber im Ernst, es ist eine wilde Gänsejagd, ohne die Einzelheiten zu kennen. Und jetzt, da wir die Tabellendefinition sehen, wird ziemlich viel klarer, warum eine einfache Abfrage so lange dauern würde, selbst wenn die Testabfragen (Update Nr. 1) so schnell ausgeführt wurden.

Das Hauptproblem hier (und in vielen Situationen mit schlechter Leistung) ist die schlechte Datenmodellierung. 238 Spalten sind nicht verboten, genauso wie 999 Indizes nicht verboten sind, aber es ist auch im Allgemeinen nicht sehr klug.

Empfehlungen:

  1. Erstens muss dieser Tisch wirklich umgebaut werden. Wenn dies eine Data Warehouse-Tabelle ist, dann vielleicht, aber wenn nicht, müssen diese Felder wirklich in mehrere Tabellen aufgeteilt werden, die alle dieselbe PK haben können. Sie hätten eine Stammdatensatztabelle und die untergeordneten Tabellen sind nur abhängige Informationen, die auf häufig zugeordneten Attributen basieren. Die PK dieser Tabellen entspricht der PK der Mastertabelle und damit auch der FK der Mastertabelle. Zwischen dem Master und allen untergeordneten Tabellen besteht eine 1: 1-Beziehung.
  2. Die Verwendung von ANSI_PADDING OFFist störend, ganz zu schweigen von der Inkonsistenz innerhalb der Tabelle aufgrund der verschiedenen Spaltenzusätze im Laufe der Zeit. Ich bin mir nicht sicher, ob Sie das jetzt beheben können, aber im Idealfall hätten Sie immer ANSI_PADDING ONoder zumindest die gleiche Einstellung für alle ALTER TABLEAnweisungen.
  3. Ziehen Sie in Betracht, zwei zusätzliche Dateigruppen zu erstellen: Tabellen und Indizes. Es ist am besten, Ihre PRIMARYDaten nicht dort abzulegen, da dort SQL SERVER alle Daten und Metadaten zu Ihren Objekten speichert. Sie erstellen Ihre Tabelle und Ihren Clustered-Index (da dies die Daten für die Tabelle sind) auf [Tables]und alle Nicht-Clustered-Indizes auf[Indexes]
  4. Erhöhen Sie den Füllfaktor von 50%. Diese niedrige Zahl ist wahrscheinlich der Grund, warum Ihr Indexbereich größer als Ihr Datenbereich ist. Durch eine Indexwiederherstellung werden die Datenseiten mit maximal 4 KB (von insgesamt 8 KB Seitengröße) für Ihre Daten neu erstellt, sodass Ihre Tabelle über einen weiten Bereich verteilt ist.
  5. Wenn die meisten oder alle Abfragen "ER101_ORG_CODE" in der WHEREBedingung enthalten, sollten Sie dies in die führende Spalte des Clustered-Index verschieben. Angenommen, es wird häufiger als "ER101_ORD_NBR" verwendet. Wenn "ER101_ORD_NBR" häufiger verwendet wird, behalten Sie es bei. Unter der Annahme, dass die Feldnamen "OrganizationCode" und "OrderNumber" bedeuten, scheint "OrgCode" eine bessere Gruppierung zu sein, die möglicherweise mehrere "OrderNumbers" enthält.
  6. Kleiner Punkt, aber wenn "ER101_ORG_CODE" immer aus 2 Zeichen besteht, verwenden Sie CHAR(2)stattdessen anstelle von, VARCHAR(2)da dadurch ein Byte im Zeilenkopf gespeichert wird, der variable Breitengrößen verfolgt und sich über Millionen von Zeilen summiert.
  7. Wie andere hier bereits erwähnt haben, beeinträchtigt SELECT *die Verwendung die Leistung. Dies erfordert nicht nur, dass SQL Server alle Spalten zurückgibt und daher mit größerer Wahrscheinlichkeit einen Clustered Index Scan unabhängig von Ihren anderen Indizes durchführt, sondern SQL Server benötigt auch Zeit, um zur Tabellendefinition zu gelangen und *in alle Spaltennamen zu übersetzen . Es sollte etwas schneller sein, alle 238 Spaltennamen in der SELECTListe anzugeben, obwohl dies das Scan-Problem nicht beheben kann. Aber brauchen Sie wirklich jemals alle 238 Spalten gleichzeitig?

Viel Glück!

UPDATE
Der Vollständigkeit halber sollte die Frage "Wie kann die Leistung einer großen Tabelle für Ad-hoc-Abfragen verbessert werden?" Vermerkt werden, dass dies in diesem speziellen Fall nicht hilfreich ist, wenn jemand SQL Server 2012 (oder neuer) verwendet Wenn diese Zeit gekommen ist) und wenn die Tabelle nicht aktualisiert wird, ist die Verwendung von Columnstore-Indizes eine Option. Weitere Informationen zu dieser neuen Funktion finden Sie hier: http://msdn.microsoft.com/en-us/library/gg492088.aspx (Ich glaube, diese wurden ab SQL Server 2014 aktualisierbar gemacht).

UPDATE 2
Zusätzliche Überlegungen sind:

  • Aktivieren Sie die Komprimierung für den Clustered Index. Diese Option wurde in SQL Server 2008 verfügbar, jedoch nur als Enterprise Edition-Funktion. Ab SQL Server 2016 SP1 wurde die Datenkomprimierung jedoch in allen Editionen verfügbar gemacht ! Weitere Informationen zur Zeilen- und Seitenkomprimierung finden Sie auf der MSDN-Seite zur Datenkomprimierung .
  • Wenn Sie nicht Datenkomprimierung verwenden können, oder wenn es nicht viel Nutzen für eine bestimmte Tabelle zur Verfügung stellt, dann , wenn Sie eine Spalte eines Typs mit fester Länge haben ( INT, BIGINT, TINYINT, SMALLINT, CHAR, NCHAR, BINARY, DATETIME, SMALLDATETIME, MONEY, etc.) und mehr als 50 % der Zeilen sind NULL, dann sollten Sie die SPARSEOption aktivieren, die in SQL Server 2008 verfügbar wurde. Weitere Informationen finden Sie auf der MSDN-Seite für die Verwendung sparsamer Spalten .

Zu Punkt 7 persönlich stelle ich mir vor, dass es schneller sein sollte, 238 Spaltennamen aus Metadaten hinzuzufügen, als sie aus dem Abfragetext zu analysieren, und dann die Metadaten trotzdem zu überprüfen, um sicherzustellen, dass sie alle existieren. *Ohne diese zweifelhafte gibt es genügend Argumente dagegen
Martin Smith vor

53

Es gibt einige Probleme mit dieser Abfrage (und diese gelten für jede Abfrage).

Fehlender Index

Das Fehlen eines Index für die er101_upd_date_isoSpalte ist das Wichtigste, wie Oded bereits erwähnt hat.

Ohne übereinstimmenden Index (dessen Fehlen einen Tabellenscan verursachen könnte) besteht keine Möglichkeit, schnelle Abfragen für große Tabellen auszuführen.

Wenn Sie keine Indizes hinzufügen können (aus verschiedenen Gründen, einschließlich der sinnlosen Erstellung eines Index für nur eine Ad-hoc-Abfrage ), würde ich einige Problemumgehungen vorschlagen (die für Ad-hoc-Abfragen verwendet werden können):

1. Verwenden Sie temporäre Tabellen

Erstellen Sie eine temporäre Tabelle für eine Teilmenge (Zeilen und Spalten) der Daten, an denen Sie interessiert sind. Die temporäre Tabelle sollte viel kleiner als die ursprüngliche Quelltabelle sein, kann einfach indiziert werden (falls erforderlich) und kann eine Teilmenge der Daten zwischengespeichert werden, an denen Sie interessiert sind.

Um eine temporäre Tabelle zu erstellen, können Sie Code (nicht getestet) wie folgt verwenden:

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

Vorteile:

  • Einfach für jede Teilmenge von Daten.
  • Einfach zu verwalten - es ist vorübergehend und es ist Tisch .
  • Beeinflusst nicht die Gesamtsystemleistung wie view.
  • Temporäre Tabelle kann indiziert werden.
  • Sie müssen sich nicht darum kümmern - es ist vorübergehend :).

Nachteile:

  • Es ist eine Momentaufnahme von Daten - aber wahrscheinlich ist dies für die meisten Ad-hoc-Abfragen gut genug.

2. Allgemeiner Tabellenausdruck - CTE

Persönlich verwende ich CTE häufig bei Ad-hoc-Abfragen - es hilft sehr beim Erstellen (und Testen) einer Abfrage Stück für Stück.

Siehe Beispiel unten (die Abfrage beginnt mit WITH).

Vorteile:

  • Einfach zu erstellen, ausgehend von der großen Ansicht und dann auswählen und filtern, was Sie wirklich brauchen.
  • Einfach zu testen.

Nachteile:

  • Einige Leute mögen CDE nicht - CDE-Abfragen scheinen lang und schwer zu verstehen.

3. Erstellen Sie Ansichten

Ähnlich wie oben, jedoch Ansichten anstelle von temporären Tabellen erstellen (wenn Sie häufig mit denselben Abfragen spielen und über eine MS SQL-Version verfügen, die indizierte Ansichten unterstützt.

Sie können Ansichten oder indizierte Ansichten für eine Teilmenge von Daten erstellen, an denen Sie interessiert sind, und Abfragen für die Ansicht ausführen. Diese sollten nur eine interessante Teilmenge von Daten enthalten, die viel kleiner als die gesamte Tabelle ist.

Vorteile:

  • Leicht zu schaffen.
  • Es ist mit den Quelldaten auf dem neuesten Stand.

Nachteile:

  • Nur für definierte Teilmenge von Daten möglich.
  • Könnte für große Tabellen mit hoher Aktualisierungsrate ineffizient sein.
  • Nicht so einfach zu verwalten.
  • Kann die Gesamtsystemleistung beeinträchtigen.
  • Ich bin nicht sicher, ob indizierte Ansichten in jeder Version von MS SQL verfügbar sind.

Alle Spalten auswählen

Es ist nicht gut, star query ( SELECT * FROM) auf einem großen Tisch auszuführen ...

Wenn Sie große Spalten haben (wie lange Zeichenfolgen), dauert es viel Zeit, diese von der Festplatte zu lesen und über das Netzwerk zu übertragen.

Ich würde versuchen, durch *Spaltennamen zu ersetzen, die Sie wirklich brauchen.

Wenn Sie alle Spalten benötigen, versuchen Sie, die Abfrage in etwas wie (unter Verwendung eines gemeinsamen Datenausdrucks ) umzuschreiben :

;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 

Dirty liest

Das Letzte, was die Ad-hoc-Abfrage beschleunigen könnte, ist das Zulassen von Dirty Reads mit TabellenhinweisenWITH (NOLOCK) .

Anstelle eines Hinweises können Sie die Transaktionsisolationsstufe so einstellen , dass sie nicht festgeschrieben lautet:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

oder legen Sie die richtige SQL Management Studio-Einstellung fest.

Ich gehe davon aus, dass für Ad-hoc-Anfragen Dirty Reads gut genug sind.


2
+1 für SELECT *- zwingt SQL Server, den Clustered-Index zu verwenden. Zumindest sollte es so sein. Ich sehe keinen wirklichen Grund für einen nicht gruppierten Deckungsindex ... der die gesamte Tabelle abdeckt :)
ta.speot.is

4
Diese Antwort befasst sich nur mit der Verbesserung der Geschwindigkeit der Beispielabfrage, NICHT mit der Frage, ob "es möglich ist, die Leistung für Ad-hoc-Abfragen zu verbessern"
Phil

CDE ist jetzt CTE (Common Table Expression)
sqluser

12

Dort wird ein Tabellenscan durchgeführt. Dies bedeutet, dass für Sie kein Index definiert er101_upd_date_isoist. Wenn diese Spalte Teil eines vorhandenen Index ist, kann der Index nicht verwendet werden (möglicherweise handelt es sich nicht um die primäre Indexerspalte).

Das Hinzufügen fehlender Indizes trägt zur unbegrenzten Leistung bei.

Es gibt bereits Indizes für die Spalten, die am häufigsten abgefragt werden

Das bedeutet nicht, dass sie in dieser Abfrage verwendet werden (und wahrscheinlich auch nicht).

Ich empfehle, die Ursachen für schlechte Leistung in SQL Server von Gail Shaw, Teil 1 und Teil 2 , zu lesen .


Mein Punkt ist, dass dies nicht eine der Spalten ist, die am häufigsten abgefragt wird :)
Lee Tickett

1
@LeeTickett - Dies ist jedoch die einzige Spalte, für die Sie möglicherweise einen Index hinzufügen können, um die Leistung dieser Abfrage zu verbessern .
Oded

2
Optimierte nicht indizierte Suchvorgänge gibt es nicht. Sie verwenden entweder einen Index oder einen vollständigen Tabellenscan. Wenn Sie keine vollständigen Tabellenscans wünschen, benötigen Sie Indizes. Abhängig von Ihrem Nutzungsprofil ist es möglicherweise billig genug, nur Indizes hinzuzufügen. Oh, und der Datentyp der Spalte ist auch wichtig. Wenn er101_upd_date_isoes sich um einen großen Varchar oder einen Int handelt, ändert sich die Leistung erheblich.
Zylindrisch

Vielen Dank. Ich habe der Frage einen Kommentar hinzugefügt. Ich schätze, dass es unwahrscheinlich ist, dass ich die Abfrage optimieren kann - aber ich stelle mir vor, dass es Möglichkeiten gibt, die Leistung von Ad-hoc-Abfragen zu verbessern
Lee Tickett,

Ich lerne, woher wissen wir, welche Spalte indiziert werden muss?
Virus

7

In der Frage wird speziell angegeben, dass die Leistung für Ad-hoc- Abfragen verbessert werden muss und dass keine Indizes hinzugefügt werden können. Was kann man also tun, um die Leistung an einem Tisch zu verbessern?

Da wir Ad-hoc-Abfragen berücksichtigen, können die WHERE-Klausel und die ORDER BY-Klausel eine beliebige Kombination von Spalten enthalten. Dies bedeutet, dass fast unabhängig davon, welche Indizes für die Tabelle platziert werden, einige Abfragen erforderlich sind, die einen Tabellenscan erfordern, wie oben im Abfrageplan einer Abfrage mit schlechter Leistung dargestellt.

Nehmen wir vor diesem Hintergrund an, dass in der Tabelle außer einem Clustered-Index für den Primärschlüssel überhaupt keine Indizes vorhanden sind. Lassen Sie uns nun überlegen, welche Optionen wir haben, um die Leistung zu maximieren.

  • Defragmentieren Sie den Tisch

    Solange wir einen Clustered-Index haben, können wir die Tabelle mit DBCC INDEXDEFRAG (veraltet) oder vorzugsweise ALTER INDEX defragmentieren . Dadurch wird die Anzahl der zum Scannen der Tabelle erforderlichen Festplattenlesevorgänge minimiert und die Geschwindigkeit verbessert.

  • Verwenden Sie die schnellstmöglichen Festplatten. Sie sagen nicht, welche Festplatten Sie verwenden, sondern ob Sie SSDs verwenden können.

  • Tempdb optimieren. Legen Sie tempdb auf die schnellstmöglichen Festplatten, wieder SSDs. Siehe diesen SO-Artikel und diesen RedGate-Artikel .

  • Wie in anderen Antworten angegeben, gibt die Verwendung einer selektiveren Abfrage weniger Daten zurück und sollte daher schneller sein.

Lassen Sie uns nun überlegen, was wir tun können, wenn wir Indizes hinzufügen dürfen.

Wenn wir nicht über Ad-hoc-Abfragen sprechen würden, würden wir Indizes speziell für die begrenzte Anzahl von Abfragen hinzufügen, die für die Tabelle ausgeführt werden. Was kann getan werden, um die Geschwindigkeit die meiste Zeit zu verbessern, da wir über Ad-hoc- Abfragen sprechen ?

  • Fügen Sie jeder Spalte einen einzelnen Spaltenindex hinzu. Dies sollte SQL Server zumindest etwas bieten, mit dem Sie arbeiten können, um die Geschwindigkeit für die meisten Abfragen zu verbessern, ist jedoch nicht optimal.
  • Fügen Sie spezifische Indizes für die häufigsten Abfragen hinzu, damit diese optimiert werden.
  • Fügen Sie nach Bedarf zusätzliche spezifische Indizes hinzu, indem Sie auf Abfragen mit schlechter Leistung überwachen.

Bearbeiten

Ich habe einige Tests für eine "große" Tabelle mit 22 Millionen Zeilen durchgeführt. Meine Tabelle hat nur sechs Spalten, enthält aber 4 GB Daten. Mein Computer ist ein seriöser Desktop mit 8 GB RAM und einer Quad-Core-CPU und verfügt über eine einzelne Agility 3-SSD.

Ich habe alle Indizes außer dem Primärschlüssel in der ID-Spalte entfernt.

Eine ähnliche Abfrage wie das in der Frage angegebene Problem dauert 5 Sekunden, wenn der SQL Server zuerst und anschließend 3 Sekunden neu gestartet wird. Der Datenbankoptimierungsberater empfiehlt offensichtlich, einen Index hinzuzufügen, um diese Abfrage zu verbessern, mit einer geschätzten Verbesserung von> 99%. Das Hinzufügen eines Index führt zu einer Abfragezeit von effektiv Null.

Interessant ist auch, dass mein Abfrageplan mit Ihrem identisch ist (mit dem Clustered-Index-Scan), der Index-Scan jedoch 9% der Abfragekosten und die Sortierung die restlichen 91% ausmacht. Ich kann nur davon ausgehen, dass Ihre Tabelle eine enorme Datenmenge enthält und / oder Ihre Festplatten sehr langsam sind oder sich über eine sehr langsame Netzwerkverbindung befinden.


2

Selbst wenn Sie Indizes für einige Spalten haben, die in einigen Abfragen verwendet werden, zeigt die Tatsache, dass Ihre Ad-hoc-Abfrage einen Tabellenscan verursacht, dass Sie nicht über genügend Indizes verfügen, um diese Abfrage effizient abzuschließen.

Insbesondere für Datumsbereiche ist es schwierig, gute Indizes hinzuzufügen.

Wenn Sie sich nur Ihre Abfrage ansehen, muss die Datenbank alle Datensätze nach der ausgewählten Spalte sortieren, um die ersten n Datensätze zurückgeben zu können.

Führt die Datenbank auch einen vollständigen Tabellenscan ohne die order by-Klausel durch? Hat die Tabelle einen Primärschlüssel - ohne PK muss die Datenbank härter arbeiten, um die Sortierung durchzuführen?


Auf dem Tisch liegt ein Primärschlüssel. Ein Tabellenscan erscheint auch im Ausführungsplan, wenn er einfach ausgeführt wirdselect top 100 * from ER101_ACCT_ORDER_DTL
Lee Tickett

2

Wie ist das möglich? Wie kann ein Clustered-Index-Scan ohne Index in der Spalte er101_upd_date_iso verwendet werden?

Ein Index ist ein B-Baum, in dem jeder Blattknoten auf eine Reihe von Zeilen verweist (in der internen SQL-Terminologie als "Seite" bezeichnet). In diesem Fall handelt es sich bei dem Index um einen nicht gruppierten Index.

Clustered Index ist ein Sonderfall, bei dem die Blattknoten die "Reihe von Zeilen" haben (anstatt auf sie zu zeigen). deswegen...

1) Es kann nur einen Clustered-Index für die Tabelle geben.

Dies bedeutet auch, dass die gesamte Tabelle als Clustered-Index gespeichert wird. Aus diesem Grund wurde der Index-Scan anstelle eines Tabellenscans angezeigt.

2) Eine Operation, die einen Clustered-Index verwendet, ist im Allgemeinen schneller als ein Nicht-Clustered-Index

Lesen Sie mehr unter http://msdn.microsoft.com/en-us/library/ms177443.aspx

Für das Problem, das Sie haben, sollten Sie wirklich in Betracht ziehen, diese Spalte einem Index hinzuzufügen, da Sie sagten, dass das Hinzufügen eines neuen Index (oder einer Spalte zu einem vorhandenen Index) die INSERT / UPDATE-Kosten erhöht. Es ist jedoch möglicherweise möglich, einen nicht ausgelasteten Index (oder eine Spalte aus einem vorhandenen Index) zu entfernen, um ihn durch 'er101_upd_date_iso' zu ersetzen.

Wenn Indexänderungen nicht möglich sind, empfehle ich, eine Statistik zur Spalte hinzuzufügen. Dies kann zu Problemen führen, wenn die Spalten mit indizierten Spalten korrelieren

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

Übrigens erhalten Sie viel mehr Hilfe, wenn Sie das Tabellenschema von ER101_ACCT_ORDER_DTL veröffentlichen können. und die vorhandenen Indizes auch ..., wahrscheinlich könnte die Abfrage neu geschrieben werden, um einige von ihnen zu verwenden.


+1 für die Antwort. Ein Kommentar: Clustered-Indizes sind nicht immer schneller, als man aus Ihrer Antwort lesen könnte (es ist möglich, dass sie falsch verstanden werden).
Gisli

Ich glaube, ich verstehe den Unterschied zwischen dem Clustered / Non-Clustered-Index, sehe aber immer noch nicht, wie die Abfrage einer Spalte, die nicht Teil des Clustered-Index ist, durch einen Clustered-Index für andere Spalten verbessert werden kann.
Lee Tickett

In SQL Server enthält der Clustered-Index alle Spalten. Der Clustered-Index entscheidet darüber, wie die Daten auf der Disc gespeichert werden. Es ist ein bisschen schwer für mich zu erklären, aber wenn Sie sich Indizes als Baum vorstellen, ist der nicht gruppierte Index ein Baum und die unteren Blätter enthalten die Informationen, die Sie als im Index definiert definiert haben. Bei einem Clustered-Index enthalten die unteren Blätter alle Spalten in der Tabelle. Dies ist in SQL Server beabsichtigt.
Gisli

Ich verstehe das. Aber ich dachte, dass die Zweige auf den Spalten im Clustered-Index basieren. Wenn also die Spalte, die ich abfrage, nicht im Clustered-Index enthalten ist, muss sicher jeder Zweig / jedes Blatt gescannt werden?
Lee Tickett

1
Ich verstehe das nicht. Ich gehe davon aus, dass ein nicht gruppierter Index gescannt wurde, was zu vielen zufälligen E / A führte. Als Sie den Clustered-Index erstellt haben, haben Sie diese zufälligen E / A entfernt? Aber das ist eine Vermutung, ich kann keinen anderen Grund für dieses Verhalten finden, aber ich bin kein Experte.
Gisli

1

Einer der Gründe, warum Ihr 1M-Test schneller ausgeführt wurde, ist wahrscheinlich, dass sich die temporären Tabellen vollständig im Speicher befinden und nur dann auf die Festplatte übertragen werden, wenn auf Ihrem Server Speicherdruck herrscht. Sie können entweder Ihre Abfrage neu erstellen, um die Reihenfolge zu entfernen, einen guten Clustered-Index und Deckungsindex (e) hinzufügen, wie zuvor erwähnt, oder die DMV abfragen, um den E / A-Druck zu überprüfen, um festzustellen, ob Hardware zusammenhängt.

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

Ich hoffe, meine gesamte Datenbank würde im Speicher sein. Gibt es eine Möglichkeit, dies zu überprüfen oder SQL mitzuteilen, welche Tabellen im Speicher gespeichert werden sollen? Ich bin seit ein paar Tagen weg, aber wenn ich zurück bin, werde ich Ihre Anfrage versuchen - danke
Lee Tickett

Avg Task Count und Avg Pending DiskIO Count erreichten einen Höchststand von 4. Ich bin immer noch neugierig darauf, die Datenbank in den RAM zu zwingen.
Lee Tickett

0

Ich weiß, dass Sie gesagt haben, dass das Hinzufügen von Indizes keine Option ist, aber dies wäre die einzige Option, um Ihren Tabellenscan zu eliminieren. Wenn Sie einen Scan durchführen, liest SQL Server alle 2 Millionen Zeilen in der Tabelle, um Ihre Abfrage zu erfüllen.

Dieser Artikel enthält weitere Informationen, aber denken Sie daran: Suchen = gut, Scannen = schlecht.

Zweitens, können Sie die Auswahl * nicht entfernen und nur die Spalten auswählen, die Sie benötigen? Drittens keine "wo" -Klausel? Selbst wenn Sie einen Index haben, da Sie alles lesen, erhalten Sie am besten einen Index-Scan (der besser ist als ein Tabellenscan, aber keine Suche, auf die Sie abzielen sollten).


Es ist nicht wahr, dass Suchen immer besser ist als Scannen. Manchmal ist ein Scan tatsächlich effizienter. Wenn dies nicht der Fall wäre, hätte M $ keinen FORCESCAN-Abfragehinweis enthalten, der in SQL Server 2008 R2 beginnt. Weitere Informationen finden Sie hier: msdn.microsoft.com/en-us/library/ms181714(v=sql.105).aspx und sogar hier für jemanden, der einen Scan erzwingen möchte (3. Antwort von Adam Haines hat gute Informationen): social .msdn.microsoft.com / Forums / en-US / transactsql / thread /…
Solomon Rutzky

1
Zunächst einmal sind Suchvorgänge gut für punktförmige Abfragen. Zweitens eignen sich Scans für Bereichsabfragen, bei denen viele Daten abgerufen werden müssen. OLAP-Systeme würden ohne Scans nicht gut funktionieren. OLTP-Systeme würden ohne Suchvorgänge nicht gut funktionieren. Alles hat seinen Platz im großen Schema der Dinge ...
Liebling

0

Ich weiß, dass es von Anfang an eine ziemliche Zeit war ... In all diesen Antworten steckt viel Weisheit. Eine gute Indizierung ist das erste, wenn Sie versuchen, eine Abfrage zu verbessern. Na ja, fast der erste. Das Erste (sozusagen) ist, Änderungen am Code vorzunehmen, damit dieser effizient ist. Wenn also eine Abfrage ohne WHERE vorliegt oder wenn die WHERE-Bedingung nicht selektiv genug ist, gibt es nur einen Weg, um die Daten abzurufen: TABLE SCAN (INDEX SCAN). Wenn alle Spalten einer Tabelle benötigt werden, wird TABLE SCAN verwendet - keine Frage. Dies kann je nach Art der Datenorganisation ein Heap-Scan oder ein Clustered-Index-Scan sein. Die einzige letzte Möglichkeit, die Dinge zu beschleunigen (wenn überhaupt möglich), besteht darin, sicherzustellen, dass so viele Kerne wie möglich für den Scan verwendet werden: OPTION (MAXDOP 0). Ich ignoriere natürlich das Thema Speicherung

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.