Jun 18, 2026, 9:47 AM

This commit is contained in:
Paweł Domański
2026-06-18 07:47:40 +00:00
parent 444b86a7c0
commit 71ddc1e1fb
+787
View File
@@ -94,3 +94,790 @@ Spotkanie kontrolne (follow-up) prawdopodobnie zostanie zaplanowane na przyszły
- Wymagane testy i ostateczna decyzja między architekturami (push vs pull). - Wymagane testy i ostateczna decyzja między architekturami (push vs pull).
- Zmiana wymaga przejścia formalnego procesu i zakomunikowania interesariuszom. - Zmiana wymaga przejścia formalnego procesu i zakomunikowania interesariuszom.
- Otwarte ryzyko: weryfikacja czy wdrożenie wymusi restart lub downtime. - Otwarte ryzyko: weryfikacja czy wdrożenie wymusi restart lub downtime.
Okej — zrobię Ci **pełny, spójny raport DBA** z całej analizy: skryptu + warningów + planu wykonania + realnych rekomendacji.
To będzie coś, co możesz praktycznie potraktować jako:
✅ dokument techniczny
✅ materiał do review / knowledge sharing
✅ podstawę do decyzji produkcyjnej
***
# 📘 RAPORT: Optymalizacja migracji kolumny (SQL Server)
## 🔍 1. Cel skryptu
Skrypt służy do:
👉 masowej zmiany typu kolumn (np. `VARCHAR(9)`)
👉 w wielu tabelach
👉 z podziałem na strategię:
| przypadek | metoda |
| ------------------ | ------------------------ |
| małe tabele (≤20M) | `ALTER COLUMN` |
| duże tabele (>20M) | migracja “shadow column” |
***
## 🧱 2. Mechanizm dla dużych tabel
Dla dużych tabel stosowany jest wzorzec:
1. `ADD new_column`
2. kopiowanie danych batchami:
```sql
UPDATE TOP (99000)
```
3. (później) drop + rename
👉 to jest **poprawny enterprise pattern**
***
# 📊 3. Diagnoza planu wykonania
Na podstawie XML i operatorów:
## ✅ Fakty
* tabela: `REPORT_XML_STACK`
* liczba wierszy: **\~52 841 100**
* typ: **HEAP (brak clustered index)**
* batch: `TOP (99000)`
* używane:
* `Table Scan`
* `Parallelism`
* `Sort`
* `Table Update`
* `Index Update`
***
## ❗ KLUCZOWY PROBLEM
### ❌ 1. FULL / LARGE TABLE SCAN
```xml
PhysicalOp="Table Scan"
```
➡️ SQL Server czyta **dużą część 52M wierszy** dla każdego batcha
👉 to jest główne wąskie gardło
***
## ❗ 2. Słaby indeks filtrowany
Masz:
```sql
ON (DUNS_NBR_NEW1)
WHERE DUNS_NBR_NEW1 IS NULL
```
### Problem:
* wszystkie wartości = `NULL`
* brak selektywności
* brak sensownego porządku
👉 indeks istnieje, ale **nie prowadzi planu**
***
## ❗ 3. Batch `TOP (99000)` bez ORDER
```sql
UPDATE TOP (99000)
```
➡️ brak deterministycznego wyboru wierszy
👉 skutki:
* sort
* niespójne batchowanie
* trudniejsza optymalizacja
***
## ⚠️ 4. Warning: CONVERT()
```
Type conversion may affect cardinality estimate
```
### Wniosek:
👉 **prawdziwy, ale drugorzędny**
bo:
* CAST jest tylko w SET
* nie w predicate
👉 NIE jest główny problem
***
## ⚙️ 4. Dodatkowe obserwacje
### Parallelism
* `Gather Streams`
* wiele wątków
* dodatkowy overhead
***
### Sort przed update
* koszt \~13 CPU w planie
* wynika z braku uporządkowania danych
***
### Heap
* brak uporządkowania fizycznego
* więcej IO
* trudniejsze updatey
***
# 🎯 4. ROOT CAUSE
Największy problem to:
> ❗ SQL Server nie ma dobrej ścieżki dostępu do „następnych 99000 rekordów do update”
czyli:
* słaby indeks
* brak ORDER
* heap
* TOP
***
# 🚀 5. STRATEGIA OPTYMALIZACJI
## 🥇 PRIORYTET 1 — popraw indeks
Zamiast:
```sql
ON (DUNS_NBR_NEW1)
```
zrób indeks **na kluczu tabeli**:
```sql
CREATE NONCLUSTERED INDEX IX_REPORT_XML_STACK_MIGRATION
ON dbo.REPORT_XML_STACK (ID) -- <- KLUCZ!
INCLUDE (DUNS_NBR)
WHERE DUNS_NBR_NEW1 IS NULL
AND DUNS_NBR IS NOT NULL;
```
### Efekt:
✅ fast seek
✅ brak full scan
✅ mniej IO
✅ brak sort
***
## 🥇 PRIORYTET 2 — zmień UPDATE
Zamiast:
```sql
UPDATE TOP (99000)
```
użyj:
### ✅ wersja CTE
```sql
;WITH batch AS
(
SELECT TOP (20000)
ID,
DUNS_NBR
FROM dbo.REPORT_XML_STACK
WHERE DUNS_NBR_NEW1 IS NULL
AND DUNS_NBR IS NOT NULL
ORDER BY ID
)
UPDATE T
SET T.DUNS_NBR_NEW1 = CAST(B.DUNS_NBR AS VARCHAR(9))
FROM dbo.REPORT_XML_STACK T
JOIN batch B ON B.ID = T.ID
OPTION (RECOMPILE);
```
***
## 🥇 PRIORYTET 3 — zmniejsz batch
Z:
```sql
TOP (99000)
```
👉 testuj:
* 10k
* 20k
* 50k
### Korzyści:
✅ mniejszy log
✅ mniej blokad
✅ bardziej stabilny runtime
***
## 🥇 PRIORYTET 4 — dodaj RECOMPILE
```sql
OPTION (RECOMPILE)
```
👉 bo dataset się zmienia (NULL → NOT NULL)
***
## 🥈 PRIORYTET 5 — kontrola parallelism
Test:
```sql
OPTION (RECOMPILE, MAXDOP 1)
```
👉 często:
* mniej overhead
* bardziej przewidywalne batchy
***
## 🥈 PRIORYTET 6 — lock strategy
### obecnie:
```sql
TABLOCK
```
### alternatywa (bardziej OLTP-friendly):
```sql
WITH (UPDLOCK, READPAST)
```
***
## 🥈 PRIORYTET 7 — statystyki
```sql
UPDATE STATISTICS dbo.REPORT_XML_STACK IX_REPORT_XML_STACK_MIGRATION;
```
👉 ważne dla planu
***
# 🧠 6. FINALNY WZORZEC (PROD READY)
```sql
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE @Rows int = 1;
WHILE @Rows > 0
BEGIN
;WITH batch AS
(
SELECT TOP (20000)
T.ID,
T.DUNS_NBR
FROM dbo.REPORT_XML_STACK AS T
WITH (INDEX(IX_REPORT_XML_STACK_MIGRATION), UPDLOCK, READPAST)
WHERE T.DUNS_NBR_NEW1 IS NULL
AND T.DUNS_NBR IS NOT NULL
ORDER BY T.ID
)
UPDATE T
SET T.DUNS_NBR_NEW1 = CAST(B.DUNS_NBR AS VARCHAR(9))
FROM dbo.REPORT_XML_STACK AS T
JOIN batch AS B
ON B.ID = T.ID
OPTION (RECOMPILE, MAXDOP 1);
SET @Rows = @@ROWCOUNT;
PRINT CONCAT('Updated rows: ', @Rows);
END
```
***
# 📉 7. Oczekiwany efekt po optymalizacji
| obszar | efekt |
| ----------- | --------------------- |
| IO | ↓ znacząco |
| CPU | ↓ (mniej sort/scan) |
| log | ↓ |
| czas batcha | ↓ |
| stabilność | ↑ |
| blokady | ↓ (jeśli bez TABLOCK) |
***
# ✅ 8. TL;DR
👉 największy problem:
> ❌ batch TOP + heap + słaby indeks → table scan
👉 najważniejsze fixy:
1. ✅ indeks na kluczu (ID) + INCLUDE
2. ✅ batchowanie po ORDER BY ID
3. ✅ mniejszy TOP
4. ✅ RECOMPILE
👉 warning o CAST:
> ️ istnieje, ale nie jest głównym problemem
Super — to jest bardzo dobry drugi kawałek Twojego frameworka 🔥
I teraz możemy zrobić **pełną analizę architektury (Step 8)**, bo to jest kluczowy moment: *odtwarzanie constraints, indeksów i obiektów* po migracji.
***
# 📘 TL;DR (co robi Step 8)
👉 Ten fragment:
* zbiera **skrypty odtwarzające** (constraints, indexy, statystyki, view-indexy)
* wrzuca je do kolejki (`Step8Queue`)
* sortuje wg kolejności zależności
* wykonuje je **sekwencyjnie przez cursor**
👉 to jest poprawny pattern typu **rebuild pipeline**
***
# 🧠 Co działa BARDZO dobrze
## ✅ 1. Kolejka wykonania (Step8Queue)
To jest bardzo dobry design:
```sql
CREATE TABLE Step8Queue (...)
```
👉 masz:
* centralny queue
* kontrolę kolejności
* możliwość debugowania
* możliwość DRY RUN
💡 to jest **bardziej kontrolowalne niż dynamiczne wykonania ad-hoc**
***
## ✅ 2. Priorytety (ExecutionOrder)
```sql
CASE ObjectType
WHEN 'PK/UNIQUE' THEN 1
WHEN 'DEFAULT' THEN 3
WHEN 'FOREIGN_KEY' THEN 5
WHEN 'INDEX' THEN 7
```
👉 bardzo ważne — bo:
| obiekt | zależność |
| ------ | ----------------- |
| PK | musi być pierwszy |
| FK | potrzebują PK |
| INDEX | na końcu |
✅ to jest poprawna kolejność odbudowy
***
## ✅ 3. DryRun mode
```sql
IF @DryRun = 1
```
👉 super ważne przy takich operacjach
***
## ✅ 4. TRY/CATCH + logowanie
```sql
BEGIN TRY
EXEC ...
END TRY
BEGIN CATCH
EXEC dbo.usp_LogError
```
👉 dobrze, bo:
* nie wywala całego procesu
* masz audit
***
# ⚠️ Problemy i miejsca do poprawy (ważne)
Teraz sedno — co bym poprawił jako DBA produkcyjny:
***
# ❌ 1. Cursor = wąskie gardło (ale… tu jest OK)
Masz:
```sql
CURSOR FAST_FORWARD
```
👉 normalnie powiedziałbym: "cursor zły"
ALE w tym przypadku:
* tworzysz obiekty (DDL)
* kolejność jest ważna
* trzeba sekwencyjnie
👉 ✅ cursor jest tu **UZASADNIONY**
***
# ❌ 2. Brak jawnej transakcji per obiekt
Masz:
```sql
SET XACT_ABORT ON;
```
ALE nie masz:
```sql
BEGIN TRAN
COMMIT
```
### Problem
Jeśli obiekt:
* padnie w połowie (np. index CREATE)
* i nie rollbacknie się poprawnie
👉 możesz mieć niespójny stan
***
## ✅ Fix
Dodać per-obiekt:
```sql
BEGIN TRY
BEGIN TRAN
EXEC sp_executesql @stmt1
COMMIT
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 ROLLBACK
```
***
# ❌ 3. PRINT zamiast realnego logowania
Masz:
```sql
PRINT @OBJT1 + '---' + @stmt1
```
👉 PRINT:
* znika przy dłuższych operacjach
* tnie się
* nie jest audytowalny
***
## ✅ Fix
Zostaw PRINT, ale dodaj:
```sql
INSERT INTO MyprintLog (...)
VALUES (...)
```
PRZED wykonaniem, nie tylko po
***
# ❌ 4. Brak retry logic
DDL potrafi failować przez:
* blocking
* deadlock
* metadane
👉 obecnie:
* jeden fail = log + jedziemy dalej
***
## ✅ Fix (mini retry)
```sql
DECLARE @retry INT = 0;
WHILE @retry < 3
BEGIN
BEGIN TRY
EXEC sp_executesql @stmt1
BREAK
END TRY
BEGIN CATCH
SET @retry = @retry + 1
WAITFOR DELAY '00:00:02'
END CATCH
END
```
***
# ❌ 5. Literówka w ObjectType
Masz:
```sql
'Foregin_sweep_Key'
```
👉 może powodować:
* brak dopasowania
* złe sortowanie
***
# ❌ 6. ORDER BY w INSERT (niezdeterminowane)
```sql
INSERT INTO Step8Queue
SELECT ...
ORDER BY ...
```
👉 SQL Server ignoruje `ORDER BY` w INSERT bez TOP
***
## ✅ Fix
Sortuj tylko przy SELECT w cursorze (już robisz ✅)
***
# ❌ 7. Brak walidacji duplikatów
Nie masz:
```sql
PRIMARY KEY na Step8Queue
```
👉 możesz mieć:
* duplikaty create scriptów
* ponowne wykonanie DDL
***
## ✅ Fix
Dodaj:
```sql
ALTER TABLE Step8Queue
ADD CONSTRAINT PK_Step8Queue UNIQUE(ObjectType, ObjectName, ParentTable);
```
***
# ❌ 8. Brak kontroli zależności między tabelami
Sortowanie:
```sql
ORDER BY ExecutionOrder, ObjectType
```
ALE:
👉 dwa FK między tabelami mogą się „gryźć”
***
## ✅ advanced fix (opcjonalne)
Topologiczne sortowanie zależności FK
(albo retry loop — prostsze)
***
# ❌ 9. Możliwy błąd: VARCHAR(MAX) dla CreateScript
Może powodować:
* duże użycie tempdb / memory
👉 OK przy małej liczbie obiektów
👉 przy tysiącach → problem
***
# 🔥 Najważniejszy problem architektoniczny
## Step 8 zakłada, że wszystko się odtworzy „bez oporu”
w praktyce:
* FK mogą failować
* PK mogą failować
* index może failować
👉 i Twój flow:
```
FAIL → log → NEXT
```
czyli:
❗ możesz skończyć ze środowiskiem:
* bez FK
* bez indeksów
* częściowo odtworzonym
***
# ✅ Lepszy pattern (produkcyjny)
## podejście 2-fazowe
### FAZA 1 CREATE (niekrytyczne błędy OK)
* PK, INDEX, DEFAULT, CHECK
### FAZA 2 FK (retry loop)
```sql
WHILE EXISTS (FK do odtworzenia)
BEGIN
spróbuj odtworzyć
jeśli fail → zostaw
END
```
👉 aż nic się nie da już zrobić
***
# 📊 FINALNA OCENA
| obszar | ocena |
| ------------------ | ----- |
| architektura | ⭐⭐⭐⭐⭐ |
| bezpieczeństwo | ⭐⭐⭐⭐ |
| odporność na błędy | ⭐⭐⭐ |
| wydajność | ⭐⭐⭐⭐ |
| produkcyjność | ⭐⭐⭐⭐ |
***
# 🚀 Co bym zrobił jako DBA (konkrety)
## ✅ MUST HAVE
1. transakcja per obiekt
2. logging pre + post
3. poprawa indeksów migracji (z poprzedniego wątku)
***
## ✅ SHOULD HAVE
4. retry logic
5. eliminacja literówek
6. unique constraint na queue
***
## ✅ NICE TO HAVE
7. FK retry loop
8. dependency-aware ordering
***
# 🧠 Finalny wniosek
👉 Step 8 jest:
✅ dobrze zaprojektowany
✅ zgodny z enterprise patternem
❗ ale wymaga hardeningu pod produkcję
***
# 👍 Jeśli chcesz
Mogę Ci:
✅ przepisać cały Step 8 w wersji **production-grade (hardcore DBA)**
✅ dodać retry engine + dependency resolver
✅ zrobić wersję z checkpointami (resume after fail)
✅ dodać metryki: ile % zrobione
Powiedz tylko — zrobimy z tego narzędzie, którego będziesz używać latami 👍