Przejdź do głównej zawartości

Projektowanie schematu bazy danych z Cursor

Zespół produktowy właśnie sfinalizował specyfikację wielodostępowego systemu billingowego SaaS. Potrzebujesz kont użytkowników, organizacji, planów subskrypcji, śledzenia zużycia, faktur i ścieżki audytu. Relacje nie są oczywiste — czy użytkownik należy do jednej organizacji, czy może należeć do kilku? Czy faktury są powiązane z subskrypcjami, czy z rekordami zużycia? A wymagania wydajnościowe nie są trywialne: dashboard billingowy musi się ładować w poniżej 200ms nawet dla organizacji z 50 000 miesięcznych zdarzeń zużycia.

Zaprojektowanie schematu dobrze za pierwszym podejściem oszczędza tygodnie bolesnych migracji później. Zrobienie tego źle oznacza albo uruchamianie ALTER TABLE na produkcyjnej bazie pod obciążeniem, albo utrzymywanie wstecznie kompatybilnych skryptów migracji, od których boli głowa. Tryb Ask w Cursor jest zaskakująco dobry w wykrywaniu problemów z normalizacją, brakujących indeksów i błędów w relacjach, zanim napiszesz swoją pierwszą migrację.

  • Workflow promptowy do przekształcania wymagań biznesowych w znormalizowany schemat bazy danych z trybem Ask w Cursor
  • Prompt do generowania migracji, który produkuje pliki migracyjne Drizzle/Prisma/Knex ze wsparciem dla rollbacku
  • Technikę wykorzystania trybu Agent do generowania danych seed, które testują przypadki brzegowe twojego schematu
  • Prompt do optymalizacji indeksów, który wyłapuje zapytania N+1 i brakujące indeksy, zanim trafią na produkcję
  • Listę kontrolną modelowania danych, która zapobiega najczęstszym błędom w projektowaniu schematów

Zacznij w trybie Ask. Nie skacz od razu do tworzenia tabel — najpierw opanuj model konceptualny. AI jest doskonałe w zadawaniu pytań uściślających, które ujawniają niejednoznaczności w wymaganiach.

Ten prompt celowo unika proszenia o SQL. Etap modelu konceptualnego wyłapuje błędy takie jak “czy użytkownik powinien należeć do jednej organizacji czy wielu?”, których naprawienie po utworzeniu tabel jest kosztowne. AI zazwyczaj ujawni 3-5 niejednoznaczności, które musisz rozwiązać z zespołem produktowym przed pisaniem kodu.

Krok 2: Wygeneruj schemat z prawidłowymi ograniczeniami

Dział zatytułowany „Krok 2: Wygeneruj schemat z prawidłowymi ograniczeniami”

Gdy model konceptualny jest jasny, przełącz się na tryb Agent i wygeneruj właściwy schemat. Bądź precyzyjny co do ORM i bazy danych — ograniczenia i składnia różnią się znacząco.

Krok 3: Zwaliduj schemat względem rzeczywistych zapytań

Dział zatytułowany „Krok 3: Zwaliduj schemat względem rzeczywistych zapytań”

Schemat, który wygląda czysto na papierze, może działać fatalnie pod rzeczywistymi wzorcami zapytań. Użyj trybu Ask, aby sprawdzić schemat względem faktycznych zapytań, które twoja aplikacja będzie wykonywać.

@src/db/schema.ts
Oto 10 najczęstszych zapytań, które ta aplikacja będzie wykonywać:
1. Pobierz wszystkie organizacje, do których należy użytkownik, z jego rolą w każdej
2. Pobierz aktualną subskrypcję i szczegóły planu dla organizacji
3. Pobierz podział zużycia w tym miesiącu dla organizacji
4. Wygeneruj fakturę: sumuj zużycie per typ dla danej organizacji i miesiąca
5. Pobierz ostatnich 50 wpisów logu audytu dla organizacji
6. Znajdź wszystkie organizacje ze statusem subskrypcji past_due
7. Pobierz miesięczny trend zużycia dla organizacji z ostatnich 12 miesięcy
8. Wyszukaj użytkowników po e-mailu we wszystkich organizacjach
9. Pobierz wszystkich członków organizacji z ich rolami
10. Zlicz łączną liczbę wywołań API we wszystkich organizacjach dla danego dnia
Dla każdego zapytania:
- Napisz SQL (lub kod Drizzle query builder), który na nie odpowiada
- Wskaż, czy jakiekolwiek zapytanie będzie wolne bez indeksu, którego nie stworzyliśmy
- Oznacz każde zapytanie wymagające pełnego skanu tabeli
- Zasugeruj zmiany w schemacie, jeśli zapytanie jest nadmiernie złożone

Ten krok prawie zawsze wyłapuje brakujące indeksy. Zapytanie o trend zużycia (zapytanie 7) zazwyczaj potrzebuje indeksu złożonego na (organization_id, month), który nie był oczywisty z samego schematu.

Krok 4: Wygeneruj dane seed testujące przypadki brzegowe

Dział zatytułowany „Krok 4: Wygeneruj dane seed testujące przypadki brzegowe”

Dobre dane seed to nie losowe dane — to dane zaprojektowane do testowania ograniczeń schematu i ujawniania przypadków brzegowych.

Uruchom skrypt seed. Jeśli jakikolwiek insert zakończy się błędem naruszenia ograniczenia, którego się nie spodziewałeś, to jest problem w projekcie schematu do naprawienia teraz, a nie na produkcji.

Każda migracja powinna być odwracalna. Tryb Agent może wygenerować zarówno migrację “up”, jak i “down” w jednym przebiegu.

@src/db/schema.ts @src/db/migrations
Wygeneruj migrację dodającą pole "billing_email" do tabeli organizations:
1. Pole jest początkowo opcjonalne (nullable)
2. Dodaj migrację, która ustawia billing_email = e-mail ownera dla wszystkich istniejących organizacji
3. Następnie ustaw pole jako NOT NULL z wartością domyślną
4. Wygeneruj zarówno migrację w przód, jak i migrację rollback
5. Rollback powinien odwrócić każdy krok w odwrotnej kolejności
6. Dodaj komentarz w migracji wyjaśniający, dlaczego robimy to w 3 krokach zamiast 1
(podpowiedź: nie możesz dodać kolumny NOT NULL do tabeli z istniejącymi wierszami bez wartości domyślnej lub wypełnienia danych)
Użyj formatu migracji Drizzle Kit.

Po wypełnieniu schematu danymi seed użyj Cursor do analizy wydajności zapytań i sugestii optymalizacji.

@src/db/schema.ts @src/db/seed.ts
Dla 3 najdroższych zapytań w naszej aplikacji (generowanie faktur, trend zużycia
i pobieranie logu audytu) wykonaj następujące kroki:
1. Napisz zapytanie za pomocą query buildera Drizzle
2. Pokaż mi surowy SQL, który Drizzle generuje
3. Zasugeruj, czego powinienem szukać w wynikach EXPLAIN ANALYZE, aby potwierdzić dobrą wydajność
4. Jeśli zapytanie robi sekwencyjny skan tam, gdzie lepszy byłby skan indeksowy, zasugeruj indeks
5. Jeśli zapytanie łączy więcej niż 3 tabele, zasugeruj, czy zmaterializowany widok lub zdenormalizowana
kolumna byłyby warte kompromisu
Rekomendacje mają być praktyczne -- nie nadindeksuj.

AI sugeruje denormalizację zbyt agresywnie. Modele trenowane na treściach tutorialowych mają tendencję do rekomendowania przechowywania obliczonych wartości (jak “total_amount” na fakturach), aby uniknąć JOIN-ów. Dla systemu billingowego stwarza to ryzyka integralności danych — jeśli pozycja się zmieni, ale suma nie zostanie przeliczona, twoje faktury są nieprawidłowe. Denormalizuj tylko wtedy, gdy zmierzysz problem z wydajnością i ryzyko integralności jest akceptowalne.

Wygenerowane migracje nie są idempotentne. Jeśli migracja zawiedzie w połowie, ponowne uruchomienie nie powinno się wywalić. Dodaj sprawdzenia IF NOT EXISTS do CREATE TABLE i IF EXISTS do DROP. Poproś Agenta jawnie o uczynienie migracji idempotentymi.

Kaskady kluczy obcych usuwają za dużo danych. ON DELETE CASCADE na niewłaściwej relacji może wyczyścić całe tabele. Dla systemu billingowego prawie nic nie powinno mieć cascade-delete. Używaj RESTRICT domyślnie i CASCADE tylko dla prawdziwych relacji posiadania (jak usunięcie organizacji usuwa jej rekordy zużycia). Zawsze weryfikuj zachowanie ON DELETE w code review.

Zmiany schematu psują istniejące zapytania. Gdy zmieniasz nazwę kolumny lub typ, istniejący kod aplikacji się psuje. Użyj migracji dwufazowej: najpierw dodaj nową kolumnę, wdróż kod zapisujący do starej i nowej kolumny, potem usuń starą kolumnę w kolejnej migracji. Poproś Agenta o wygenerowanie pełnego planu dwufazowego.

AI generuje składnię specyficzną dla PostgreSQL dla SQLite. Jeśli twoje środowisko deweloperskie używa SQLite, ale produkcja PostgreSQL, napotkasz różnice w składni. Bądź precyzyjny co do docelowej bazy danych w swoim prompcie. Jeszcze lepiej, użyj abstrakcji dialektu Drizzle i testuj na tym samym silniku bazy, na którym wdrażasz.