sql >> Databáze >  >> RDS >> Database

Špatné návyky:Počítání řádků obtížným způsobem

[Zobrazit rejstřík všech příspěvků se špatnými návyky / osvědčenými postupy]

Jeden ze snímků v mé opakované prezentaci Bad Habits &Best Practices má název „Zneužívání COUNT(*) .“ Vidím toto zneužívání docela dost ve volné přírodě a má několik podob.

Kolik řádků v tabulce?

Obvykle vidím toto:

SELECT @count = COUNT(*) FROM dbo.tablename;

SQL Server musí spustit blokovací sken proti celé tabulce, aby mohl odvodit tento počet. To je drahé. Tyto informace jsou uloženy v zobrazeních katalogu a DMV a můžete je získat bez všech těchto I/O nebo blokování:

SELECT @count = SUM(p.rows)
  FROM sys.partitions AS p
  INNER JOIN sys.tables AS t
  ON p.[object_id] = t.[object_id]
  INNER JOIN sys.schemas AS s
  ON t.[schema_id] = s.[schema_id]
  WHERE p.index_id IN (0,1) -- heap or clustered index
  AND t.name = N'tablename'
  AND s.name = N'dbo';

(Stejné informace můžete získat z sys.dm_db_partition_stats , ale v tom případě změňte p.rows na p.row_count (yaa konzistence!). Ve skutečnosti se jedná o stejný pohled jako sp_spaceused používá k odvození počtu – a přestože je mnohem snazší psát než výše uvedený dotaz, nedoporučuji jej používat pouze k odvození počtu kvůli všem dodatečným výpočtům, které provádí – pokud tyto informace také nechcete. Všimněte si také, že používá funkce metadat, které se neřídí vaší úrovní vnější izolace, takže při volání této procedury můžete čekat na zablokování.)

Nyní je pravda, že tyto pohledy nejsou 100%, na mikrosekundu přesné. Pokud nepoužíváte haldu, spolehlivější výsledek lze získat pomocí sys.dm_db_index_physical_stats() sloupec record_count (opět konzistence!), nicméně tato funkce může mít dopad na výkon, může stále blokovat a může být dokonce dražší než SELECT COUNT(*) – musí provádět stejné fyzické operace, ale musí vypočítat další informace v závislosti na mode (jako je fragmentace, která vás v tomto případě nezajímá). Varování v dokumentaci vypráví část příběhu, relevantní, pokud používáte skupiny dostupnosti (a pravděpodobně ovlivňuje zrcadlení databáze podobným způsobem):

Pokud zadáte dotaz sys.dm_db_index_physical_stats na instanci serveru, která je hostitelem sekundární repliky, kterou lze číst AlwaysOn, můžete narazit na problém s blokováním REDO. Důvodem je to, že tento pohled dynamické správy získává zámek IS na zadané uživatelské tabulce nebo pohledu, který může blokovat požadavky vlákna REDO na zámek X v dané uživatelské tabulce nebo pohledu.

Dokumentace také vysvětluje, proč toto číslo nemusí být spolehlivé pro hromadu (a také jim poskytuje kvaziprůchod pro nekonzistenci řádků vs. záznamů):

U haldy nemusí počet záznamů vrácených touto funkcí odpovídat počtu řádků, které jsou vráceny spuštěním SELECT COUNT(*) proti haldě. Důvodem je, že řádek může obsahovat více záznamů. Například v některých situacích aktualizace může mít jeden řádek haldy záznam předávání a předávaný záznam jako výsledek operace aktualizace. Většina velkých řádků LOB je také rozdělena do více záznamů v úložišti LOB_DATA.

Takže bych se přiklonil k sys.partitions jako způsob, jak to optimalizovat, obětovat nějaký okrajový kousek přesnosti.

    "Nemohu ale používat DMV; můj počet musí být super přesný!"

    "Super přesný" počet je ve skutečnosti docela nesmyslný. Uvažujme, že vaší jedinou možností pro „superpřesný“ počet je uzamknout celou tabulku a zakázat komukoli přidávat nebo mazat jakékoli řádky (ale bez zabránění sdílenému čtení), např.:

    SELECT @count = COUNT(*) FROM dbo.table_name WITH (TABLOCK); -- not TABLOCKX!

    Takže váš dotaz hučí, skenuje všechna data a pracuje na tom "dokonalém" počtu. Mezitím jsou požadavky na zápis blokovány a čekají. Najednou, když se vrátí váš přesný počet, vaše zámky na stole se uvolní a všechny ty požadavky na zápis, které byly ve frontě a čekající, začnou odpalovat všechny druhy vložení, aktualizací a smazání vašeho stolu. Jak „super přesný“ je nyní váš počet? Stálo to za to získat „přesný“ počet, který je už tak strašně zastaralý? Pokud systém není zaneprázdněn, pak to není tak velký problém – ale pokud systém zaneprázdněn není, velmi silně bych tvrdil, že DMV budou zatraceně přesné.

    Mohli jste použít NOLOCK místo toho, ale to jen znamená, že autoři mohou měnit data, když je čtete, a vede to také k dalším problémům (nedávno jsem o tom mluvil). Je to v pořádku pro mnoho míčků, ale ne, pokud je vaším cílem přesnost. DMV budou přímo na (nebo alespoň mnohem blíže) v mnoha scénářích a dále ve velmi málo případech (ve skutečnosti mě nenapadají žádné).

    Nakonec můžete použít Read Committed Snapshot Isolation. Kendra Little má fantastický příspěvek o úrovních izolace snímků, ale zopakuji seznam upozornění, která jsem zmínil ve svém NOLOCK článek:

    • Zámky Sch-S je stále třeba používat i pod RCSI.
    • Úrovně izolace snímků používají verzování řádků v tempdb, takže je opravdu potřeba otestovat dopad tam.
    • RCSI nemůže používat efektivní skenování pořadí přidělení; místo toho uvidíte skenování rozsahu.
    • Paul White (@SQL_Kiwi) má několik skvělých příspěvků, které byste si měli přečíst o těchto úrovních izolace:
      • Přečtěte si Committed Snapshot Isolation
      • Úpravy dat v části Read Committed Snapshot Isolation
      • Úroveň izolace SNAPSHOT

    Navíc i s RCSI získání "přesného" počtu vyžaduje čas (a další zdroje v tempdb). V době, kdy je operace dokončena, je počet stále přesný? Pouze pokud se mezitím nikdo nedotkne stolu. Takže jedna z výhod RCSI (čtečky neblokují spisovatele) je promarněna.

Kolik řádků odpovídá klauzuli WHERE?

Toto je trochu jiný scénář – potřebujete vědět, kolik řádků existuje pro určitou podmnožinu tabulky. Nemůžete k tomu použít DMV, pokud není WHERE klauzule odpovídá filtrovanému indexu nebo zcela pokrývá přesný oddíl (nebo více).

Pokud je vaše WHERE klauzule je dynamická, můžete použít RCSI, jak je popsáno výše.

Pokud je vaše WHERE klauzule není dynamická, můžete použít také RCSI, ale můžete také zvážit jednu z těchto možností:

  • Filtrovaný index – například pokud máte jednoduchý filtr jako is_active = 1 nebo status < 5 , pak byste mohli vytvořit index takto:
    CREATE INDEX ix_f ON dbo.table_name(leading_pk_column) WHERE is_active = 1;

    Nyní můžete získat docela přesné počty z DMV, protože tam budou položky reprezentující tento index (stačí identifikovat index_id namísto spoléhání se na heap(0)/clustered index(1)). Musíte však zvážit některé slabiny filtrovaných indexů.

  • Indexované zobrazení – například pokud často počítáte objednávky podle zákazníků, může vám pomoci indexované zobrazení (ačkoli to prosím neberte jako obecné doporučení, že „indexovaná zobrazení zlepšují všechny dotazy!“):
    CREATE VIEW dbo.view_name
    WITH SCHEMABINDING
    AS
      SELECT 
        customer_id, 
        customer_count = COUNT_BIG(*)
      FROM dbo.table_name
      GROUP BY customer_id;
    GO
     
    CREATE UNIQUE CLUSTERED INDEX ix_v ON dbo.view_name(customer_id);

    Nyní se data v pohledu zhmotní a je zaručeno, že počet bude synchronizován s daty tabulky (existuje několik nejasných chyb, kde to není pravda, jako je tato s MERGE , ale obecně je to spolehlivé). Nyní tedy můžete získat své počty na zákazníka (nebo pro skupinu zákazníků) dotazem na zobrazení za mnohem nižší cenu dotazu (1 nebo 2 čtení):

    SELECT customer_count FROM dbo.view_name WHERE customer_id = <x>;

    Žádný oběd zdarma však neexistuje . Musíte vzít v úvahu režii údržby indexovaného pohledu a dopad, který to bude mít na zápisovou část vaší pracovní zátěže. Pokud tento typ dotazu nespouštíte příliš často, je nepravděpodobné, že by to stálo za to.

Odpovídá alespoň jeden řádek klauzuli WHERE?

To je také trochu jiná otázka. Ale často vidím toto:

IF (SELECT COUNT(*) FROM dbo.table_name WHERE <some clause>) > 0 -- or = 0 for not exists

Protože vás zjevně nezajímá skutečný počet, zajímá vás pouze to, zda existuje alespoň jeden řádek, opravdu si myslím, že byste jej měli změnit na následující:

IF EXISTS (SELECT 1 FROM dbo.table_name WHERE <some clause>)

To má alespoň šanci na zkrat před dosažením konce tabulky a téměř vždy překoná COUNT varianta (ačkoli existují případy, kdy je SQL Server dostatečně chytrý na to, aby převedl IF (SELECT COUNT...) > 0 na jednodušší IF EXISTS() ). V absolutně nejhorším případě, kdy není nalezen žádný řádek (nebo je první řádek nalezen na úplně poslední stránce skenování), bude výkon stejný.

[Zobrazit rejstřík všech příspěvků se špatnými návyky / osvědčenými postupy]


  1. SQL Firewalling snadno s ClusterControl &ProxySQL

  2. Jak ladit překročení časového limitu čekání na zámek na MySQL?

  3. Funkce rozdělení v oracle na hodnoty oddělené čárkou s automatickou sekvencí

  4. Digitální transformace:Vše začíná uvažováním dat