DEV Community

nikosst
nikosst

Posted on

Χαρτογράφηση & Διάγνωση σε SQL Server: Τα 30 Queries που Χρησιμοποιώ Πάντα

SQL Server DBA Troubleshooting Playbook DMVs Blocking Waits & Performance

Κεφάλαιο Τίτλος Σκοπός (1 γραμμή)
1 Schema Discovery (Αποτύπωση Σχήματος) Χαρτογράφηση αντικειμένων της βάσης: πίνακες, views, στήλες, κλειδιά και σχέσεις.
2 Storage & Data Footprint (Χώρος και Όγκος Δεδομένων) Κατανόηση μεγέθους/όγκου: row counts, table sizes και πού συγκεντρώνεται το βάρος.
3 Indexing & Access Paths (Πρόσβαση στα Δεδομένα) Έλεγχος indexes και access paths για να εξηγείς scans/seeks/lookups και να βελτιώνεις queries.
4 Integrity & Data Quality (Ακεραιότητα και Ποιότητα) Έλεγχος constraints, uniqueness και ανίχνευση duplicates/orphans που χαλάνε correctness και performance.
5 Activity & Sessions (Τι Τρέχει Τώρα) Live εικόνα: ενεργά requests, sessions και connections για γρήγορο triage σε incident.
6 Blocking, Locks & Transactions (Concurrency) Εντοπισμός blocking chains, locks και long transactions που “παγώνουν” τη βάση χωρίς υψηλούς πόρους.
7 Waits, Bottlenecks & Root Cause (Διάγνωση Αιτίας) Ερμηνεία waits και bottlenecks (CPU/IO/log/memory/locks) και σύνδεση με μόνιμες διορθώσεις.

Μια βάση δεδομένων δεν είναι απλά “ένα μέρος που αποθηκεύουμε πίνακες”. Είναι το κεντρικό σύστημα καταγραφής της πραγματικότητας μιας εφαρμογής: πελάτες, παραγγελίες, τιμολόγια, προϊόντα, σχέσεις, κανόνες. Όταν κάτι “δεν πάει καλά” (αργεί ένα query, βγαίνουν λάθος νούμερα, εμφανίζονται διπλοεγγραφές, σβήνονται δεδομένα κατά λάθος), το πρόβλημα συχνά δεν είναι μόνο το query, είναι το μοντέλο, οι σχέσεις, οι κανόνες ακεραιότητας, τα indexes, τα στατιστικά, ή η ταυτόχρονη πρόσβαση.

Τα “τεχνικά queries” που μαζέψαμε είναι σημαντικά γιατί λειτουργούν σαν:

Χάρτης (να καταλάβεις τι υπάρχει: πίνακες, κλειδιά, σχέσεις).

Διαγνωστικά (να βρεις γιατί κάτι αργεί: waits, blocking, active requests).

Έλεγχος ποιότητας (duplicates, nulls, orphan records).

Οδηγός βελτιστοποίησης (indexes, Query Store, table sizes).

Με λίγα λόγια: είναι τα ερωτήματα που σε κάνουν να δουλεύεις σαν μηχανικός και όχι απλά “να γράφεις SELECT”.


Τι είναι “βάση δεδομένων” (με πρακτικούς όρους)

Μια βάση δεδομένων είναι ένα οργανωμένο σύνολο δεδομένων, συνήθως σε μορφή πινάκων (rows/columns), που υποστηρίζει:

  1. Αποθήκευση και ανάκτηση δεδομένων γρήγορα και αξιόπιστα
  2. Σχέσεις μεταξύ οντοτήτων (π.χ. Customer → Invoice → InvoiceLine)
  3. Ακεραιότητα: κανόνες που αποτρέπουν “παράλογα” δεδομένα
  4. π.χ. να υπάρχει invoice line για track που δεν υπάρχει
  5. Ταυτόχρονη πρόσβαση από πολλούς χρήστες/εφαρμογές (concurrency)
  6. Ασφάλεια και έλεγχος πρόσβασης (ρόλοι, δικαιώματα)
  7. Ιχνηλασιμότητα & συναλλαγές (transactions): αλλαγές είτε γίνονται όλες μαζί είτε καμία

Στο επιχειρησιακό κομμάτι, η βάση είναι η “αλήθεια”: αν το data model είναι σωστό και προστατεύεται, όλα τα υπόλοιπα (reports, APIs, dashboards) στέκονται.


Τι είναι ο SQL Server (και τι ρόλο παίζει)

Ο Microsoft SQL Server είναι ένα RDBMS (Relational Database Management System): το λογισμικό που:

  • αποθηκεύει τα δεδομένα σε database files,
  • εκτελεί T-SQL ερωτήματα,
  • διαχειρίζεται transactions και locks,
  • κρατά indexes & statistics,
  • και προσπαθεί να βρει το καλύτερο execution plan για κάθε query (query optimizer).

Στο SQL Server, δύο έννοιες είναι κομβικές:

1) Το Relational μοντέλο

Δουλεύεις με πίνακες και σχέσεις (PK/FK). Αυτό επιτρέπει καθαρή μοντελοποίηση και joins που “δένουν” σωστά τις οντότητες.

2) Ο Optimizer και τα Execution Plans

Όταν γράφεις SQL, δεν λες “πώς να το κάνει”, λες “τι θέλεις”. Ο SQL Server αποφασίζει:

  • με ποια σειρά να κάνει joins,
  • αν θα κάνει nested loops ή hash join,
  • ποιο index θα χρησιμοποιήσει,
  • αν θα κάνει parallelism,
  • πόση μνήμη θα δεσμεύσει κ.λπ.

Γι’ αυτό και μικρές αλλαγές σε join/filters μπορούν να αλλάξουν δραματικά την απόδοση.


Γιατί τα “metadata/diagnostic queries” είναι θεμέλιο

Για να γράψεις (ή να διορθώσεις) SQL σε σοβαρό επίπεδο, πρέπει να μπορείς να απαντήσεις:

  • Τι σχήμα έχω; (πίνακες/columns/PK/FK)
  • Τι δεδομένα έχω; (row counts, nulls, duplicates)
  • Υπάρχουν σπασμένες σχέσεις; (orphans, missing references)
  • Γιατί αργεί; (active requests, waits, blocking)
  • Τι μπορώ να βελτιώσω; (indexes, Query Store insights)
  • Πού μεγαλώνει η βάση; (table sizes)

Αν δεν έχεις αυτά, κάνεις debugging “στα τυφλά”.


1: Schema Discovery (Αποτύπωση Σχήματος)

Σε κάθε DBA-ish διερεύνηση, το πρώτο βήμα είναι να ξέρεις “τι υπάρχει”. Το schema discovery σου δίνει τον χάρτη της βάσης: πίνακες, views, στήλες, κλειδιά και σχέσεις. Χωρίς αυτό, κάθε προσπάθεια troubleshooting γίνεται στα τυφλά, γιατί δεν ξέρεις ποια αντικείμενα είναι core, ποια είναι staging/logging, πώς συνδέονται οι οντότητες και ποια είναι τα κρίσιμα joins που θα επηρεάσουν τόσο την ορθότητα όσο και την απόδοση.

Schema Discovery — Query #1: Λίστα πινάκων

SELECT
  s.name AS schema_name,
  t.name AS table_name
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
ORDER BY s.name, t.name;

Enter fullscreen mode Exit fullscreen mode

Αυτό το ερώτημα σου δίνει μια καθαρή απογραφή των πινάκων της βάσης, μαζί με το schema στο οποίο ανήκουν. Σε DBA-style troubleshooting είναι το πρώτο “zoom out” βήμα: πριν ψάξεις bottlenecks, locks ή κακά execution plans, χρειάζεσαι μια γρήγορη εικόνα του μοντέλου ώστε να καταλάβεις πού βρίσκονται τα δεδομένα και πώς είναι οργανωμένα.

Ανάλυση: Το sys.tables περιέχει όλους τους user tables, το sys.schemas μεταφράζει το schema_id σε όνομα schema και το ORDER BY δίνει σταθερή ταξινόμηση. Το αποτέλεσμα είναι η βάση για να συνεχίσεις σε keys, σχέσεις, indexes και μεγέθη.


Schema Discovery — Query #2: Λίστα views

SELECT
  s.name AS schema_name,
  v.name AS view_name
FROM sys.views v
JOIN sys.schemas s ON s.schema_id = v.schema_id
ORDER BY s.name, v.name;

Enter fullscreen mode Exit fullscreen mode

Τα views συχνά κρύβουν πολυπλοκότητα και “επιχειρησιακή λογική” που αλλιώς θα ήταν διάσπαρτη σε queries. Σε troubleshooting, ένα αργό report ή API endpoint πολύ συχνά χτυπά view (ή view πάνω σε view), οπότε χρειάζεσαι γρήγορη ορατότητα στο τι υπάρχει.

Ανάλυση: Το sys.views σου δείχνει όλα τα views της βάσης και με sys.schemas παίρνεις το πλήρες όνομα. Από εδώ ξεκινάς για να κάνεις trace ποια views είναι “βαριά” και να ανοίξεις definitions ή να ελέγξεις dependencies.


Schema Discovery — Query #3: Columns + τύποι + nullability

SELECT
  s.name  AS schema_name,
  t.name  AS table_name,
  c.column_id,
  c.name  AS column_name,
  ty.name AS data_type,
  c.max_length,
  c.precision,
  c.scale,
  c.is_nullable
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
JOIN sys.types ty  ON ty.user_type_id = c.user_type_id
ORDER BY s.name, t.name, c.column_id;

Enter fullscreen mode Exit fullscreen mode

Αυτό είναι το “data dictionary lite” που χρειάζεσαι για να καταλάβεις τι δεδομένα υπάρχουν και σε τι μορφή, χωρίς να ανοίγεις table designer ή να ψάχνεις αντικείμενα ένα-ένα. Είναι πολύ χρήσιμο όταν πρέπει να κάνεις root-cause σε conversion errors, implicit casts, λάθος joins και γενικά σε query regressions.

Ανάλυση: Το query ενώνει sys.tables, sys.columns και sys.types ώστε για κάθε πίνακα να πάρεις τις στήλες με σειρά (column_id) και τα βασικά χαρακτηριστικά τους. Το max_length/precision/scale σε βοηθά να εντοπίσεις ύποπτες ασυμφωνίες τύπων (π.χ. NVARCHAR(50) joined με NVARCHAR(100) ή numeric mismatch) που οδηγούν σε scans ή spills.


Schema Discovery — Query #4: Identity, computed, default constraints

SELECT
  s.name AS schema_name, t.name AS table_name,
  c.column_id, c.name AS column_name,
  ty.name AS data_type,
  c.is_identity, c.is_computed,
  dc.definition AS default_definition
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.columns c ON c.object_id = t.object_id
JOIN sys.types ty ON ty.user_type_id = c.user_type_id
LEFT JOIN sys.default_constraints dc
  ON dc.parent_object_id = t.object_id
 AND dc.parent_column_id = c.column_id
ORDER BY s.name, t.name, c.column_id;

Enter fullscreen mode Exit fullscreen mode

Πολύ συχνά προβλήματα “περίεργων” values ή απρόσμενων inserts οφείλονται σε defaults ή computed columns, ενώ τα identity columns επηρεάζουν inserts, replication patterns και index design. Αυτό το query δίνει καθαρή εικόνα για αυτά τα “κρυφά” χαρακτηριστικά.

Ανάλυση: Με LEFT JOIN στα sys.default_constraints φέρνεις τη default έκφραση, ενώ τα flags is_identity και is_computed σε ενημερώνουν αν η στήλη γεμίζει αυτόματα. Στο troubleshooting είναι χρήσιμο για να εξηγήσεις γιατί ένα πεδίο παίρνει τιμές που “δεν γράφει κανείς” ή γιατί κάποια inserts σπάνε.


Schema Discovery — Query #5: Primary Keys ανά πίνακα

SELECT
  s.name AS schema_name,
  t.name AS table_name,
  kc.name AS pk_name,
  c.name AS column_name,
  ic.key_ordinal
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.key_constraints kc
  ON kc.parent_object_id = t.object_id AND kc.type = 'PK'
JOIN sys.index_columns ic
  ON ic.object_id = t.object_id AND ic.index_id = kc.unique_index_id
JOIN sys.columns c
  ON c.object_id = t.object_id AND c.column_id = ic.column_id
ORDER BY s.name, t.name, ic.key_ordinal;

Enter fullscreen mode Exit fullscreen mode

Τα primary keys δεν είναι μόνο “κανόνας μοναδικότητας”. Είναι η βάση για joins, foreign keys, clustering strategy και πολλές φορές καθορίζουν αν το μοντέλο είναι “υγιές” ή γεμάτο duplicates και workarounds. Στην πράξη, πολλά performance issues ξεκινούν από κακή επιλογή PK.

Ανάλυση: Ο SQL Server υλοποιεί PK ως unique index, γι’ αυτό το query περνά από sys.key_constraints και μετά στους sys.index_columns για να φέρει τη σειρά των key columns. Έτσι βλέπεις αν ένα PK είναι composite, αν είναι “λογικό” και αν ταιριάζει με τα πιο συχνά join patterns.


Schema Discovery — Query #6: Foreign Keys (σχέσεις)

SELECT
  sch_from.name AS from_schema,
  t_from.name   AS from_table,
  c_from.name   AS from_column,
  fk.name       AS fk_name,
  sch_to.name   AS to_schema,
  t_to.name     AS to_table,
  c_to.name     AS to_column
FROM sys.foreign_keys fk
JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id
JOIN sys.tables t_from ON t_from.object_id = fkc.parent_object_id
JOIN sys.schemas sch_from ON sch_from.schema_id = t_from.schema_id
JOIN sys.columns c_from ON c_from.object_id = t_from.object_id AND c_from.column_id = fkc.parent_column_id
JOIN sys.tables t_to ON t_to.object_id = fkc.referenced_object_id
JOIN sys.schemas sch_to ON sch_to.schema_id = t_to.schema_id
JOIN sys.columns c_to ON c_to.object_id = t_to.object_id AND c_to.column_id = fkc.referenced_column_id
ORDER BY from_schema, from_table, fk_name;

Enter fullscreen mode Exit fullscreen mode

Οι foreign keys είναι ο “σκελετός” της σχεσιακής ακεραιότητας και επιτρέπουν να εμπιστευτείς ότι τα joins σου περιγράφουν πραγματικές σχέσεις. Σε troubleshooting, οι ελλείψεις σε FKs οδηγούν σε orphan rows, λάθος reports και queries που αναγκάζονται να κάνουν ακριβά validations στην εφαρμογή.

Ανάλυση: Το query διαβάζει τα FK definitions και τα “σπάει” σε από-προς πίνακα/στήλη. Με αυτό φτιάχνεις πρακτικά ένα ERD σε κείμενο και μπορείς να εντοπίσεις γρήγορα αν υπάρχουν “core” σχέσεις που λείπουν ή αν ένα FK είναι composite και απαιτεί συγκεκριμένη στρατηγική indexing.


2: Storage & Data Footprint (Χώρος και Όγκος Δεδομένων)

Πριν μιλήσουμε για “αργά queries”, πρέπει να μιλήσουμε για όγκο. Η ίδια λογική ερώτηση μπορεί να είναι ακαριαία σε 10.000 rows και αργή σε 200 εκατομμύρια. Σε αυτό το κεφάλαιο κοιτάμε πόσο “μεγάλη” είναι πραγματικά η βάση: row counts, sizes, και πού συγκεντρώνεται το βάρος. Είναι το σημείο που συνδέει το λογικό μοντέλο με την πραγματικότητα του storage και εξηγεί γιατί κάποιες λειτουργίες (backups, index maintenance, reports) αρχίζουν να πονάνε όσο το σύστημα μεγαλώνει.

Storage & Data Footprint — Query #7: Row counts ανά πίνακα (γρήγορο)

SELECT
  s.name AS schema_name,
  t.name AS table_name,
  SUM(p.rows) AS row_count
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.partitions p
  ON p.object_id = t.object_id AND p.index_id IN (0,1)
GROUP BY s.name, t.name
ORDER BY row_count DESC;

Enter fullscreen mode Exit fullscreen mode

Όταν κάτι αργεί, το πρώτο πρακτικό ερώτημα είναι “πόσο μεγάλο είναι αυτό που χτυπάω”. Ένα απλό row-count inventory σε κατευθύνει αμέσως στους μεγάλους πίνακες που είναι πιθανότεροι ύποπτοι για scans, μεγάλο IO και βαριά maintenance.

Ανάλυση: Τα row counts έρχονται από sys.partitions για heap (0) και clustered index (1), κάτι που δίνει γρήγορη εικόνα χωρίς να κάνεις COUNT(*) σε κάθε table. Είναι estimate-accurate για troubleshooting και ιδανικό για triage.


Storage & Data Footprint — Query #8: Μέγεθος πινάκων (MB)

SELECT
  s.name AS schema_name,
  t.name AS table_name,
  SUM(p.rows) AS row_count,
  CAST(SUM(a.total_pages) * 8.0 / 1024 AS DECIMAL(18,2)) AS total_mb,
  CAST(SUM(a.used_pages)  * 8.0 / 1024 AS DECIMAL(18,2)) AS used_mb
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.indexes i ON i.object_id = t.object_id
JOIN sys.partitions p ON p.object_id = t.object_id AND p.index_id = i.index_id
JOIN sys.allocation_units a ON a.container_id = p.partition_id
GROUP BY s.name, t.name
ORDER BY total_mb DESC;

Enter fullscreen mode Exit fullscreen mode

Το storage growth είναι από τα πιο συχνά “σιωπηλά” προβλήματα: ξαφνικά backups, restores, index rebuilds και queries γίνονται πιο αργά απλώς επειδή ο όγκος μεγάλωσε. Αυτό το query σου δείχνει ποιοι πίνακες τρώνε χώρο και σε ποια κλίμακα.

Ανάλυση: Η μέτρηση βασίζεται σε pages, όπου κάθε page είναι 8KB, και έτσι μετατρέπεις σε MB. Με αυτό βλέπεις όχι μόνο rows αλλά και “βάρος” που μπορεί να οφείλεται σε wide rows, LOB columns, ή πολλά indexes.


Storage & Data Footprint — Query #9: Indexes ανά πίνακα (τύπος/uniqueness)

SELECT
  s.name AS schema_name, t.name AS table_name, i.name AS index_name,
  i.type_desc, i.is_unique, i.is_primary_key, i.is_disabled, i.fill_factor
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.indexes i ON i.object_id = t.object_id
WHERE i.index_id > 0
ORDER BY s.name, t.name, i.index_id;

Enter fullscreen mode Exit fullscreen mode

Τα indexes είναι σχεδόν πάντα η διαφορά ανάμεσα σε millisecond query και minute-long scan. Παράλληλα, πολλά ή λάθος indexes αυξάνουν write κόστος, fragmentation, και maintenance time. Ένα inventory σε προστατεύει από το να βελτιώνεις “στα τυφλά”.

Ανάλυση: Το query εμφανίζει τον τύπο index (clustered/nonclustered), αν είναι unique/PK, αν είναι disabled και το fill factor. Είναι το baseline για να καταλάβεις αν ένας πίνακας έχει indexing strategy ή αν παλεύει με heaps και ad-hoc indexes.


Storage & Data Footprint — Query #10: Index columns (key vs included)

SELECT
  s.name AS schema_name, t.name AS table_name, i.name AS index_name,
  ic.key_ordinal, ic.is_included_column,
  c.name AS column_name
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.indexes i ON i.object_id = t.object_id
JOIN sys.index_columns ic ON ic.object_id = t.object_id AND ic.index_id = i.index_id
JOIN sys.columns c ON c.object_id = t.object_id AND c.column_id = ic.column_id
WHERE i.index_id > 0
ORDER BY s.name, t.name, i.name, ic.is_included_column, ic.key_ordinal;

Enter fullscreen mode Exit fullscreen mode

Για να κρίνεις αν ένα index “βοηθάει” ένα query, πρέπει να ξέρεις τη δομή του: ποια columns είναι στο key (χρήσιμα για seek/sort/join) και ποια είναι included (χρήσιμα για covering). Αυτό το query σου δίνει ακριβώς αυτή την εικόνα.

Ανάλυση: key_ordinal δίνει σειρά key columns, ενώ το is_included_column ξεχωρίζει τις included. Έτσι μπορείς να εξηγήσεις γιατί ένας optimizer δεν κάνει seek (λάθος leading column) ή γιατί κάνει key lookups (λείπουν include columns).


Storage & Data Footprint — Query #11: Index usage stats (unused indexes)

SELECT
  s.name AS schema_name,
  t.name AS table_name,
  i.name AS index_name,
  COALESCE(us.user_seeks,0)  AS user_seeks,
  COALESCE(us.user_scans,0)  AS user_scans,
  COALESCE(us.user_lookups,0) AS user_lookups,
  COALESCE(us.user_updates,0) AS user_updates
FROM sys.tables t
JOIN sys.schemas s ON s.schema_id = t.schema_id
JOIN sys.indexes i ON i.object_id = t.object_id AND i.index_id > 0
LEFT JOIN sys.dm_db_index_usage_stats us
  ON us.database_id = DB_ID()
 AND us.object_id = i.object_id
 AND us.index_id = i.index_id
ORDER BY user_updates DESC, user_seeks + user_scans + user_lookups ASC;

Enter fullscreen mode Exit fullscreen mode

Τα “άχρηστα” indexes είναι hidden tax: κάνουν inserts/updates/deletes πιο ακριβά και αυξάνουν storage, χωρίς να δίνουν value στις αναγνώσεις. Αυτή η εικόνα είναι απαραίτητη όταν θες να κάνεις cleanup ή να εξηγήσεις γιατί το write workload πονάει.

Ανάλυση: Τα usage stats δείχνουν πώς χρησιμοποιήθηκαν indexes από το τελευταίο restart/clear. Συνδυάζοντας χαμηλή χρήση με υψηλά updates μπορείς να εντοπίσεις candidates για αφαίρεση ή redesign, με την προσοχή ότι τα counters μηδενίζονται σε restart.


3: Indexing & Access Paths (Πώς “βρίσκει” δεδομένα ο SQL Server)

Ο SQL Server δεν ψάχνει δεδομένα “μαγικά”—ψάχνει μέσω access paths: indexes, scans, seeks, lookups. Αυτό το κεφάλαιο είναι για να καταλάβεις τι ευρετήρια υπάρχουν, πώς είναι δομημένα (key vs included columns) και αν εξυπηρετούν το workload ή απλώς επιβαρύνουν τα writes. Στο DBA troubleshooting, η διαφορά ανάμεσα σε ένα σωστό covering index και ένα τυχαίο nonclustered index είναι συχνά η διαφορά ανάμεσα σε σταθερή απόδοση και διαρκείς φωτιές.

Integrity & Data Quality — Query #12: Check constraints

SELECT
  s.name AS schema_name, t.name AS table_name,
  cc.name AS constraint_name, cc.definition
FROM sys.check_constraints cc
JOIN sys.tables t ON t.object_id = cc.parent_object_id
JOIN sys.schemas s ON s.schema_id = t.schema_id
ORDER BY s.name, t.name, cc.name;

Enter fullscreen mode Exit fullscreen mode

Οι check constraints είναι η πρώτη γραμμή άμυνας απέναντι σε “παράλογα” δεδομένα που μετά γίνονται εφιάλτης στο reporting και στο troubleshooting. Όταν υπάρχουν, ξέρεις ότι κάποια business rules επιβάλλονται στη βάση και όχι μόνο στον κώδικα.

Ανάλυση: Το query εμφανίζει την έκφραση constraint, άρα μπορείς να δεις τι ακριβώς απαγορεύεται ή απαιτείται. Σε incident όπου “περίεργες τιμές” πέρασαν, αυτό σε βοηθά να δεις αν έλειπε constraint ή αν ήταν disabled/not trusted.


Integrity & Data Quality — Query #13: Unique constraints

SELECT
  s.name AS schema_name,
  t.name AS table_name,
  kc.name AS constraint_name
FROM sys.key_constraints kc
JOIN sys.tables t ON t.object_id = kc.parent_object_id
JOIN sys.schemas s ON s.schema_id = t.schema_id
WHERE kc.type = 'UQ'
ORDER BY s.name, t.name, kc.name;

Enter fullscreen mode Exit fullscreen mode

Οι unique constraints προστατεύουν against duplicates σε business keys, κάτι που αλλιώς οδηγεί σε διπλές χρεώσεις, διπλές εγγραφές και “ανεξήγητα” αποτελέσματα σε joins. Για DBA-ish troubleshooting, είναι σημαντικό να ξέρεις ποια μοναδικότητα εγγυάται η βάση.

Ανάλυση: Στο SQL Server τα UQ constraints υλοποιούνται επίσης με unique indexes, αλλά η ύπαρξή τους δηλώνει πρόθεση και κανόνα. Αυτό το inventory σε βοηθά να δεις αν η μοναδικότητα που υποθέτουν οι εφαρμογές όντως επιβάλλεται.


Integrity & Data Quality — Query #14: Orphans (γενικό μοτίβο για FK-less μοντέλα)

-- Παράδειγμα: InvoiceLine που δείχνει σε Invoice που δεν υπάρχει
SELECT il.*
FROM dbo.InvoiceLine il
LEFT JOIN dbo.Invoice i ON i.InvoiceId = il.InvoiceId
WHERE i.InvoiceId IS NULL;

Enter fullscreen mode Exit fullscreen mode

Όταν λείπουν foreign keys ή όταν έγιναν imports/διαγραφές εκτός ελέγχου, δημιουργούνται orphan rows που καταστρέφουν joins και reports. Αυτό το μοτίβο σε βοηθά να εντοπίσεις γρήγορα “σπασμένες σχέσεις” ακόμα κι αν δεν υπάρχουν constraints.

Ανάλυση: Το LEFT JOIN ... WHERE parent IS NULL είναι κλασικό anti-join για orphans. Αν επιστρέψει rows, έχεις data integrity problem που επηρεάζει τόσο correctness όσο και performance, γιατί πολλές query assumptions καταρρέουν.


Integrity & Data Quality — Query #15: Duplicates σε business key (template)

-- Άλλαξε Table/Columns
SELECT col1, col2, COUNT(*) AS cnt
FROM dbo.YourTable
GROUP BY col1, col2
HAVING COUNT(*) > 1
ORDER BY cnt DESC;

Enter fullscreen mode Exit fullscreen mode

Τα duplicates είναι από τις πιο ύπουλες αιτίες λάθους: ένα join ξαφνικά πολλαπλασιάζει γραμμές, ένα aggregation ανεβαίνει χωρίς προφανή λόγο και κανείς δεν ξέρει γιατί. Αυτό το query είναι βασικό εργαλείο για να αποδείξεις ότι το πρόβλημα είναι στο data, όχι στο report.

Ανάλυση: Με GROUP BY στο business key και HAVING COUNT(*) > 1 εντοπίζεις πολλαπλές εγγραφές που θα έπρεπε να είναι μοναδικές. Μετά το troubleshooting μετακινείται στο “γιατί δημιουργήθηκαν” και “πώς τις διορθώνουμε” με constraints ή ETL αλλαγές.


4: Integrity & Data Quality (Ακεραιότητα και Ποιότητα Δεδομένων)

Πολλά “performance προβλήματα” ξεκινούν ως “data προβλήματα”. Duplicates πολλαπλασιάζουν joins, orphans χαλάνε aggregations, και η απουσία constraints μεταφέρει την ευθύνη ακεραιότητας στην εφαρμογή, όπου συνήθως χάνεται. Σε αυτό το κεφάλαιο εξετάζουμε τα θεμέλια: constraints, uniqueness, check rules και μοτίβα εντοπισμού σπασμένων σχέσεων. Είναι το κομμάτι που σε βοηθά να ξεχωρίσεις αν ένα bug είναι query bug ή δεδομένων—και να το τεκμηριώσεις.

Activity & Sessions — Query #16: Ενεργά requests τώρα (top)

SELECT TOP (30)
  r.session_id, r.status, r.command,
  r.cpu_time, r.total_elapsed_time,
  r.logical_reads, r.reads, r.writes,
  r.wait_type, r.wait_time, r.last_wait_type,
  st.text
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) st
WHERE r.session_id <> @@SPID
ORDER BY r.total_elapsed_time DESC;

Enter fullscreen mode Exit fullscreen mode

Όταν κάποιος λέει “η βάση κόλλησε”, η πιο αξιόπιστη πρώτη κίνηση είναι να δεις τι τρέχει αυτή τη στιγμή και ποιο είναι το βαρύτερο. Αυτό το query είναι το real-time triage εργαλείο που σε πάει από τη φήμη στο γεγονός.

Ανάλυση: Οι μετρικές cpu_time, logical_reads, reads/writes και wait_type σε βοηθούν να καταλάβεις αν το bottleneck είναι CPU, IO, logging ή blocking. Το sql_text σου λέει ποιο statement είναι υπεύθυνο και είναι το κλειδί για το επόμενο βήμα (plan, indexes, query rewrite).


Activity & Sessions — Query #17: Session details (who/where/program)

SELECT
  s.session_id,
  s.status,
  s.login_name,
  s.host_name,
  s.program_name,
  s.open_transaction_count,
  s.last_request_start_time,
  s.last_request_end_time
FROM sys.dm_exec_sessions s
WHERE s.is_user_process = 1
ORDER BY s.open_transaction_count DESC, s.session_id DESC;

Enter fullscreen mode Exit fullscreen mode

Το “ποιος κάνει τι” είναι κρίσιμο όταν έχεις spikes ή ανεξήγητη δραστηριότητα. Με αυτό το query βλέπεις από ποιο πρόγραμμα (SSMS, app, job) και από ποιο host έρχονται sessions, ώστε να κάνεις σωστό scoping του incident.

Ανάλυση: Η πληροφορία αυτή δεν είναι performance metric αλλά attribution. Το open_transaction_count και τα last request timestamps βοηθούν να ξεχωρίσεις idle sessions από sessions που κρατούν transactions ανοιχτά ή κάνουν poll.


Activity & Sessions — Query #18: Connections (client address)

SELECT
  c.session_id,
  c.client_net_address,
  c.connect_time,
  c.net_transport,
  c.protocol_type,
  c.encrypt_option
FROM sys.dm_exec_connections c
ORDER BY c.connect_time DESC;

Enter fullscreen mode Exit fullscreen mode

Σε περιβάλλοντα με VPN, load balancers και πολλαπλές εφαρμογές, το να ξέρεις από πού έρχονται τα connections είναι πολύ χρήσιμο. Αυτό το query βοηθά όταν θες να αποδώσεις προβλήματα σε συγκεκριμένο client subnet ή σε αλλαγές transport/encryption.

Ανάλυση: Το client_net_address και τα transport fields δείχνουν το network path όπως το βλέπει ο SQL Server. Δεν λύνει μόνο του performance, αλλά είναι ισχυρό στοιχείο όταν κάνεις correlation με network incidents.


5: Activity & Sessions (Τι τρέχει τώρα και από ποιον)

Όταν “κάτι κολλάει”, η πιο χρήσιμη πληροφορία είναι η ζωντανή: τι τρέχει αυτή τη στιγμή, πόση ώρα, από ποιο πρόγραμμα και ποιος χρήστης το ξεκίνησε. Αυτό το κεφάλαιο είναι το triage kit σου: DMVs για ενεργά requests, sessions και connections. Εδώ μαθαίνεις να περνάς από το “νιώθω ότι αργεί” στο “αυτό το session εκτελεί αυτό το query, περιμένει σε αυτό το wait, με αυτά τα reads”.

Blocking / Locks / Transactions — Query #19: Who is blocking whom

SELECT
  r.session_id,
  r.blocking_session_id,
  r.status,
  r.wait_type,
  r.wait_time,
  r.total_elapsed_time,
  st.text AS running_sql
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) st
WHERE r.blocking_session_id <> 0
ORDER BY r.wait_time DESC;

Enter fullscreen mode Exit fullscreen mode

Το blocking είναι η πιο “χειροπιαστή” αιτία καθυστέρησης γιατί δεν χρειάζεται καν υψηλό CPU ή IO για να παραλύσουν τα πάντα. Αυτό το query σου δείχνει άμεσα ποιος περιμένει ποιον, ώστε να εντοπίσεις τη ρίζα της αλυσίδας.

Ανάλυση: Το blocking_session_id σε βγάζει από το guessing και σε πάει κατευθείαν στον blocker. Το wait_type και το wait_time βοηθούν να επιβεβαιώσεις ότι όντως πρόκειται για locking waits και να ξεκινήσεις drill-down στον blocker για το τι transaction κρατά.


Blocking / Locks / Transactions — Query #20: Locks inventory

SELECT
  tl.request_session_id AS session_id,
  tl.resource_type,
  tl.request_mode,
  tl.request_status,
  tl.resource_database_id,
  DB_NAME(tl.resource_database_id) AS db_name
FROM sys.dm_tran_locks tl
WHERE tl.resource_database_id = DB_ID()
ORDER BY tl.request_session_id, tl.request_status DESC;

Enter fullscreen mode Exit fullscreen mode

Τα locks είναι φυσιολογικό μέρος του ACID μοντέλου, αλλά όταν η διάρκεια τους μεγαλώνει, μετατρέπονται σε bottleneck. Με αυτό το query βλέπεις τι locks ζητούνται/κρατιούνται και σε ποιο mode, ώστε να καταλάβεις αν έχεις escalation ή “βαριές” αποκλειστικότητες.

Ανάλυση: Το output δείχνει resource types και lock modes, που είναι η γλώσσα του SQL Server για concurrency. Αν δεις πολλά waiting locks ή εκτεταμένα X locks σε hot tables, έχεις πιθανό contention pattern που χρειάζεται αλλαγή σε indexes, isolation ή query design.


Blocking / Locks / Transactions — Query #21: Open transactions (ηλικία)

SELECT
  s.session_id,
  s.login_name,
  s.host_name,
  s.program_name,
  at.transaction_begin_time,
  DATEDIFF(SECOND, at.transaction_begin_time, SYSDATETIME()) AS tran_age_seconds
FROM sys.dm_exec_sessions s
JOIN sys.dm_tran_session_transactions st ON st.session_id = s.session_id
JOIN sys.dm_tran_active_transactions at ON at.transaction_id = st.transaction_id
WHERE s.is_user_process = 1
ORDER BY tran_age_seconds DESC;

Enter fullscreen mode Exit fullscreen mode

Ένα “ξεχασμένο” transaction είναι από τα πιο κλασικά σενάρια incident, ειδικά με apps που ανοίγουν transaction και μετά περιμένουν κάτι ή κρασάρουν. Αυτό το query σου δίνει αμέσως ποιος κρατά transaction και για πόση ώρα.

Ανάλυση: Φέρνεις begin time και υπολογίζεις ηλικία σε seconds, ώστε να ξεχωρίσεις φυσιολογικά transactions από “παθολογικά” long-lived. Στην πράξη, το μεγαλύτερο tran_age_seconds σε hot table σχεδόν πάντα εξηγεί αλυσίδες blocking.


Blocking / Locks / Transactions — Query #22: KILL status (rollback progress)

-- Αν έχεις ήδη κάνει KILL <spid>, αυτό δείχνει rollback progress
KILL 56 WITH STATUSONLY;

Enter fullscreen mode Exit fullscreen mode

Το KILL δεν σημαίνει ότι “σταμάτησε αμέσως”. Αν το session είχε κάνει writes, ο SQL Server πρέπει να κάνει rollback, που μπορεί να πάρει χρόνο και να συνεχίσει να κρατά resources. Αυτό το query σε βοηθά να βλέπεις πρόοδο και να μην υποθέτεις λάθος ότι “δεν δούλεψε”.

Ανάλυση: Το STATUSONLY επιστρέφει εκτίμηση προόδου rollback χωρίς να ξαναστέλνει kill. Είναι κρίσιμο σε incident response γιατί σου λέει αν το σύστημα θα απελευθερωθεί σύντομα ή αν χρειάζεται εναλλακτική προσέγγιση.


6: Blocking, Locks & Transactions (Συγχρονισμός και Contention)

Το blocking είναι ο πιο ύπουλος τρόπος να “παγώσει” μια βάση χωρίς να δείχνει υψηλό CPU ή IO. Ένα long-running transaction, μια κακή σειρά ενημερώσεων ή ένα hot table μπορεί να δημιουργήσει αλυσίδες αναμονής που γίνονται αντιληπτές ως “η βάση δεν δουλεύει”. Σε αυτό το κεφάλαιο βλέπουμε πώς να εντοπίζεις blockers, να χαρτογραφείς lock modes και να βρίσκεις ανοιχτές συναλλαγές που κρατάνε πόρους. Είναι το σημείο που η θεωρία ACID γίνεται πρακτική διαχείριση incident.

Waits & Bottlenecks — Query #23: Top waits (instance/db context)

SELECT TOP (30)
  wait_type,
  waiting_tasks_count,
  wait_time_ms,
  max_wait_time_ms,
  signal_wait_time_ms
FROM sys.dm_os_wait_stats
ORDER BY wait_time_ms DESC;

Enter fullscreen mode Exit fullscreen mode

Ανάλυση του query/στηλών: Αυτό το ερώτημα σου δείχνει πού “χάνεται χρόνος” επειδή τα queries περιμένουν κάτι. Το wait_type είναι ο λόγος αναμονής (τι ακριβώς περιμένει), το waiting_tasks_count είναι πόσες φορές συνέβη αυτό το είδος αναμονής, το wait_time_ms είναι ο συνολικός χρόνος αναμονής (όσο πιο μεγάλο, τόσο πιο σημαντικό σαν πρόβλημα), το max_wait_time_ms δείχνει τη χειρότερη μεμονωμένη αναμονή (χρήσιμο για spikes), και το signal_wait_time_ms είναι το κομμάτι του χρόνου που το query ήταν “έτοιμο να τρέξει” αλλά περίμενε να του δοθεί CPU (άρα ένδειξη πίεσης σε CPU/πολλή ταυτόχρονη δουλειά). Το κοιτάς για να αποφασίσεις αν το βασικό σου θέμα είναι CPU, δίσκος/IO, κλειδώματα (blocking), log writes, ή κάτι άλλο—ανάλογα με το ποια wait_type βγαίνουν πρώτα.

Τα waits είναι ο πιο ξεκάθαρος τρόπος να καταλάβεις τι “φρενάρει” τη βάση, γιατί δείχνουν πού περνάει χρόνο περιμένοντας. Έτσι μπορείς να ξεχωρίσεις αν το πρόβλημα είναι έλλειψη CPU, αργό διάβασμα/γράψιμο δεδομένων, κλειδώματα από άλλες συναλλαγές, καθυστέρηση στο γράψιμο του log ή καθυστέρηση δικτύου.

Ανάλυση: Τα νούμερα είναι συγκεντρωτικά για ένα διάστημα (π.χ. από τότε που άρχισε να μετράει το σύστημα), άρα είναι πιο χρήσιμα για να δεις “τι κυριαρχεί γενικά” και όχι για ένα στιγμιαίο συμβάν. Στο troubleshooting, το σημαντικό είναι να πάρεις τα πρώτα wait types και να τα συνδέσεις με το σωστό είδος προβλήματος, ώστε να κάνεις τη σωστή επόμενη κίνηση (π.χ. να ψάξεις blocking/locks, να δεις αν η CPU είναι στο όριο, ή αν το IO είναι ο περιορισμός), αντί να μαντεύεις.


Waits & Bottlenecks — Query #24: Currently waiting tasks (live)

1) Ποια requests περιμένουν τώρα (μέσα στη βάση σου)

SELECT TOP (50)
  r.session_id,
  r.status,
  r.wait_type,
  r.wait_time AS wait_time_ms,
  r.blocking_session_id,
  r.cpu_time,
  r.total_elapsed_time,
  DB_NAME(r.database_id) AS db_name,
  SUBSTRING(st.text,
            (r.statement_start_offset/2) + 1,
            CASE WHEN r.statement_end_offset = -1
                 THEN (DATALENGTH(st.text) - r.statement_start_offset)/2 + 1
                 ELSE (r.statement_end_offset - r.statement_start_offset)/2 + 1
            END) AS running_statement
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) st
WHERE r.database_id = DB_ID()
ORDER BY r.wait_time DESC;

Enter fullscreen mode Exit fullscreen mode

Στο (1) “requests που περιμένουν τώρα” βλέπεις τι τρέχει αυτή τη στιγμή μέσα στη βάση σου και αν “κολλάει” κάπου: το session_id είναι ποιος χρήστης/connection το εκτελεί, το status αν εκτελείται ή περιμένει, το wait_type και wait_time_ms δείχνουν σε τι περιμένει και για πόση ώρα, το blocking_session_id σου λέει αν το μπλοκάρει κάποιο άλλο session (άρα πιθανό lock/blocking), ενώ τα cpu_time και total_elapsed_time σου δίνουν αίσθηση πόση CPU έχει κάψει και πόσο χρόνο τρέχει συνολικά. Το running_statement είναι το κομμάτι του SQL που εκτελείται εκείνη τη στιγμή, ώστε να ξέρεις ποιο query είναι ο “ένοχος”.

2) Locks που κρατάνε τα sessions (και σε ποιο αντικείμενο)

-- 2) Locks που κρατάνε τα sessions (και σε ποιο αντικείμενο)
SELECT TOP (200)
  tl.request_session_id AS session_id,
  tl.resource_type,
  tl.request_mode,
  tl.request_status,
  tl.resource_description,
  OBJECT_NAME(p.object_id) AS object_name
FROM sys.dm_tran_locks tl
LEFT JOIN sys.partitions p
  ON tl.resource_associated_entity_id = p.hobt_id
WHERE tl.resource_database_id = DB_ID()
ORDER BY tl.request_session_id;

Enter fullscreen mode Exit fullscreen mode

Στο (2) “locks που κρατάνε τα sessions” βλέπεις ποια sessions κρατάνε ή ζητάνε locks και πάνω σε τι: το request_session_id σου λέει ποιος τα κρατά/ζητά, το resource_type και resource_description περιγράφουν το είδος του resource (π.χ. row/page/object/KEY/HOBT), το request_mode είναι ο τύπος lock (π.χ. shared για read, exclusive για write), και το request_status αν το lock έχει δοθεί ή περιμένει. Το object_name (όταν μπορεί να αντιστοιχηθεί) σε βοηθά να καταλάβεις σε ποιον πίνακα “γίνεται η φασαρία”, ώστε να δεις αν έχεις hot table ή κακό query που κρατά locks πολλή ώρα.


3) Βρες αλυσίδες blocking (ποιος μπλοκάρει ποιον)

WITH b AS (
  SELECT
    r.session_id,
    r.blocking_session_id,
    r.wait_type,
    r.wait_time
  FROM sys.dm_exec_requests r
  WHERE r.database_id = DB_ID()
),
chain AS (
  SELECT session_id, blocking_session_id, 0 AS lvl
  FROM b
  WHERE blocking_session_id <> 0
  UNION ALL
  SELECT b.session_id, b.blocking_session_id, c.lvl + 1
  FROM b
  JOIN chain c ON b.session_id = c.blocking_session_id
  WHERE b.blocking_session_id <> 0
)
SELECT TOP (100) *
FROM chain
ORDER BY lvl DESC, session_id;

Enter fullscreen mode Exit fullscreen mode

Στο (3) “αλυσίδες blocking” βλέπεις τη δομή του μπλοκαρίσματος σαν αλυσίδα: ποια sessions περιμένουν άλλα sessions και πόσο “βαθιά” είναι η ουρά. Αν υπάρχει ένα session που μπλοκάρει πολλά άλλα, αυτό θα εμφανιστεί σαν “ρίζα” της αλυσίδας, και τα υπόλοιπα θα φαίνονται σε επίπεδα (lvl) κάτω από αυτό. Αυτό είναι χρήσιμο γιατί σε πραγματικά incidents δεν σε νοιάζει μόνο “ποιος περιμένει”, αλλά ποιος είναι ο κύριος blocker που αν λυθεί, ξεμπλοκάρουν πολλοί μαζί.

Waits & Bottlenecks — Query #25: Scheduler pressure (runnable queue)

Για CPU/IO/Memory πίεση στη βάση σου

SELECT
  end_time,
  avg_cpu_percent,
  avg_data_io_percent,
  avg_log_write_percent,
  avg_memory_usage_percent
FROM sys.dm_db_resource_stats
ORDER BY end_time DESC;

Enter fullscreen mode Exit fullscreen mode

Για το τι “περιμένει” η βάση (waits)

SELECT TOP (20)
  wait_type,
  waiting_tasks_count,
  wait_time_ms
FROM sys.dm_db_wait_stats
ORDER BY wait_time_ms DESC;

Enter fullscreen mode Exit fullscreen mode

Για τα queries που τρέχουν τώρα και περιμένουν

SELECT TOP (50)
  r.session_id,
  r.status,
  r.command,
  r.wait_type,
  r.wait_time,
  r.total_elapsed_time,
  r.cpu_time,
  DB_NAME(r.database_id) AS db_name,
  st.text AS running_sql
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) st
WHERE r.database_id = DB_ID()
ORDER BY r.total_elapsed_time DESC;

Enter fullscreen mode Exit fullscreen mode

Τα παραπάνω 3 ερωτήματα σου δίνουν την εικόνα που θα έπαιρνες από τα “runnable tasks”, αλλά σε επίπεδο βάσης: το sys.dm_db_resource_stats δείχνει αν η βάση σου χτυπάει CPU/IO/memory όρια, το sys.dm_db_wait_stats δείχνει σε τι “κολλάει” συνολικά (π.χ. CPU, locks, IO), και το sys.dm_exec_requests σου δείχνει αυτή τη στιγμή ποια queries τρέχουν/περιμένουν και με τι wait_type. Έτσι, όταν βλέπεις καθυστερήσεις ή concurrency θέματα, μπορείς να ξεχωρίσεις αν το πρόβλημα είναι ότι η βάση είναι “τερματισμένη” από CPU, αν περιμένει σε locks, ή αν περιμένει σε IO, χωρίς να χρειάζεσαι server-level δικαιώματα.

Ανάλυση: Αν στο sys.dm_db_resource_stats βλέπεις το avg_cpu_percent συχνά πολύ ψηλά, και στα wait_stats εμφανίζονται CPU-related waits (ή γενικά μεγάλος χρόνος αναμονών που ταιριάζει με CPU πίεση), τότε οι καθυστερήσεις είναι πιθανό να οφείλονται σε έλλειψη CPU/υψηλή ταυτόχρονη χρήση. Αν αντίθετα στα wait_stats κυριαρχούν lock waits και στο dm_exec_requests βλέπεις sessions να περιμένουν με wait_type σχετικό με locks, τότε το θέμα είναι μπλοκαρίσματα και πρέπει να πας σε locking/transaction patterns και indexes. Με άλλα λόγια, τα 3 queries “δένουν” το σύμπτωμα (slow requests/αναμονές) με την αιτία (CPU πίεση vs locks vs IO) και σου λένε πού να κάνεις optimization πρώτα.


7: Waits, Bottlenecks & Root Cause (Από σύμπτωμα σε αιτία)

Τα waits είναι η γλώσσα με την οποία ο SQL Server σου λέει “τι περιμένω”. Αντί να μαντεύεις αν το πρόβλημα είναι CPU, IO, log, memory grants ή locks, τα wait stats σε κατευθύνουν απευθείας στο σωστό subsystem. Σε αυτό το κεφάλαιο δένουμε την εικόνα: live waiting tasks, cumulative wait stats, scheduler pressure, και όπου υπάρχει διαθέσιμο—Query Store για ιστορικό. Ο στόχος δεν είναι να δεις απλώς ότι “αργεί”, αλλά να μπορείς να το εξηγήσεις, να το αποδείξεις και να το διορθώσεις με στοχευμένες ενέργειες.

Performance / Query Intelligence — Query #26: Query Store top queries by CPU (αν είναι enabled)

SELECT TOP (20)
  qt.query_sql_text,
  SUM(rs.avg_cpu_time * rs.count_executions) AS total_cpu
FROM sys.query_store_query q
JOIN sys.query_store_query_text qt ON qt.query_text_id = q.query_text_id
JOIN sys.query_store_plan p ON p.query_id = q.query_id
JOIN sys.query_store_runtime_stats rs ON rs.plan_id = p.plan_id
GROUP BY qt.query_sql_text
ORDER BY total_cpu DESC;

Enter fullscreen mode Exit fullscreen mode

Το Query Store είναι σαν “καταγραφικό” απόδοσης για τη βάση, γιατί κρατάει ιστορικό για το πόσο κοστίζουν τα SQL ερωτήματα με τον χρόνο. Αυτό βοηθάει στο troubleshooting επειδή σου δείχνει ποια ερωτήματα έχουν καταναλώσει τη περισσότερη CPU συνολικά μέσα στην ημέρα/εβδομάδα, όχι μόνο ποιο έτυχε να τρέχει τη στιγμή που κοιτάς. Το συγκεκριμένο query υπολογίζει ένα συνολικό μέτρο CPU πολλαπλασιάζοντας τον μέσο χρόνο CPU μιας εκτέλεσης με το πόσες φορές εκτελέστηκε, ώστε να βρει τα “βαριά” ή/και “πολύ συχνά” ερωτήματα που αξίζει να βελτιώσεις πρώτα. Μετά το τρέξιμο, διαλέγεις τα πρώτα αποτελέσματα που είναι κρίσιμα για την εφαρμογή σου, ξεχωρίζεις αν το κόστος έρχεται από πολλές εκτελέσεις ή από ακριβές εκτελέσεις, και αποφασίζεις αν θα μειώσεις τη συχνότητα (στην εφαρμογή) ή θα βελτιώσεις το ερώτημα (με σωστό index ή πιο επιλεκτικά φίλτρα), και τέλος ξαναμετράς για να επιβεβαιώσεις ότι όντως έπεσε το συνολικό CPU.

https://learn.microsoft.com/en-us/sql/relational-databases/performance/monitoring-performance-by-using-the-query-store?view=sql-server-ver17&utm_source=chatgpt.com


Performance / Query Intelligence — Query #27: Missing index suggestions (DMV)

SELECT TOP (20)
  migs.avg_total_user_cost * (migs.avg_user_impact / 100.0) * (migs.user_seeks + migs.user_scans) AS improvement_measure,
  mid.statement AS table_name,
  mid.equality_columns,
  mid.inequality_columns,
  mid.included_columns
FROM sys.dm_db_missing_index_group_stats migs
JOIN sys.dm_db_missing_index_groups mig ON mig.index_group_handle = migs.group_handle
JOIN sys.dm_db_missing_index_details mid ON mid.index_handle = mig.index_handle
ORDER BY improvement_measure DESC;

Enter fullscreen mode Exit fullscreen mode

Το improvement_measure είναι ένα σκορ προτεραιότητας: όταν είναι μεγάλο, σημαίνει ότι ο SQL Server έχει δει ένα pattern που (α) κοστίζει αρκετά, (β) συμβαίνει συχνά, και (γ) πιστεύει ότι ένα σωστό index θα το βελτίωνε αισθητά, άρα αξίζει να το κοιτάξεις πρώτο. Όταν είναι μικρό, συνήθως σημαίνει ότι είτε το pattern δεν είναι τόσο “βαρύ”, είτε δεν εμφανίζεται συχνά, είτε το πιθανό κέρδος από index είναι μικρό—οπότε δεν είναι καλή πρώτη προτεραιότητα, εκτός αν αφορά κρίσιμο endpoint ή συγκεκριμένο χρήστη/ροή.

Το query των missing index DMVs(Dynamic Management Views) σου δίνει μια γρήγορη λίστα με σημεία όπου ο SQL Server θα ήθελε ένα index για να μπορεί να βρίσκει κατευθείαν τις σωστές γραμμές αντί να διαβάζει μεγάλο μέρος του πίνακα, άρα πιθανή μεγάλη βελτίωση. Το table_name δείχνει ποιος πίνακας επηρεάζεται, τα equality_columns είναι οι στήλες που στα queries μπαίνουν συνήθως με = (ή σε joins) και γι’ αυτό προτείνονται να μπουν πρώτες στο index, ενώ τα inequality_columns είναι στήλες με φίλτρα εύρους (>, <, BETWEEN, LIKE 'abc%') και προτείνονται μετά μέσα στο index. Το included_columns είναι στήλες που μπαίνουν ως INCLUDE ώστε το query να παίρνει όλα τα δεδομένα από το index χωρίς επιπλέον πήγαινε-έλα στον πίνακα, και το improvement_measure είναι ένα σκορ προτεραιότητας (όχι εγγύηση) που σε βοηθά να αποφασίσεις τι αξίζει να κοιτάξεις πρώτο, πριν φτιάξεις νέο index αφού ελέγξεις αν υπάρχει ήδη κάτι παρόμοιο.

Συμπέρασμα: Με τα αποτελέσματα δεν “φτιάχνεις indexes με τη μία”, αλλά τα χρησιμοποιείς σαν λίστα ενεργειών. Ξεκινάς από τα μεγαλύτερα improvement_measure και για κάθε γραμμή ελέγχεις αν υπάρχει ήδη παρόμοιο index στον πίνακα· αν υπάρχει, συνήθως χρειάζεται μικρή προσαρμογή (π.χ. include columns) ή τίποτα. Αν δεν υπάρχει, σχεδιάζεις ένα index που έχει πρώτα τις equality_columns, μετά τις inequality_columns και προσθέτεις τις included_columns για κάλυψη, αλλά το εφαρμόζεις μόνο αφού επιβεβαιώσεις ότι το αντίστοιχο query/endpoint είναι σημαντικό και ότι δεν θα δημιουργήσεις “πολλά παρόμοια indexes” που θα βαραίνουν τα writes. Μετά τη δημιουργία, μετράς αν όντως μειώθηκαν τα reads/χρόνος ή αν άλλαξε το execution plan προς πιο αποδοτική πρόσβαση, ώστε να κρατήσεις μόνο ό,τι δίνει πραγματική αξία.


Performance / Query Intelligence — Query #28: Current query plan (για ένα session)

-- Βάλε το session_id που σε ενδιαφέρει
SELECT qp.query_plan
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) qp
WHERE r.session_id = 56;

Enter fullscreen mode Exit fullscreen mode

Τα execution plans είναι εκεί που “κρύβεται” η αλήθεια για το γιατί ένα query αργεί, γιατί κάνει scan, γιατί κάνει spill, ή γιατί αλλάζει συμπεριφορά ανά parameter. Αυτό το query σου δίνει το plan ενός ενεργού request για να κάνεις άμεσο diagnosis.

Ανάλυση: Το plan handle αντιστοιχεί στο compiled plan που χρησιμοποιείται τώρα και το XML plan σου επιτρέπει να εντοπίσεις operators υψηλού κόστους, λάθος join choices, missing indexes σε predicates και memory grant προβλήματα. Είναι το επόμενο βήμα μετά το “τι τρέχει τώρα”.


Performance / Query Intelligence — Query #29: Stats last updated (για tables)

SELECT
  s.name AS schema_name,
  t.name AS table_name,
  st.name AS stats_name,
  sp.last_updated,
  sp.rows,
  sp.rows_sampled
FROM sys.stats st
JOIN sys.tables t ON t.object_id = st.object_id
JOIN sys.schemas s ON s.schema_id = t.schema_id
CROSS APPLY sys.dm_db_stats_properties(st.object_id, st.stats_id) sp
ORDER BY sp.last_updated ASC;

Enter fullscreen mode Exit fullscreen mode

Πολλά performance regressions δεν είναι “κακός κώδικας”, είναι κακά ή παλιά statistics που οδηγούν σε λάθος cardinality estimates και άρα λάθος plans. Αυτό το query είναι βασικό για να καταλάβεις αν οι εκτιμήσεις του optimizer βασίζονται σε φρέσκα δεδομένα ή όχι.

Ανάλυση: Σκέψου τα statistics (stats) σαν τη “φωτογραφία/περίληψη” που κρατά ο SQL Server για το πώς είναι μοιρασμένες οι τιμές σε μια στήλη ή σε ένα index. Ο optimizer χρησιμοποιεί αυτή τη φωτογραφία για να μαντέψει πόσες γραμμές θα επιστρέψει ένα φίλτρο ή ένα join, ώστε να διαλέξει πλάνο (seek vs scan, nested loops vs hash join κ.λπ.). Το last_updated σου λέει πόσο “φρέσκια” είναι αυτή η φωτογραφία, ενώ το rows_sampled δείχνει πόσο μεγάλο κομμάτι των δεδομένων κοίταξε όταν την έφτιαξε. Αν τα stats είναι παλιά ή φτιαγμένα από πολύ μικρό δείγμα σε μεγάλο πίνακα, ο optimizer μπορεί να κάνει λάθος εκτίμηση (να νομίζει π.χ. ότι θα βγουν 10 rows ενώ βγαίνουν 1.000.000) και τότε επιλέγει λάθος πλάνο, που φαίνεται ως “αδικαιολόγητα scans/hashes” ή γενικά απότομες διαφορές στην απόδοση ανά εκτέλεση (plan instability).


8: Operational Safety — Query #30: Recent deadlocks (system_health XE)

SELECT TOP (20)
  xed.value('(event/@timestamp)[1]', 'datetime2') AS [utc_timestamp],
  xed.query('.') AS deadlock_graph
FROM (
  SELECT CAST(t.target_data AS xml) AS target_data
  FROM sys.dm_xe_database_session_targets t
  JOIN sys.dm_xe_database_sessions s
    ON s.address = t.event_session_address
  WHERE s.name = 'system_health'
) AS src
CROSS APPLY src.target_data.nodes(
  '//RingBufferTarget/event[@name="xml_deadlock_report"]/data/value/deadlock'
) AS X(xed)
ORDER BY [utc_timestamp] DESC;

Enter fullscreen mode Exit fullscreen mode

Τα deadlocks είναι από τα πιο κλασικά concurrency incidents και μπορεί να φαίνονται “τυχαία” στους χρήστες. Το system_health session συνήθως καταγράφει deadlock graphs, οπότε μπορείς να πάρεις πραγματικό διάγραμμα για να δεις ποια αντικείμενα/queries συγκρούστηκαν.

Ανάλυση: Αυτό το ερώτημα αναζητά τα πιο πρόσφατα deadlocks που έχουν καταγραφεί από το ενσωματωμένο διαγνωστικό session της βάσης (system_health) στην Azure SQL Database: παίρνει τα δεδομένα καταγραφής που κρατάει η βάση σε μορφή XML, φιλτράρει μόνο τα γεγονότα τύπου xml_deadlock_report, και επιστρέφει για τα τελευταία 20 τη χρονική στιγμή που συνέβησαν (σε UTC) μαζί με το “deadlock graph”, δηλαδή την πλήρη περιγραφή του deadlock (ποια sessions/queries συγκρούστηκαν και ποιο έγινε victim) ώστε να μπορείς να εντοπίσεις την πραγματική αιτία και να κάνεις στοχευμένη διόρθωση σε query, indexes ή σειρά ενημερώσεων.


Top comments (0)