Najlepsze rozwiązanie do naprawy projektu bazy danych z GUID jako kluczem podstawowym

18

Jestem po pewnym potwierdzeniu tego pomysłu naprawienia źle działającej bazy danych lub lepszej sugestii, jeśli ktoś ją posiada. Zawsze otwarci na lepsze sugestie.

Mam bardzo dużą bazę danych (ponad 20 milionów rekordów rosnących o około 1/2 miliona dziennie), które używają GUID jako PK.

Niedopatrzenie z mojej strony, ale PK jest zgrupowane na serwerze SQL i powoduje problemy z wydajnością.

Powód przewodnika - ta baza danych jest częściowo zsynchronizowana ze 150 innymi bazami danych, więc PK musiał być unikalny. Synchronizacja nie jest zarządzana przez SQL Server, ale jest zbudowany niestandardowy proces, który utrzymuje dane w synchronizacji dla wymagań systemu - wszystko na podstawie tego identyfikatora GUID.

Każda ze 150 zdalnych baz danych nie przechowuje pełnych danych przechowywanych w centralnej bazie danych SQL. przechowują tylko podzbiór danych, których faktycznie potrzebują, a wymagane dane nie są dla nich unikalne (10 ze 150 baz danych może mieć na przykład niektóre te same rekordy z baz danych innych witryn - współużytkują). Ponadto - dane są generowane w odległych lokalizacjach - nie w centralnym punkcie - stąd potrzeba GUID.

Centralna baza danych służy nie tylko do synchronizacji wszystkiego, ale zapytania od ponad 3000 użytkowników będą wykonywane względem tej bardzo dużej pofragmentowanej bazy danych. Jest to już duży problem w początkowych testach.

Na szczęście nie jesteśmy jeszcze na żywo - więc mogę wprowadzać zmiany i przestawiać je w razie potrzeby w trybie offline, co jest przynajmniej czymś.

Wydajność zdalnych baz danych nie stanowi problemu - podzbiory danych są dość małe, a baza danych zwykle nigdy nie przekracza łącznie 1 GB. Rekordy są dość regularnie przekazywane do głównego systemu i usuwane z mniejszych płyt BD, gdy nie są już potrzebne.

Wydajność centralnej bazy danych, która przechowuje wszystkie rekordy, jest żałosna - ze względu na klastrowy identyfikator GUID jako klucz podstawowy dla tak wielu rekordów. Fragmentacja indeksu jest wyłączona z wykresów.

Tak więc - myślami, aby rozwiązać problem z wydajnością, należy utworzyć nową kolumnę - Nie podpisano BIGINT TOŻSAMOŚĆ (1,1), a następnie zmienić klastrowane PK tabeli BIGINT kolumny.

Utworzyłbym unikalny indeks nieklastrowany w polu GUID, który był kluczem podstawowym.

Mniejsze zdalne 150 baz danych nie musi wiedzieć o nowej PK w bazie danych Central SQL Server - będzie ona służyć wyłącznie do organizowania danych w bazie danych i zatrzymania złej wydajności i fragmentacji.

Czy to zadziała i poprawi wydajność centralnej bazy danych SQL i zapobiegnie przyszłej fragmentacji indeksu (do pewnego stopnia)? czy może przegapiłem tutaj coś bardzo ważnego, co podskoczy i ugryzie mnie i spowoduje jeszcze większy smutek?

Łopatki
źródło
2
@mattytommo Zgadzam się.
Paul Fleming
2
Czy przeprowadzasz defragmentację indeksu co najmniej raz w tygodniu?
Andomar
1
Czy masz coś, co ma znaczenie dla klastrowania? To znaczy, jakie zapytanie powinno być szybkie? Zdecydowanie nie będzie to skanowanie zakresu w GUID, więc zamiast po prostu wybrać autoinkrement, zastanów się, czy możesz wybrać klastrowanie optymalne pod względem czasu zapytania. Jeśli nie, to idź dalej i skorzystaj z bigint
2
@ Borik Nie jest to świetny pomysł, w oparciu o to, co ma i tempo wzrostu, wyczerpałby się intza 4255 dni (11,5 lat). Gdyby to zrobił,
obwiniłby
1
Przeciwny pogląd: jak myślisz, dlaczego typ danych GUID stanowi problem? Jest to 128-bitowa liczba całkowita. Jak myślisz, dlaczego zastąpienie go 64-bitową liczbą całkowitą (bigint) lub 32-bitową liczbą całkowitą (int) spowoduje zauważalną różnicę prędkości? Myślę, że zdecydowanie powinieneś zmienić klucz klastrowania na coś innego, aby uniknąć podziału strony prowadzącego do fragmentacji, ale nie sądzę, że powinieneś zmienić typ danych, chyba że masz pewność, że typ danych jest problemem.
Greenstone Walker,

Odpowiedzi:

8

Z pewnością NIE musisz klastrować na GUID. Jeśli masz coś, co pozwoliłoby ci jednoznacznie zidentyfikować rekordy inne niż ten GUID, sugeruję, abyś spojrzał na zbudowanie unikalnego indeksu na tym innym polu i utworzenie tego indeksu w klastrze. Jeśli nie, możesz klastrować na innych polach, nawet przy użyciu nietypowych indeksów. Takie podejście polegałoby na klastrze, jednak najlepiej ułatwia dzielenie danych i wysyłanie zapytań - jeśli więc masz pole „region” lub coś takiego, może to być kandydat do twojego schematu klastrowania.

Problemem z przejściem na a BIGINTbyłoby dodanie danych z innych baz danych i zintegrowanie ich bazy danych z centralnym sklepem. Jeśli nie jest to rozważanie - i nigdy nie będzie rozważaniem - to tak, BIGINTrozwiązałoby to problem przywrócenia równowagi indeksu.

Za kulisami, jeśli nie określisz indeksu klastrowego, SQL Server robi to samo: tworzy pole ID wiersza i odwzorowuje w nim wszystkie inne indeksy. Tak więc, robiąc to sam, rozwiązujesz go tak, jak rozwiązałby go SQL.

David T. Macknet
źródło
Jedynym naprawdę unikalnym polem w tabeli jest GUD - pozostałe kolumny nie są unikalne i istnieją kombinacje kolumn, które mogą być unikalne na początku - ale z czasem istnieje niewielka szansa, że ​​wygenerują zduplikowany rekord. Bardzo odległe, ale jest to możliwe, biorąc pod uwagę charakter danych. Czytałem, że wszystkie inne indeksy nieklastrowane odwołują się do indeksu klastrowanego w celu poprawy wydajności wyszukiwania itp. Czy nie miałbyś klastrowanego PK, ponieważ GUID miałby wpływ na wydajność? Zdaję sobie sprawę z przestrzeni i - choć martwię się - wydajność jest najważniejsza.
Roddles
Osiągnięcie wydajności, jeśli nie określisz indeksu klastrowanego, polega na tym, że SQL utworzy dla Ciebie jeden za kulisami i zamapuje na niego wszystkie pozostałe indeksy. Tak więc, w twoim przypadku, możesz uzyskać poprawę wydajności , pozwalając SQL to zrobić, ponieważ w tej chwili ciągle przetasujesz wszystkie swoje dane na dysku, aby zachować porządek sortowania, gdy porządek sortowania nie jest ważny. Będziesz potrzebował więcej miejsca do przechowywania, ale zobaczysz znaczną poprawę w przechowywaniu i minimalny / brak wpływu na pobieranie.
David T. Macknet
Więc wydaje mi się, że pytanie brzmi: jeśli nie zrobię BIGINT PK klastrowanego, a po prostu zmienię PK na GUI nieklastrowany, jakie są konsekwencje dla wydajności? W tabeli znajdują się inne indeksy nieklastrowane, które będą często przeszukiwane. Czy wpłynęłoby to na skuteczność tych wyszukiwań?
Roddles
+1 Sugeruję również pozostanie przy GUID. Bardzo trudno jest je wymienić w systemach rozproszonych. Indeks klastrowy dużej tabeli powinien być widoczny na podstawie sposobu zapytania danych.
Remus Rusanu
1
Cześć chłopaki - tylko aktualizacja - dokonałem modyfikacji i uczyniłem PK bezklastrowym na GUID, a SQL Server jest zajęty wstawianiem ponad 2 milionów rekordów do bazy danych. W tym samym czasie, gdy dane były wstawiane, mogłem przesłać zapytanie do bazy danych w celu uzyskania informacji i zapytań, które czasami przed zmianą upłynęły 10 minut, a ukończone w ciągu 1-2 sekund. Tak więc - sprawienie, że PK nie jest zgrupowane i nie przejmowanie się BIGINTEM, wydaje się działać. Wielkie dzięki za wkład wszystkich i pomoc.
Roddles
1

To wysokie zamówienie.

Pozwól, że zasugeruję podejście środkowego człowieka.

Miałem problemy z generowaniem losowych przewodników przez System.Guid.NewGuid (). (Pozwoliłem klientowi na utworzenie własnego identyfikatora GUID, zamiast polegać na bazie danych, aby utworzyć sekwencję).

Po przejściu do UuidCreateSequential po stronie klienta moja wydajność stała się DUŻO lepsza, szczególnie w przypadku INSERT.

Oto kod klienta DotNet voodoo. Jestem pewien, że skądś zastawiłem:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;


namespace MyCompany.MyTechnology
{
  public static class Guid
  {


    [DllImport("rpcrt4.dll", SetLastError = true)]
    static extern int UuidCreateSequential(out System.Guid guid);


    public static System.Guid NewGuid()
    {
      return CreateSequentialUUID();
    }


    public static System.Guid CreateSequentialUUID()
    {
      const int RPC_S_OK = 0;
      System.Guid g;
      int hr = UuidCreateSequential(out g);
      if (hr != RPC_S_OK)
        throw new ApplicationException("UuidCreateSequential failed: " + hr);
      return g;
    }


  }
}














    /*

Original Reference for Code:
http://www.pinvoke.net/default.aspx/rpcrt4/UuidCreateSequential.html


*/

/*



Text From URL above:

UuidCreateSequential (rpcrt4)

Type a page name and press Enter. You'll jump to the page if it exists, or you can create it if it doesn't.
To create a page in a module other than rpcrt4, prefix the name with the module name and a period.
. Summary
Creates a new UUID 
C# Signature:
[DllImport("rpcrt4.dll", SetLastError=true)]
static extern int UuidCreateSequential(out Guid guid);


VB Signature:
Declare Function UuidCreateSequential Lib "rpcrt4.dll" (ByRef id As Guid) As Integer


User-Defined Types:
None.

Notes:
Microsoft changed the UuidCreate function so it no longer uses the machine's MAC address as part of the UUID. Since CoCreateGuid calls UuidCreate to get its GUID, its output also changed. If you still like the GUIDs to be generated in sequential order (helpful for keeping a related group of GUIDs together in the system registry), you can use the UuidCreateSequential function.

CoCreateGuid generates random-looking GUIDs like these:

92E60A8A-2A99-4F53-9A71-AC69BD7E4D75
BB88FD63-DAC2-4B15-8ADF-1D502E64B92F
28F8800C-C804-4F0F-B6F1-24BFC4D4EE80
EBD133A6-6CF3-4ADA-B723-A8177B70D268
B10A35C0-F012-4EC1-9D24-3CC91D2B7122



UuidCreateSequential generates sequential GUIDs like these:

19F287B4-8830-11D9-8BFC-000CF1ADC5B7
19F287B5-8830-11D9-8BFC-000CF1ADC5B7
19F287B6-8830-11D9-8BFC-000CF1ADC5B7
19F287B7-8830-11D9-8BFC-000CF1ADC5B7
19F287B8-8830-11D9-8BFC-000CF1ADC5B7



Here is a summary of the differences in the output of UuidCreateSequential:

The last six bytes reveal your MAC address 
Several GUIDs generated in a row are sequential 
Tips & Tricks:
Please add some!

Sample Code in C#:
static Guid UuidCreateSequential()
{
   const int RPC_S_OK = 0;
   Guid g;
   int hr = UuidCreateSequential(out g);
   if (hr != RPC_S_OK)
     throw new ApplicationException
       ("UuidCreateSequential failed: " + hr);
   return g;
}



Sample Code in VB:
Sub Main()
   Dim myId As Guid
   Dim code As Integer
   code = UuidCreateSequential(myId)
   If code <> 0 Then
     Console.WriteLine("UuidCreateSequential failed: {0}", code)
   Else
     Console.WriteLine(myId)
   End If
End Sub




*/

POMYSŁ ALTERNATYWNY:

Jeśli twoja główna baza danych i zdalne bazy danych są „połączone” (jak w, sp_linkserver) ...... to możesz użyć głównej bazy danych jako „generator uuid”.

Nie chcesz dostawać UUIDa „jeden po drugim”, to zbyt dużo sprytu.

Ale możesz wziąć zestaw Uuida.

Poniżej znajduje się kod:

IF EXISTS (SELECT * FROM sys.objects WHERE object_id =
 OBJECT_ID(N'[dbo].[uspNewSequentialUUIDCreateRange]') AND type in (N'P',
 N'PC'))

 DROP PROCEDURE [dbo].[uspNewSequentialUUIDCreateRange]

 GO



 CREATE PROCEDURE [dbo].[uspNewSequentialUUIDCreateRange] (

 @newUUIDCount int --return

 )

 AS

 SET NOCOUNT ON

 declare @t table ( dummyid int , entryid int identity(1,1) , uuid
 uniqueidentifier default newsequentialid() )

 insert into @t ( dummyid ) select top (@newUUIDCount) 0 from dbo.sysobjects
 so with (nolock)

 select entryid , uuid from @t

 SET NOCOUNT OFF

 GO

/ *

--START TEST

 set nocount ON

 Create Table #HolderTable (entryid int , uuid uniqueidentifier )

 declare @NewUUIDCount int

 select @NewUUIDCount = 20

 INSERT INTO #HolderTable EXEC dbo.uspNewSequentialUUIDCreateRange
 @NewUUIDCount

 select * from #HolderTable

 DROP Table #HolderTable

 --END TEST CODE

* /

granadaCoder
źródło
Ciekawe - i podejście, którego nie rozważałem - przyjrzę się temu dokładniej, ponieważ wygląda to ładnie i wykonałem kilka projektów testowych. Gdybyśmy mieli 150 baz danych generujących sekwencyjne prowadnice, które są zgłaszane z powrotem do centralnej bazy danych, nie spowodowałoby to nadal fragmentacji, ponieważ prowadnice nadal byłyby dość losowe po wstawieniu do centralnej bazy danych. Chyba że oczywiście masz na myśli upuszczenie PK w klastrze i masz PK w klastrze?
Roddles
Czy 150 „zdalnych” baz danych wstawia pojedynczo? A może przenoszą dane w zbiorczych zestawach w nocy czy coś? Więc jesteś trochę między kamieniem a twardym miejscem. Używanie biginta ostatecznie zabraknie miejsca (być może) i nadal będziesz musiał uzyskać unikalną wartość w wielu bazach danych. Oto mój radykalny pomysł. Czy 150 zdalnych baz danych może uzyskać swoje UUID z centralnej usługi? To jeden pomysł. Czy 150 zdalnych baz danych jest „powiązanych” (jak w sp_addlinkedserver) z główną bazą danych? Potem mam UDF, który można rozważyć. Pokaż mi, czy mogę to znaleźć.
granadaCoder
Oto artykuł, który mówi o sequentialid's (niezwiązanym z tym, co już napisałem, myślę, że jest to interesujące) codeproject.com/Articles/388157/…
granadaCoder
0

Na podstawie Twojego opisu wybierz BIGINT. Jednak indeks GUID może nie być unikalny, ponieważ GUID i tak powinny być globalnie unikalne.

Jimbo
źródło
-1

Jeśli GUID jest przechowywany poprawnie jako unikatowy identyfikator, nie powinien mieć żadnych problemów z wydajnością ... a jeśli możesz użyć Sekwencyjnego GUID jeszcze lepiej ...

Również @mattytommo ma dobry punkt około 11,5 roku z użyciem INT ...

Borik
źródło
Tak - ale GUID jest generowany w zdalnych 150 bazach danych, a nie w bazie danych SQL Server - więc nie mogę użyć sekwencyjnego przewodnika - ale dziękuję za odpowiedź.
Roddles
W takim przypadku, moim zdaniem, twój plan jest rozsądny, zrobiłem podobną rzecz na jednym z zarządzanych przeze mnie DB, stworzyłem INT DENTITY (1,1) i ustawiłem to jako Clustered PK, a także humanitarny identyfikator danych pull up i zachowałem GUID (indeks) jako moduł śledzący, aby móc śledzić jego pochodzenie. Ale moją motywacją była bardziej oszczędność miejsca ...
Borik
Dziękujemy i dziękujemy za odpowiedzi i spostrzeżenia. :)
Roddles