Microsoft SQL Server administration standards
Criteria verified as of August 2026. Re-verify on the web before committing to anything (§8).
Thesis of this document: in SQL Server the edition is an architecture decision, not a
purchase-order line. Moving from Standard to Enterprise to obtain full availability groups
or online index rebuilds is a six-figure purchase in medium-sized installations;
designing a solution that only works on Enterprise and landing it on Standard is a
failed project. The edition is fixed before the design, and the design respects it.
Second thesis: most of the instances found in production are
oversized (Enterprise for a workload that fits comfortably in Standard) and at the same time
misconfigured in what is actually free: tempdb, MAXDOP, the recovery model, RCSI and
maintenance. There is almost always more performance in fixing that than in moving up an edition.
1. Scope and triggers
Applies to designing, licensing, operating, backing up, making highly available, diagnosing and maintaining
SQL Server on-prem, on Linux and in containers: editions and limits, instance architecture and
system databases, tempdb, recovery models and the log chain, native backup and restore,
Always On (AG and FCI), performance through wait statistics and Query Store, indexes,
blocking and isolation, T-SQL with judgement, maintenance jobs, engine security and
versions/support.
Triggers: sqlcmd, SSMS, bcp, mssql-conf, mssql-cli, dbatools, T-SQL,
DBCC CHECKDB, DBCC SHOW_STATISTICS, tempdb, master/model/msdb,
RECOVERY FULL|SIMPLE|BULK_LOGGED, log_reuse_wait_desc, BACKUP DATABASE, BACKUP LOG,
RESTORE ... WITH NORECOVERY|STANDBY, RESTORE VERIFYONLY, BACKUP TO URL, msdb.dbo.backupset,
Always On, availability group, AG listener, WSFC, CLUSTER_TYPE = EXTERNAL|NONE,
FAILOVER_MODE, FCI, Query Store, sys.query_store_*, sys.dm_os_wait_stats,
sys.dm_exec_requests, sys.dm_db_index_usage_stats, sys.dm_db_index_physical_stats,
sp_WhoIsActive, sp_Blitz, sp_BlitzIndex, sp_QuickieStore, MaintenanceSolution.sql,
READ_COMMITTED_SNAPSHOT, ALLOW_SNAPSHOT_ISOLATION, deadlock graph, MAXDOP,
cost threshold for parallelism, max server memory, SQL Server Agent, TDE, sysadmin,
mssql/server (container), "cumulative update", "Standard edition", "Software Assurance".
Not applicable: see
data-platform-standards (parent skill: PostgreSQL as the default, relational modelling,
expand/contract migrations, data classification and retention. Its principle —a store by
need, not by fashion— still rules: this skill does not justify choosing SQL Server, it covers
operating it well when it is already there by historical decision, because a third-party product requires it or
because of a consolidated .NET/Windows ecosystem).
windows-server-ad-standards (critical boundary, total delegation: forest/domain/OU, GPO,
Kerberos and NTLM, SPN, delegation, gMSA/dMSA, Tier 0 model, PAW, forest recovery
and Windows Server hardening are theirs. SQL Server's integrated authentication
rests on all of that: here it is only stated what the engine requires —service account, correct
SPN for Kerberos, server roles— and the how is delegated. Forbidden to duplicate AD
criteria here.).
ha-clustering-standards (Pacemaker/Corosync, quorum, fencing/STONITH and their discipline are
theirs). Boundary declared explicitly: WSFC is the Windows cluster and it belongs to this
skill (it is inseparable from FCI and from AGs on Windows); when SQL Server runs on Linux, the
cluster manager is Pacemaker and ha-clustering-standards rules —including fencing, without
which there is no HA— and here only what belongs to the engine lives: CLUSTER_TYPE = EXTERNAL,
FAILOVER_MODE = EXTERNAL, the AG resource and the mssql-server-ha package.
backup-recovery-standards and bcdr-standards — arbitration rule mirrored word for
word from backup-recovery-standards §1:
"how is the copy made?" belongs to backup-recovery (tool, repository, 3-2-1, GFS,
dedup, repo encryption, integrity, catalogue, restore procedure); "how much can we
lose, in what order do we bring it back and who decides?" belongs to bcdr.
sql-standards (the SQL language; arbitration rule mirrored from its §1: if the question
changes how the query or the DDL is written, it belongs to sql-standards; if it changes which engine is
chosen, how it is sized, backed up, replicated or restored, it belongs here). Theirs are the
T-SQL peculiarities that change the code —MERGE and its conditions for safe use,
OUTPUT, TOP, APPLY, OFFSET/FETCH, the effect of collation on string
comparison—; from here, everything that decides execution: Query Store, forced plans, compatibility
levels, READ_COMMITTED_SNAPSHOT, statistics, DBCC and the per-core licensing of
the features a construct may require.
Its own extension: what is engine-specific belongs here — recovery models, the log
chain and how it breaks, full/differential/log backups, RESTORE ... WITH STANDBY,
RESTORE VERIFYONLY/CHECKSUM, backup to URL and the role of AGs in the RPO. The repository,
its immutability and the cadence of the test restore belong to backup-recovery; the RPO/RTO
that justifies synchronous mode and the failover exercise belong to bcdr.
oracle-dba-standards (the catalogue's other proprietary engine; a different vendor, the same
pattern: licensing decides the architecture. They do not compete).
dotnet-standards (the C#/EF Core code that consumes this database is theirs: driver,
pool, Microsoft.Data.SqlClient, migrations from the application).
streaming-cdc-standards (capture is theirs: CDC, change tracking, change event streaming,
Debezium; the cost in the engine belongs here: log retention, capture jobs, impact
on the recovery model).
azure-standards (Azure SQL Database, Managed Instance, Arc, Azure Hybrid Benefit),
aws-standards/gcp-standards (RDS for SQL Server, Cloud SQL for SQL Server).
vulnerability-management-standards (the CVE cycle and the patching window are theirs; here
only the mechanics of CUs and their cadence), identity-access-management-standards,
cryptography-pki-standards (TLS and the keys TDE rests on), secrets-management-standards,
linux-hardening-standards and podman-systemd-containers-standards/kubernetes-standards
(the OS and the runtime when SQL Server runs outside Windows),
observability-standards, grc-compliance-standards, privacy-engineering-standards,
iac-standards, firewall-policy-standards (exposure of port 1433).
mysql-mariadb-dba-standards (the catalogue's third relational engine; open source, without the
edition variable that dominates here), timeseries-db-standards, message-brokers-standards,
nosql-standards, search-engines-standards, caching-cdn-standards (other specialised
stores).
2. Editions and licensing: the dominant decision
Scope warning, non-negotiable: what is set here is technical criteria, not contractual
advice. Every decision with an economic impact is validated against the SQL Server licensing
guide, the current Product Terms and the organisation's licence manager.
No price appears in this document (§8).
2.1 Standard's real limits — verified on SQL Server 2025 (17.x)
Primary source: Editions and supported features of SQL Server 2025, learn.microsoft.com
(also checked against the markdown of the MicrosoftDocs/sql-docs repository).
| Limit |
Enterprise |
Standard |
Express |
| Maximum compute per instance (engine) |
OS maximum |
Lesser of 4 sockets or 32 cores |
Lesser of 1 socket or 4 cores |
| Maximum buffer pool memory per instance |
OS maximum |
256 GB |
1,410 MB |
| Columnstore segment cache |
Unlimited |
32 GB |
352 MB |
| Memory-optimized data per database |
Unlimited |
32 GB |
352 MB |
A change that does move a design — footnote 2 of the document itself: "In SQL Server 2022 (16.x)
and earlier versions, the limit is the lesser of 4 sockets or 24 cores." That is: Standard went
from 24 to 32 cores and its buffer pool rose to 256 GB in the 2025 version. A sizing exercise done
on "Standard is 24 cores and 128 GB" is out of date and may be justifying an unnecessary
Enterprise. Always verify against the page for the specific version: the limits are per
version.
2.2 What separates Enterprise from Standard (what decides the architecture)
Enterprise only (verified in the 2025 table):
- Full Always On availability groups, contained AG, distributed AG, automatic
read/write connection redirection. Standard only has basic availability groups:
"A basic availability group supports two replicas, with one database." — two replicas, a
single database, with no readable replica.
- FCI: Enterprise up to 16 nodes; Standard 2 nodes.
- Online index rebuild and create (and its resumable version), online schema
change, online page and file restore, fast recovery, mirrored backups.
- Almost all advanced Intelligent Query Processing (batch mode on rowstore, adaptive joins,
memory grant feedback, cardinality feedback, DOP feedback, automatic tuning), Query Store on
secondary replicas, parallel index maintenance, parallel
CHECKDB, distributed partitioned
views.
In Standard, and this is what usually surprises people (and dismantles many "we need Enterprise"):
TDE, backup encryption, backup compression, table and index partitioning,
data compression, columnstore, In-Memory OLTP, Query Store, Always Encrypted
(also with secure enclaves), row-level security, dynamic data masking, auditing,
Change Data Capture, Accelerated Database Recovery, optimized locking, backup and
restore to S3-compatible object storage, clusterless AG and
—new in 2025— Resource Governor, which used to be Enterprise-only.
Decision rule: Enterprise is justified by real HA (a multi-database or multi-replica AG),
24×7 online maintenance or a compute ceiling; not by features that are already in Standard.
Every Enterprise proposal is accompanied by which of those three reasons applies.
Other edition facts verified in 2025: Express goes up to 1 socket/4 cores and now
includes what used to be Express with Advanced Services; Enterprise Developer and
Standard Developer exist as separate editions (Developer = the full functionality of its edition,
licensed only for development and testing — never in production); the Web edition is withdrawn
from 2025 onwards (2022 is the last one that includes it); on-prem Reporting Services is consolidated under
Power BI Report Server.
2.3 Licensing model
- Per core: physical cores are counted (hyperthreading does not count), with a per-processor
minimum and sale in packs of 2. It is the only model available for Enterprise in new
agreements.
- Server + CAL: Standard only. It stops paying off beyond a certain number of users; the
threshold depends on the agreement — calculate it, do not estimate it.
- Enterprise with Server+CAL (legacy, not available for new agreements) is limited to
20 cores per instance (footnote 1 of the editions document): an old contract may
be imposing a performance ceiling nobody remembers.
- Virtualisation: with Software Assurance, Enterprise offers unlimited virtualisation on
a fully licensed host; without SA, you license per VM. Counting VMs on a shared
host without SA is the fast route to an audit finding.
2.4 Software Assurance and its role in high availability — the expensive trap
Failover rights are a Software Assurance (or subscription licence) benefit.
Without SA, a passive replica is fully licensed, even if it never serves a
query. With SA, for each licensed OSE you can run passive replicas in anticipation of
a failover (typically one for HA, one for DR and one in Azure), provided they neither serve data nor
run active work and do not exceed the primary's licence.
Direct design consequences, not procurement ones:
- A readable secondary in an AG stops being passive: it is licensed. "Offloading the reports to
the replica" is a purchase. The same goes for running backups or
CHECKDB on the secondary
under the current terms: verify it before designing it.
- The cost of HA in SQL Server is not the cluster: it is the second node's licence if there is no SA.
- Verify the current Product Terms: failover rights have been redefined more than
once. Their wording is not fixed here; what is fixed is the obligation to check it.
3. Instance architecture
- One instance per host as the default. Multiple named instances split memory and CPU
between competing engines and complicate patching; separating by container or by VM is cleaner
and easier to license. Consolidate into databases within one instance, not into
instances within a host.
- System databases:
master (configuration and logins) and msdb (Agent, backup
history, plans) are part of the backup strategy — losing them costs a rebuild of the
environment; model is the template for every new database (setting the recovery model and default
file sizes there avoids surprises); tempdb is recreated at startup and is not
backed up.
- Memory:
max server memory always set, leaving headroom for the OS (and for other consumers
on the host); never left at the default. min server memory only if there is real competition. On Linux, limits
via mssql-conf and cgroup v2 (honoured from SQL Server 2025 and from 2022 CU 20 —
before that, a container with a memory limit could die of OOM because the engine ignored it).
- Parallelism:
MAXDOP and cost threshold for parallelism explicit from day one. The
default value of cost threshold (5) is from the nineties and parallelises trivial queries:
raising it is one of the product's best benefit/risk changes. MAXDOP according to
core count and NUMA, with the exception documented per workload.
- Storage: data, log and
tempdb on volumes with different I/O profiles; the transaction
log is sequential write and latency-sensitive — it is the first place to put
fast storage. NTFS formatted with a 64 KB allocation unit on Windows unless the array vendor's
criteria say otherwise (linux-storage-standards/onprem-standards for the rest).
- Autogrowth: in fixed and large increments, never as a percentage, and with
Instant File Initialization enabled (the Perform Volume Maintenance Tasks privilege) so that
data growth does not freeze the instance. Autogrowth is a safety net,
not a capacity strategy: files are pre-sized.
3.1 tempdb — the setting with the most real impact
- Multiple data files, all of the same size and with the same autogrowth: allocation
page contention in
tempdb is the classic bottleneck of busy instances, and it only
disappears if the files are symmetric (the allocator is round-robin proportional to free
space: an unequal file takes all the work). The modern installer proposes a reasonable
number based on cores; review it, do not accept it blindly, and never
leave a single file on a server with several cores.
- Pre-size so that it does not grow while hot; a dedicated and fast volume; memory-optimized
tempdb metadata only on Enterprise and with a workload that justifies it.
tempdb is shared by the whole instance: a query with a monstrous sort or a badly used
snapshot isolation affects every database. Watch it as a global resource.
- On Linux,
tempdb on tmpfs is supported: a real performance option, with the consequent
RAM consumption accepted.
3.2 Recovery models and their direct consequence
| Model |
What it implies |
Recovery point |
| SIMPLE |
The log is truncated automatically at each checkpoint. There is no log backup |
Only the last full/differential. RPO = hours, like it or not |
| FULL |
The log is retained until it is backed up. Mandatory for AGs and log shipping |
PITR to the minute/second, if and only if there are periodic log backups |
| BULK_LOGGED |
Minimal logging of certain bulk operations |
It breaks PITR within the interval containing the bulk operation: you recover to the end of the log backup, not to an instant |
The consequence that gets forgotten: putting a database in FULL without scheduling log backups does not give
better recovery — it makes the log grow until it fills the disk and takes the instance down. It is the
product's most frequent self-inflicted incident. Operational rule:
- The model is determined by the RPO derived by
bcdr-standards, and FULL implies scheduled log
backups on the same day it is enabled. There are no half measures.
- Mandatory diagnosis when the log grows:
sys.databases.log_reuse_wait_desc says why
it cannot be reused (LOG_BACKUP, ACTIVE_TRANSACTION, AVAILABILITY_REPLICA,
REPLICATION…). It is read before touching anything.
- Forbidden:
DBCC SHRINKFILE on the log as a routine, and forbidden any Internet
recipe that goes through putting the database into SIMPLE to "clean the log": it breaks the log chain
(§4) and with it the recovery point.
- VLF: grow the log in large and few increments; thousands of VLFs slow down startup and
recovery.
4. Native backup and restore
- Three pieces: full (the base of the chain), differential (everything changed since the
last full — not since the previous differential) and log (the changes since the previous log
backup, and only in FULL/BULK_LOGGED).
- The log chain is the asset: an uninterrupted sequence of log backups since a
full. It is broken by: putting the database into SIMPLE (even if it goes back to FULL, a new
full is needed), a log backup with
TRUNCATE_ONLY from old versions, or an out-of-band
backup taken by another tool that does not use COPY_ONLY. Hence the rule:
every ad-hoc backup is taken with COPY_ONLY — a normal full from an external tool
resets the differential base and leaves the scheduled differentials meaningless.
- Verification:
WITH CHECKSUM on the backup and RESTORE VERIFYONLY WITH CHECKSUM as the automatic
minimum. That is not a restore: it only says the file is readable.
RESTORE ... WITH NORECOVERY to chain the differential and the logs; WITH STANDBY leaves the
database readable between log applications (an undo file) — it is the right tool
for log shipping with a queryable secondary, and the cheap way to have a readable delayed
copy without licensing readable replicas.
- Restore to a point in time:
RESTORE ... WITH STOPAT (or STOPATMARK) over the log chain.
Rehearse it before needing it: human error is recovered with this, not with an AG.
- Backup to URL (object storage): supported to block blobs with SAS; in 2025 also
to S3-compatible storage via REST, on Enterprise and Standard. It is a destination, not
a policy — the repository, immutability and the 3-2-1 rule belong to
backup-recovery-standards. On Linux, backup to URL with a page blob is not supported.
- Encryption:
BACKUP ... WITH ENCRYPTION available in Standard and Enterprise; the certificate or
asymmetric key is backed up and held outside the system being backed up (key custody,
bcdr-standards). An encrypted backup whose certificate was lost with the server is a useless
copy: it is the most common way of discovering there was no DR.
- Catalogue:
msdb.dbo.backupset/backupmediafamily is the record of what exists. It is queried
to detect coverage gaps and it is backed up with msdb.
Shared invariant, with no nuance: a backup without a tested restore does not exist. The gate is not
"the job finished green", it is "we restored, timed it and validated it". The cadence and the record
of the exercise are set by backup-recovery-standards; the engine's procedure belongs here.
5. High availability
- Availability Groups (AG): replication at the database level via log shipping.
- Synchronous = RPO 0 and automatic failover possible, at the cost of latency on every commit
(the primary waits for the hardening on the secondary). Asynchronous = no latency impact,
with potential loss and only forced manual failover, with data loss. The choice is
dictated by
bcdr-standards's RPO, not by convenience.
- Listener: the application connects to the listener, never to a node's name, and the
connection string declares
MultiSubnetFailover=True when there are different subnets. Without that, the failover
"works" and the application does not come back.
- Readable replica: it is not free in two senses. Licensing (§2.4: it stops being passive) and
performance — read-only queries implicitly take snapshot isolation,
which generates row versioning on the primary (14 bytes per modified row) and increases
tempdb usage on the secondary; besides, the REDO can block against long queries and
trigger redo lag, which is real RPO lost. It is monitored.
- On Enterprise, per the 2025 documentation: up to 8 secondary replicas. Standard only
has basic AG: 2 replicas, 1 database, with no readable secondary (§2.2).
- FCI (failover cluster instance): it shares storage; it protects against node
failure, not against data failure (a corrupt LUN is corrupt for everyone). It requires a cluster
(WSFC/Pacemaker) and shared storage. Enterprise 16 nodes, Standard 2.
- AG vs FCI, in one line: FCI protects the server with a single copy of the data; AG protects the
data with several copies and allows geographic separation. They are combined (an FCI as a replica of an
AG) only with a written reason: the complexity multiplies.
- Cluster boundary, declared:
- Windows → WSFC is the substrate of FCI and of AGs (except clusterless). Its quorum
(disk, file share or cloud witness), its vote model and its operation belong to this
skill. The domain, the accounts and the DNS it rests on belong to
windows-server-ad-standards.
- Linux → the manager is Pacemaker/Corosync and
ha-clustering-standards rules,
including its invariant: without tested fencing there is no HA, there is deferred corruption. What belongs to the engine
is: CLUSTER_TYPE = EXTERNAL with FAILOVER_MODE = EXTERNAL (the only combination with automatic
failover), the mssql-server-ha package —with HA agent v2 from SQL Server 2025
CU 3— and the AG and IP resources.
CLUSTER_TYPE = NONE (clusterless AG): manual failover only, intended for read-scale
and rolling upgrades. It is not high availability: do not sell it as such.
- An AG or FCI cannot span WSFC and Pacemaker. For mixed scenarios there are only two routes,
both AG-based:
CLUSTER_TYPE = NONE or a distributed AG.
- Mandatory rehearsal: failover is tested with the application inside the exercise (reconnection,
pool, DNS,
MultiSubnetFailover), at bcdr-standards's cadence. An AG that has never
failed over is an assumption.
- Forbidden: database mirroring in new designs (deprecated), and presenting an AG as
a substitute for backup — a
DELETE replicates in milliseconds.
6. SQL Server on Linux and in containers
It is a first-class deployment, with known cutbacks. Not supported on Linux (verified in
the 2025 documentation): merge replication, FILESTREAM and FileTable, extended
system procedures (xp_cmdshell), linked servers to non-SQL Server sources
(use PolyBase), EXTERNAL_ACCESS/UNSAFE CLR assemblies, Buffer Pool Extension,
database mirroring, Always Encrypted with secure enclaves, EKM (except with Azure Key
Vault from 2022 CU 12), integrated Windows authentication for linked servers and for
AG endpoints (the endpoints use certificate authentication), Analysis Services,
Reporting Services, several Agent subsystems (CmdExec, PowerShell, SSIS, SSAS, SSRS),
Agent alerts and Managed Backup. In addition: a single instance per host (there is no SQL
Browser and no named instances) and Linux deployments are not FIPS compliant. From
SQL Server 2025, SLES is no longer supported.
Containers: the official mssql/server image. Criteria:
- Pin the image by digest, explicit
MSSQL_PID, the sa password from the secrets
manager (never in the Dockerfile, the compose file or the command line), and persistent
volumes for data, log and backups — a database container with no volume is
scheduled data loss.
- cgroup v2 honoured from 2025 (and 2022 CU 20). With a cgroup v2 CPU limit, the engine keeps
reporting the host's CPUs: align it with
ALTER SERVER CONFIGURATION SET PROCESS AFFINITY and
trace flag 8002 so that parallelism decisions are not taken on a false count.
- Features that depend on the Azure Arc agent (Entra ID, Purview, pay-as-you-go,
Defender) are not supported in containers.
- On Kubernetes,
kubernetes-standards rules for the rest; container HA via a statefulset
and restart is not an AG: it is a restart, with its own RTO.
7. Performance
7.1 Method: wait statistics and Query Store
- Diagnose by waits, not by hunches:
sys.dm_os_wait_stats (cumulative since
startup — measure by delta between two instants, never the raw cumulative),
sys.dm_exec_requests/sys.dm_exec_session_wait_stats for what is happening now, and
sp_WhoIsActive as the first-line tool. Ignore the benign background waits before
drawing conclusions.
- Query Store is the tool that changed diagnosis and it is in every edition
(enabled by default on new databases from 2022): it stores queries, plans and execution
statistics with history, so that "yesterday it was fine and today it is not" goes from anecdote to evidence.
Criteria: enabled on every production database, with the capture mode and retention sized
(
AUTO in general), and watching that it does not fill up —if the store goes READ_ONLY it stops
capturing silently—. sp_QuickieStore to query it without writing SQL by hand.
- Plan regression: identify with Query Store the query with several plans and force the good one
(
sys.sp_query_store_force_plan) as a temporary containment measure, with a review date and the
cause investigated afterwards. A forced plan nobody reviews is debt: the engine may stop
being able to apply it and nobody finds out. On Enterprise there is automatic tuning to fix
regressions automatically — with supervision, not as an autopilot.
- Statistics:
AUTO_CREATE_STATISTICS and AUTO_UPDATE_STATISTICS enabled; manual
updates after bulk loads and on large tables where the automatic threshold arrives late.
AUTO_UPDATE_STATISTICS_ASYNC with judgement. The number 1 cause of a bad plan is an old
statistic, not the optimiser.
- Parameter sniffing: a classic symptom (the same procedure, erratic performance). It is attacked
in this order: statistics → rewriting the query → targeted
OPTIMIZE FOR/RECOMPILE →
Query Store hints (preferable to touching the code) → separating code paths.
A global WITH RECOMPILE is an expensive sticking plaster.
7.2 Indexes
- One clustered index per table, almost always: the table is the clustered index.
A narrow, increasing, unique and immutable key (an
int/bigint identity or a sequence);
a random GUID as the clustered key fragments and widens every nonclustered index
(which includes it as a pointer) — vetoed unless justified by measurement. A heap (a table without a clustered
index) is the exception, not the default.
- Nonclustered: by real query pattern, with
INCLUDE to achieve measured coverage, and
filtered for hot subsets. Every index is justified with a plan; every index
costs on every INSERT/UPDATE/DELETE and on every backup.
- Retirement:
sys.dm_db_index_usage_stats to detect indexes with no reads and with writes.
Before dropping, disable and observe. Careful: usage statistics reset with the
instance — do not decide on a short window.
- Missing indexes: the missing indexes DMVs are a hint, not an order. Applying their
suggestions wholesale is one of the worst frequent practices: they overlap, duplicate and fatten
the table. Consolidate by hand (
sp_BlitzIndex helps).
- Fragmentation and the myth of routine maintenance: reorganising or rebuilding indexes every
night out of habit is, on modern storage (SSD/NVMe/array with cache), almost always
useless work that generates tonnes of log (and therefore huge log backups, AG traffic
and I/O pressure) for a marginal improvement. Criteria:
- What almost always matters is updating the statistics, not defragmentation.
- Rebuild with high thresholds and only on large and genuinely fragmented indexes;
ONLINE = ON is Enterprise — on Standard, a rebuild blocks: schedule it in a
window or do not do it.
- Watch the global
FILLFACTOR: lowering it "to reduce fragmentation" wastes memory and I/O
on every read.
7.3 Blocking, isolation and why RCSI is usually the answer
- SQL Server's default is
READ COMMITTED with locks: readers block writers
and vice versa. That is where most of the blocking attributed to "the database is slow" comes from, and where
the WITH (NOLOCK) antipattern comes from, which is not an optimisation: it is reading dirty data, with
duplicated or missing reads and, in real cases, error 601. Forbidden as a general practice.
READ_COMMITTED_SNAPSHOT ON (RCSI) changes the database's default level to row
versioning: readers stop blocking writers without changing a line of application
code. It is, in practice, the right answer for the vast majority of OLTP workloads —
the behaviour anyone coming from PostgreSQL or Oracle is already used to. Its cost,
declared: row versioning in tempdb (size it), 14 extra bytes per versioned row,
and a real semantic change —certain locks that today serialise accidentally disappear,
so there is application logic that relied on them without knowing. Hence: it is enabled with
testing, not hot on a Friday, and it requires momentary exclusive access to the database.
ALLOW_SNAPSHOT_ISOLATION (explicit snapshot isolation) is different: a transaction with a fully
consistent view and update conflicts the application must retry. It is used
where it is needed, not by default.
- Deadlocks: they are captured, not guessed — the
system_health extended events
session records them by default; read the deadlock graph to know which
resources and in what order. Solution in order of preference: consistent access order →
indexes that reduce the scope of the lock → shorter transactions → isolation level →
retry with backoff in the application (always, as a net: a deadlock is a recoverable
error, not a failure of the user's request).
- Short transactions with no external interaction inside: no HTTP calls and no waiting for
a user with an open transaction. Set
LOCK_TIMEOUT and command timeouts in the application.
Optimized locking (2022+, and in Standard in 2025) reduces escalation and the number of locks:
evaluate it, with the same before/after measurement discipline.
7.4 T-SQL: antipatterns that kill plans
- Non-SARGable predicates: a function on the column (
WHERE YEAR(date)=2026,
WHERE UPPER(col)=…), a computation on the column, LIKE '%something'. Rewrite to ranges.
- Implicit type conversion (
nvarchar against varchar, text against number): it invalidates the
index silently. It is the most expensive and easiest-to-fix performance bug.
- User-defined scalar functions in
SELECT/WHERE: historically row-by-row
execution. Scalar UDF inlining exists (all editions), but it does not cover every case:
prefer inline table-valued functions (iTVF) or expressions.
- Cursors and
WHILE loops where a set-based operation would fit.
SELECT * in interfaces and views; views nested on views (the optimiser ends up
with plans impossible to reason about).
sp_executesql with concatenated literals: massive recompilation and SQL injection.
Parameters always. It is a security veto as well as a performance one.
MERGE: use with caution (a long history of bugs and blocking). Explicit INSERT/UPDATE
with logical ON CONFLICT handling is usually more predictable.
- Triggers with non-obvious side effects and without set handling (
inserted/deleted
have several rows): a chronic source of logical corruption.
- Treat
NULL, ANSI_NULLS and SET options consciously: they change plans and affect filtered
and indexed indexes.
8. Maintenance: what every instance needs
Four jobs, not one fewer, all with an alert when they fail (a maintenance job that
fails silently is worse than not having it):
- Integrity:
DBCC CHECKDB with DATA_PURITY, at a weekly cadence at minimum on databases that
matter. It is the only control that detects corruption; without it, corruption is discovered the
day you restore. On Enterprise there is parallel checking; on large databases, split by
tables/filegroups or run it on a restored copy —which, incidentally, tests the
restore: two controls for the price of one.
- Statistics (priority) and indexes (with high thresholds, §7.2).
- Backups full/differential/log according to the recovery model (§4).
- History purge:
msdb (backup and job history), Query Store, extended event
sessions, old backup files. An unpurged msdb degrades the Agent itself.
Reference community solutions — status verified in August 2026:
- Ola Hallengren, SQL Server Maintenance Solution (
MaintenanceSolution.sql,
DatabaseBackup, DatabaseIntegrityCheck, IndexOptimize): alive and updated, with declared support
for SQL Server 2017, 2019, 2022 and 2025 plus Azure SQL MI. It is the default: you do not
write your own backup and maintenance scripts unless there is a documented reason. It is distributed by
dated versions, not by semver: download the latest from ola.hallengren.com.
- dbatools (PowerShell, >500 commands): alive — 2.8.2 published on the PowerShell Gallery in
May 2026, with continuous activity in
dataplat/dbatools. It is the right way to automate
(Install-DbaMaintenanceSolution, migrations, inventory) rather than GUIs and hand-written scripts.
- First Responder Kit (
sp_Blitz, sp_BlitzIndex, sp_BlitzCache, sp_BlitzFirst): alive,
with the "The First Responder Kit 2026" release (2026-07-08) and an annual versioning model. Real warnings
from the author himself: since April 2026 it only supports SQL Server 2016 SP2 and later,
sp_Blitz requires sp_ineachdb, several scripts were deprecated (sp_BlitzQueryStore →
sp_QuickieStore), and there was a release marked by the author as unreliable for containing a lot of
AI-edited code. Test before deploying, especially with case-sensitive collation.
sp_WhoIsActive (Adam Machanic) for live diagnosis.
- Cross-cutting rule: any third-party script installed on a production instance is
reviewed line by line — it runs with high privileges inside the engine.
9. Security
- Authentication: integrated mode (Windows/Kerberos) by default; SQL authentication only
when there is no alternative (Linux, containers, third parties), with passwords from the secrets
manager. Everything relating to Kerberos, SPN, delegation, managed service accounts
(gMSA/dMSA), GPO and Tier 0 is delegated entirely to
windows-server-ad-standards — here only
what the engine requires is declared: a dedicated service account with no domain privileges, a correct
SPN (without it the connection silently falls back to NTLM and Kerberos and delegation are lost),
and no domain administrative account running the service.
sa: disabled and renamed; never as an application account. No application login
in sysadmin or in db_owner: minimum permissions per schema, ideally via
procedures or defined database roles.
- Roles: use server and database roles (including user-defined server roles) instead of granting to
individual principals. Periodically review
sysadmin,
securityadmin, CONTROL SERVER and db_owner membership — it is the access review that
nobody does until the audit.
- Surface:
xp_cmdshell disabled (and absent on Linux); CLR disabled unless
needed and never UNSAFE; Ad Hoc Distributed Queries disabled; SQL Browser off
where it is not needed; port 1433 never exposed to the Internet or to the user network
(firewall-policy-standards), and encryption in transit mandatory (Encrypt=True with
certificate validation in the connection string — a TrustServerCertificate=True in
production voids the protection).
- Linked servers: permanent trust bridges. Inventoried, with the least-privileged
account and reviewed; never mapping to an administrative account.
- Encryption at rest: TDE is available in Standard and Enterprise (not in Express) — the myth
that "TDE is Enterprise only" is out of date and has justified unnecessary purchases. TDE protects
files and backups at rest, not from a user with permissions. The certificate/DEK is backed up
and held outside the server: losing it is losing the data (§4). For column-level sensitive
data, Always Encrypted (also in Standard, even with secure enclaves on Windows),
with the key outside the engine — it is the only protection against the DBA themselves. Keys and their
lifecycle, in
cryptography-pki-standards/secrets-management-standards.
- Auditing: SQL Server Audit (server and database auditing, available in all
editions in 2025) over what matters —permission changes, access to classified data,
use of privileged accounts—, with output to the SIEM. Do not audit everything: nobody reads it and it costs.
- Patching: SQL Server is served through Cumulative Updates (since 2017 there are no Service Packs).
Apply CUs on a cadence, in a window, with a planned rollback; security bulletins arrive
through Patch Tuesday. In 2026 significant engine vulnerabilities have been published —among them
critical deserialisation RCEs reaching from SQL Server 2016 SP3 through 2025, and elevation
of privilege to
sysadmin— which reinforces two things: patch and do not grant privileges that
turn an elevation into a disaster. Triage and the window are set by
vulnerability-management-standards.
10. Versions and support — verified in a primary source
Data taken from the lifecycle pages on learn.microsoft.com (August 2026). It is the data
that ages fastest: always re-verify.
| Version |
Start |
End of mainstream support |
End of extended support |
| SQL Server 2016 |
2016-06-01 |
2021-07-13 |
2026-07-14 — already expired |
| SQL Server 2017 |
2017-09-29 |
2022-10-11 |
2027-10-12 |
| SQL Server 2019 |
2019-11-04 |
2025-02-28 |
**20 |
…(truncated)
1---2name: sqlserver-dba-standards3description: Use when operating Microsoft SQL Server — sqlcmd, SSMS, T-SQL, DBCC CHECKDB, tempdb file configuration, recovery models and transaction log growth, BACKUP DATABASE/DIFFERENTIAL/LOG and a broken log chain, RESTORE WITH NORECOVERY or WITH STANDBY, backup to URL, Always On availability groups, basic availability groups, failover cluster instances on WSFC, CLUSTER_TYPE EXTERNAL with Pacemaker on Linux, Query Store, sys.dm_os_wait_stats and wait statistics, plan regression and forced plans, clustered versus nonclustered and covering indexes, index fragmentation, RCSI and snapshot isolation, deadlock graphs and blocking, SQL Server Agent jobs, Ola Hallengren MaintenanceSolution, First Responder Kit sp_Blitz, dbatools, TDE, mssql-conf and mssql-server containers, edition core and memory limits, core versus Server+CAL licensing and Software Assurance failover rights, or SQL Server 2016/2017/2019/2022/2025 support dates and cumulative updates.4---56# Microsoft SQL Server administration standards78Criteria verified as of **August 2026**. Re-verify on the web before committing to anything (§8).910> **Thesis of this document**: in SQL Server **the edition is an architecture decision**, not a11> purchase-order line. Moving from Standard to Enterprise to obtain full availability groups12> or online index rebuilds is a six-figure purchase in medium-sized installations;13> designing a solution that only works on Enterprise and landing it on Standard is a14> failed project. **The edition is fixed before the design, and the design respects it.**15>16> **Second thesis**: most of the instances found in production are17> **oversized** (Enterprise for a workload that fits comfortably in Standard) and at the same time18> **misconfigured** in what is actually free: `tempdb`, `MAXDOP`, the recovery model, RCSI and19> maintenance. There is almost always more performance in fixing that than in moving up an edition.2021## 1. Scope and triggers2223Applies to designing, licensing, operating, backing up, making highly available, diagnosing and maintaining24SQL Server on-prem, on Linux and in containers: editions and limits, instance architecture and25system databases, `tempdb`, recovery models and the log chain, native backup and restore,26Always On (AG and FCI), performance through wait statistics and Query Store, indexes,27blocking and isolation, T-SQL with judgement, maintenance jobs, engine security and28versions/support.2930Triggers: `sqlcmd`, `SSMS`, `bcp`, `mssql-conf`, `mssql-cli`, `dbatools`, `T-SQL`,31`DBCC CHECKDB`, `DBCC SHOW_STATISTICS`, `tempdb`, `master`/`model`/`msdb`,32`RECOVERY FULL|SIMPLE|BULK_LOGGED`, `log_reuse_wait_desc`, `BACKUP DATABASE`, `BACKUP LOG`,33`RESTORE ... WITH NORECOVERY|STANDBY`, `RESTORE VERIFYONLY`, `BACKUP TO URL`, `msdb.dbo.backupset`,34`Always On`, `availability group`, `AG listener`, `WSFC`, `CLUSTER_TYPE = EXTERNAL|NONE`,35`FAILOVER_MODE`, `FCI`, `Query Store`, `sys.query_store_*`, `sys.dm_os_wait_stats`,36`sys.dm_exec_requests`, `sys.dm_db_index_usage_stats`, `sys.dm_db_index_physical_stats`,37`sp_WhoIsActive`, `sp_Blitz`, `sp_BlitzIndex`, `sp_QuickieStore`, `MaintenanceSolution.sql`,38`READ_COMMITTED_SNAPSHOT`, `ALLOW_SNAPSHOT_ISOLATION`, `deadlock graph`, `MAXDOP`,39`cost threshold for parallelism`, `max server memory`, `SQL Server Agent`, `TDE`, `sysadmin`,40`mssql/server` (container), "cumulative update", "Standard edition", "Software Assurance".4142**Not applicable**: see43- `data-platform-standards` (**parent skill**: PostgreSQL as the default, relational modelling,44 expand/contract migrations, data classification and retention. Its principle —*a store by45 need, not by fashion*— still rules: **this skill does not justify choosing SQL Server**, it covers46 operating it well when it is already there by historical decision, because a third-party product requires it or47 because of a consolidated .NET/Windows ecosystem).48- `windows-server-ad-standards` (**critical boundary, total delegation**: forest/domain/OU, GPO,49 **Kerberos and NTLM**, SPN, delegation, **gMSA/dMSA**, Tier 0 model, PAW, forest recovery50 and Windows Server hardening are **theirs**. SQL Server's integrated authentication51 *rests* on all of that: **here it is only stated what the engine requires** —service account, correct52 SPN for Kerberos, server roles— and **the how is delegated**. **Forbidden to duplicate AD53 criteria here.**).54- `ha-clustering-standards` (**Pacemaker/Corosync, quorum, fencing/STONITH and their discipline are55 theirs**). Boundary declared explicitly: **WSFC is the Windows cluster and it belongs to this56 skill** (it is inseparable from FCI and from AGs on Windows); **when SQL Server runs on Linux, the57 cluster manager is Pacemaker and `ha-clustering-standards` rules** —including fencing, without58 which there is no HA— and here only what belongs to the engine lives: `CLUSTER_TYPE = EXTERNAL`,59 `FAILOVER_MODE = EXTERNAL`, the AG resource and the `mssql-server-ha` package.60- `backup-recovery-standards` and `bcdr-standards` — **arbitration rule mirrored word for61 word from `backup-recovery-standards` §1**:62 > **"how is the copy made?" belongs to `backup-recovery`** (tool, repository, 3-2-1, GFS,63 > dedup, repo encryption, integrity, catalogue, restore procedure); **"how much can we64 > lose, in what order do we bring it back and who decides?" belongs to `bcdr`**.65- `sql-standards` (**the SQL language**; arbitration rule mirrored from its §1: *if the question66 changes how the query or the DDL is written, it belongs to `sql-standards`; if it changes which engine is67 chosen, how it is sized, backed up, replicated or restored, it belongs here*). Theirs are the68 T-SQL peculiarities that **change the code** —`MERGE` and its conditions for safe use,69 `OUTPUT`, `TOP`, `APPLY`, `OFFSET/FETCH`, the effect of collation on string70 comparison—; from here, everything that decides execution: Query Store, forced plans, compatibility71 levels, `READ_COMMITTED_SNAPSHOT`, statistics, DBCC and the per-core licensing of72 the features a construct may require.7374 Its own extension: **what is engine-specific belongs here** — recovery models, the log75 chain and how it breaks, full/differential/log backups, `RESTORE ... WITH STANDBY`,76 `RESTORE VERIFYONLY`/`CHECKSUM`, backup to URL and the role of AGs in the RPO. **The repository,77 its immutability and the cadence of the test restore belong to `backup-recovery`**; **the RPO/RTO78 that justifies synchronous mode and the failover exercise belong to `bcdr`**.79- `oracle-dba-standards` (the catalogue's other proprietary engine; a different vendor, the same80 pattern: **licensing decides the architecture**. They do not compete).81- `dotnet-standards` (**the C#/EF Core code that consumes this database is theirs**: driver,82 pool, `Microsoft.Data.SqlClient`, migrations from the application).83- `streaming-cdc-standards` (**capture is theirs**: CDC, change tracking, change event streaming,84 Debezium; **the cost in the engine belongs here**: log retention, capture jobs, impact85 on the recovery model).86- `azure-standards` (Azure SQL Database, Managed Instance, Arc, Azure Hybrid Benefit),87 `aws-standards`/`gcp-standards` (RDS for SQL Server, Cloud SQL for SQL Server).88- `vulnerability-management-standards` (**the CVE cycle and the patching window are theirs**; here89 only the mechanics of CUs and their cadence), `identity-access-management-standards`,90 `cryptography-pki-standards` (TLS and the keys TDE rests on), `secrets-management-standards`,91 `linux-hardening-standards` and `podman-systemd-containers-standards`/`kubernetes-standards`92 (the OS and the runtime when SQL Server runs outside Windows),93 `observability-standards`, `grc-compliance-standards`, `privacy-engineering-standards`,94 `iac-standards`, `firewall-policy-standards` (exposure of port 1433).95- `mysql-mariadb-dba-standards` (the catalogue's third relational engine; open source, without the96 edition variable that dominates here), `timeseries-db-standards`, `message-brokers-standards`,97 `nosql-standards`, `search-engines-standards`, `caching-cdn-standards` (other specialised98 stores).99100## 2. Editions and licensing: the dominant decision101102> **Scope warning, non-negotiable**: what is set here is **technical criteria**, not contractual103> advice. Every decision with an economic impact is validated against the **SQL Server licensing104> guide**, the current **Product Terms** and the organisation's **licence manager**.105> No price appears in this document (§8).106107### 2.1 Standard's real limits — verified on SQL Server 2025 (17.x)108109Primary source: *Editions and supported features of SQL Server 2025*, learn.microsoft.com110(also checked against the markdown of the `MicrosoftDocs/sql-docs` repository).111112| Limit | Enterprise | **Standard** | Express |113|---|---|---|---|114| Maximum compute per instance (engine) | OS maximum | **Lesser of 4 sockets or 32 cores** | Lesser of 1 socket or 4 cores |115| Maximum *buffer pool* memory per instance | OS maximum | **256 GB** | 1,410 MB |116| Columnstore segment cache | Unlimited | 32 GB | 352 MB |117| *Memory-optimized* data per database | Unlimited | 32 GB | 352 MB |118119**A change that does move a design** — footnote 2 of the document itself: *"In SQL Server 2022 (16.x)120and earlier versions, the limit is the lesser of 4 sockets or 24 cores."* That is: **Standard went121from 24 to 32 cores and its buffer pool rose to 256 GB in the 2025 version**. A sizing exercise done122on "Standard is 24 cores and 128 GB" is out of date and may be justifying an unnecessary123Enterprise. **Always verify against the page for the specific version**: the limits are per124version.125126### 2.2 What separates Enterprise from Standard (what decides the architecture)127128Enterprise only (verified in the 2025 table):129- Full **Always On availability groups**, **contained AG**, **distributed AG**, automatic130 read/write connection redirection. **Standard only has *basic availability groups***:131 *"A basic availability group supports two replicas, with one database."* — two replicas, **a132 single database**, with no readable replica.133- **FCI**: Enterprise up to **16 nodes**; Standard **2 nodes**.134- **Online index rebuild and create** (and its *resumable* version), **online schema135 change**, **online page and file restore**, *fast recovery*, mirrored backups.136- Almost all advanced *Intelligent Query Processing* (batch mode on rowstore, adaptive joins,137 memory grant feedback, cardinality feedback, DOP feedback, *automatic tuning*), **Query Store on138 secondary replicas**, parallel index maintenance, parallel `CHECKDB`, distributed partitioned139 views.140141**In Standard, and this is what usually surprises people** (and dismantles many "we need Enterprise"):142**TDE**, **backup encryption**, **backup compression**, **table and index partitioning**,143**data compression**, **columnstore**, **In-Memory OLTP**, **Query Store**, **Always Encrypted**144(also with *secure enclaves*), **row-level security**, **dynamic data masking**, **auditing**,145**Change Data Capture**, **Accelerated Database Recovery**, **optimized locking**, **backup and146restore to S3-compatible object storage**, **clusterless AG** and147—**new in 2025**— **Resource Governor**, which used to be Enterprise-only.148149Decision rule: **Enterprise is justified by real HA (a multi-database or multi-replica AG),15024×7 online maintenance or a compute ceiling**; not by features that are already in Standard.151Every Enterprise proposal is accompanied by which of those three reasons applies.152153Other edition facts verified in 2025: **Express** goes up to 1 socket/4 cores and now154includes what used to be *Express with Advanced Services*; **Enterprise Developer** and155**Standard Developer** exist as separate editions (Developer = the full functionality of its edition,156**licensed only for development and testing — never in production**); the **Web** edition is withdrawn157from 2025 onwards (2022 is the last one that includes it); on-prem *Reporting Services* is consolidated under158**Power BI Report Server**.159160### 2.3 Licensing model161162- **Per core**: **physical cores** are counted (*hyperthreading* does not count), with a per-processor163 minimum and sale in packs of 2. It is the only model available for Enterprise in new164 agreements.165- **Server + CAL**: Standard only. It stops paying off beyond a certain number of users; the166 threshold depends on the agreement — **calculate it, do not estimate it**.167- **Enterprise with Server+CAL** (legacy, not available for new agreements) is **limited to168 20 cores per instance** (footnote 1 of the editions document): an old contract may169 be imposing a performance ceiling nobody remembers.170- **Virtualisation**: with **Software Assurance**, Enterprise offers unlimited virtualisation on171 a fully licensed host; without SA, you license per VM. Counting VMs on a shared172 host without SA is the fast route to an audit finding.173174### 2.4 Software Assurance and its role in high availability — **the expensive trap**175176**Failover rights are a Software Assurance (or subscription licence) benefit.177Without SA, a passive replica is fully licensed, even if it never serves a178query.** With SA, for each licensed OSE you can run passive replicas in anticipation of179a failover (typically one for HA, one for DR and one in Azure), **provided they neither serve data nor180run active work** and do not exceed the primary's licence.181182Direct design consequences, not procurement ones:183- A **readable secondary** in an AG **stops being passive**: it is licensed. "Offloading the reports to184 the replica" is a purchase. The same goes for running backups or `CHECKDB` on the secondary185 under the current terms: **verify it before designing it**.186- The cost of HA in SQL Server is not the cluster: **it is the second node's licence if there is no SA**.187- **Verify the current Product Terms**: failover rights have been redefined more than188 once. Their wording is not fixed here; what is fixed is the obligation to check it.189190## 3. Instance architecture191192- **One instance per host as the default.** Multiple named instances split memory and CPU193 between competing engines and complicate patching; separating by container or by VM is cleaner194 and easier to license. Consolidate into **databases within one instance**, not into195 instances within a host.196- **System databases**: `master` (configuration and logins) and `msdb` (Agent, backup197 history, plans) **are part of the backup strategy** — losing them costs a rebuild of the198 environment; `model` is the template for every new database (setting the recovery model and default199 file sizes there avoids surprises); `tempdb` is recreated at startup and **is not200 backed up**.201- **Memory**: `max server memory` **always set**, leaving headroom for the OS (and for other consumers202 on the host); never left at the default. `min server memory` only if there is real competition. On Linux, limits203 via `mssql-conf` and **cgroup v2** (honoured from SQL Server 2025 and from 2022 CU 20 —204 before that, a container with a memory limit could die of OOM because the engine ignored it).205- **Parallelism**: `MAXDOP` and `cost threshold for parallelism` **explicit** from day one. The206 default value of `cost threshold` (5) is from the nineties and parallelises trivial queries:207 raising it is one of the product's best benefit/risk changes. `MAXDOP` according to208 core count and NUMA, with the exception documented per workload.209- **Storage**: data, log and `tempdb` on volumes with different I/O profiles; the transaction210 log is **sequential write and latency-sensitive** — it is the first place to put211 fast storage. NTFS formatted with a 64 KB allocation unit on Windows unless the array vendor's212 criteria say otherwise (`linux-storage-standards`/`onprem-standards` for the rest).213- **Autogrowth**: in **fixed and large** increments, never as a percentage, and with214 *Instant File Initialization* enabled (the *Perform Volume Maintenance Tasks* privilege) so that215 data growth does not freeze the instance. Autogrowth is a **safety net**,216 not a capacity strategy: files are pre-sized.217218### 3.1 `tempdb` — the setting with the most real impact219220- **Multiple data files, all of the same size and with the same autogrowth**: allocation221 page contention in `tempdb` is the classic bottleneck of busy instances, and it only222 disappears if the files are symmetric (the allocator is *round-robin* proportional to free223 space: an unequal file takes all the work). The modern installer proposes a reasonable224 number based on cores; **review it, do not accept it blindly**, and **never**225 leave a single file on a server with several cores.226- **Pre-size** so that it does not grow while hot; a dedicated and fast volume; *memory-optimized227 tempdb metadata* only on Enterprise and with a workload that justifies it.228- `tempdb` is **shared by the whole instance**: a query with a monstrous `sort` or a badly used229 `snapshot isolation` affects every database. Watch it as a global resource.230- On Linux, `tempdb` on **tmpfs** is supported: a real performance option, with the consequent231 RAM consumption accepted.232233### 3.2 Recovery models and their direct consequence234235| Model | What it implies | Recovery point |236|---|---|---|237| **SIMPLE** | The log is truncated automatically at each *checkpoint*. **There is no log backup** | Only the last full/differential. **RPO = hours**, like it or not |238| **FULL** | The log is retained **until it is backed up**. Mandatory for AGs and *log shipping* | PITR to the minute/second, if and only if there are periodic log backups |239| **BULK_LOGGED** | Minimal logging of certain bulk operations | **It breaks PITR within the interval** containing the bulk operation: you recover to the end of the log backup, not to an instant |240241**The consequence that gets forgotten**: putting a database in **FULL without scheduling log backups** does not give242better recovery — it makes **the log grow until it fills the disk** and takes the instance down. It is the243product's most frequent self-inflicted incident. Operational rule:244245- The model is determined by the **RPO derived by `bcdr-standards`**, and **FULL implies scheduled log246 backups on the same day it is enabled**. There are no half measures.247- Mandatory diagnosis when the log grows: `sys.databases.log_reuse_wait_desc` says **why**248 it cannot be reused (`LOG_BACKUP`, `ACTIVE_TRANSACTION`, `AVAILABILITY_REPLICA`,249 `REPLICATION`…). It is read **before** touching anything.250- **Forbidden**: `DBCC SHRINKFILE` on the log as a routine, and forbidden any Internet251 recipe that goes through putting the database into SIMPLE to "clean the log": **it breaks the log chain**252 (§4) and with it the recovery point.253- **VLF**: grow the log in large and few increments; thousands of VLFs slow down startup and254 recovery.255256## 4. Native backup and restore257258- **Three pieces**: **full** (the base of the chain), **differential** (everything changed since the259 last full — not since the previous differential) and **log** (the changes since the previous log260 backup, and **only** in FULL/BULK_LOGGED).261- **The log chain** is the asset: an uninterrupted sequence of log backups since a262 full. **It is broken by**: putting the database into SIMPLE (even if it goes back to FULL, a new263 full is needed), a log backup with `TRUNCATE_ONLY` from old versions, or an **out-of-band264 backup** taken by another tool that does not use `COPY_ONLY`. Hence the rule:265 **every ad-hoc backup is taken with `COPY_ONLY`** — a normal full from an external tool266 resets the differential base and leaves the scheduled differentials meaningless.267- **Verification**: `WITH CHECKSUM` on the backup and `RESTORE VERIFYONLY WITH CHECKSUM` as the automatic268 minimum. That is **not** a restore: it only says the file is readable.269- **`RESTORE ... WITH NORECOVERY`** to chain the differential and the logs; **`WITH STANDBY`** leaves the270 database **readable between log applications** (an undo file) — it is the right tool271 for *log shipping* with a queryable secondary, and the cheap way to have a readable delayed272 copy without licensing readable replicas.273- **Restore to a point in time**: `RESTORE ... WITH STOPAT` (or `STOPATMARK`) over the log chain.274 Rehearse it before needing it: human error is recovered with this, not with an AG.275- **Backup to URL** (object storage): supported to *block blobs* with SAS; in 2025 also276 **to S3-compatible storage via REST**, on Enterprise **and Standard**. It is a destination, not277 a policy — **the repository, immutability and the 3-2-1 rule belong to278 `backup-recovery-standards`**. On Linux, *backup to URL* with a *page blob* is not supported.279- **Encryption**: `BACKUP ... WITH ENCRYPTION` available in Standard and Enterprise; the **certificate or280 asymmetric key is backed up and held outside the system being backed up** (key custody,281 `bcdr-standards`). An encrypted backup whose certificate was lost with the server is a useless282 copy: **it is the most common way of discovering there was no DR**.283- **Catalogue**: `msdb.dbo.backupset`/`backupmediafamily` is the record of what exists. It is queried284 to detect **coverage gaps** and it is backed up with `msdb`.285286> **Shared invariant, with no nuance**: **a backup without a tested restore does not exist.** The gate is not287> "the job finished green", it is "we restored, timed it and validated it". The cadence and the record288> of the exercise are set by `backup-recovery-standards`; the engine's procedure belongs here.289290## 5. High availability291292- **Availability Groups (AG)**: replication at the **database** level via log shipping.293 - **Synchronous** = RPO 0 and automatic failover possible, **at the cost of latency on every commit**294 (the primary waits for the hardening on the secondary). **Asynchronous** = no latency impact,295 with potential loss and **only forced manual failover, with data loss**. The choice is296 dictated by `bcdr-standards`'s RPO, not by convenience.297 - **Listener**: the application connects to the *listener*, never to a node's name, and the298 connection string declares `MultiSubnetFailover=True` when there are different subnets. Without that, the failover299 "works" and the application does not come back.300 - **Readable replica**: it is not free in two senses. **Licensing** (§2.4: it stops being passive) and301 **performance** — read-only queries implicitly take *snapshot isolation*,302 which generates **row versioning on the primary** (14 bytes per modified row) and increases303 `tempdb` usage on the secondary; besides, the `REDO` can block against long queries and304 trigger *redo lag*, which is real RPO lost. It is monitored.305 - On Enterprise, per the 2025 documentation: up to **8 secondary replicas**. **Standard only306 has *basic AG***: 2 replicas, **1 database**, with no readable secondary (§2.2).307- **FCI (failover cluster instance)**: it shares **storage**; it protects against node308 failure, **not against data failure** (a corrupt LUN is corrupt for everyone). It requires a cluster309 (WSFC/Pacemaker) and shared storage. Enterprise 16 nodes, Standard 2.310- **AG vs FCI, in one line**: FCI protects the server with a single copy of the data; AG protects the311 data with several copies and allows geographic separation. **They are combined** (an FCI as a replica of an312 AG) only with a written reason: the complexity multiplies.313- **Cluster boundary, declared**:314 - **Windows** → **WSFC** is the substrate of FCI and of AGs (except *clusterless*). Its quorum315 (disk, file share or cloud witness), its vote model and its operation belong **to this316 skill**. The domain, the accounts and the DNS it rests on belong to317 `windows-server-ad-standards`.318 - **Linux** → the manager is **Pacemaker/Corosync** and **`ha-clustering-standards`** rules,319 including its invariant: **without tested fencing there is no HA, there is deferred corruption**. What belongs to the engine320 is: `CLUSTER_TYPE = EXTERNAL` with `FAILOVER_MODE = EXTERNAL` (the only combination with automatic321 failover), the **`mssql-server-ha`** package —with HA agent v2 from **SQL Server 2025322 CU 3**— and the AG and IP resources.323 - **`CLUSTER_TYPE = NONE`** (clusterless AG): **manual failover only**, intended for *read-scale*324 and rolling upgrades. **It is not high availability**: do not sell it as such.325 - **An AG or FCI cannot span WSFC and Pacemaker.** For mixed scenarios there are only two routes,326 both AG-based: `CLUSTER_TYPE = NONE` or a **distributed AG**.327- **Mandatory rehearsal**: failover is tested with the application inside the exercise (reconnection,328 pool, DNS, `MultiSubnetFailover`), at `bcdr-standards`'s cadence. An AG that has never329 failed over is an assumption.330- **Forbidden**: *database mirroring* in new designs (**deprecated**), and presenting an AG as331 a substitute for backup — a `DELETE` replicates in milliseconds.332333## 6. SQL Server on Linux and in containers334335It is a first-class deployment, **with known cutbacks**. Not supported on Linux (verified in336the 2025 documentation): **merge replication**, **FILESTREAM and FileTable**, **extended337system procedures (`xp_cmdshell`)**, **linked servers to non-SQL Server sources**338(use PolyBase), **`EXTERNAL_ACCESS`/`UNSAFE` CLR assemblies**, **Buffer Pool Extension**,339**database mirroring**, **Always Encrypted with secure enclaves**, **EKM** (except with Azure Key340Vault from 2022 CU 12), **integrated Windows authentication for linked servers and for341AG endpoints** (the endpoints use certificate authentication), **Analysis Services**,342**Reporting Services**, several Agent subsystems (CmdExec, PowerShell, SSIS, SSAS, SSRS),343**Agent alerts** and *Managed Backup*. In addition: **a single instance per host** (there is no SQL344Browser and no named instances) and **Linux deployments are not FIPS compliant**. From345SQL Server 2025, **SLES is no longer supported**.346347Containers: the official `mssql/server` image. Criteria:348- Pin the image **by digest**, explicit `MSSQL_PID`, the `sa` password from the secrets349 manager (never in the `Dockerfile`, the `compose` file or the command line), and **persistent350 volumes** for data, log and backups — a database container with no volume is351 scheduled data loss.352- **cgroup v2** honoured from 2025 (and 2022 CU 20). With a cgroup v2 CPU limit, the engine keeps353 reporting the host's CPUs: align it with `ALTER SERVER CONFIGURATION SET PROCESS AFFINITY` and354 *trace flag* 8002 so that parallelism decisions are not taken on a false count.355- Features that depend on the **Azure Arc** agent (Entra ID, Purview, pay-as-you-go,356 Defender) **are not supported in containers**.357- On Kubernetes, `kubernetes-standards` rules for the rest; container HA via a *statefulset*358 and restart **is not an AG**: it is a restart, with its own RTO.359360## 7. Performance361362### 7.1 Method: wait statistics and Query Store363364- **Diagnose by waits**, not by hunches: `sys.dm_os_wait_stats` (cumulative since365 startup — **measure by delta between two instants**, never the raw cumulative),366 `sys.dm_exec_requests`/`sys.dm_exec_session_wait_stats` for what is happening now, and367 `sp_WhoIsActive` as the first-line tool. Ignore the benign background waits before368 drawing conclusions.369- **Query Store is the tool that changed diagnosis** and it is in **every edition**370 (enabled by default on new databases from 2022): it stores queries, plans and execution371 statistics **with history**, so that "yesterday it was fine and today it is not" goes from anecdote to evidence.372 Criteria: **enabled on every production database**, with the capture mode and retention sized373 (`AUTO` in general), and **watching that it does not fill up** —if the store goes `READ_ONLY` it stops374 capturing silently—. `sp_QuickieStore` to query it without writing SQL by hand.375- **Plan regression**: identify with Query Store the query with several plans and force the good one376 (`sys.sp_query_store_force_plan`) as a **temporary containment measure**, with a review date and the377 cause investigated afterwards. A forced plan nobody reviews is debt: the engine may stop378 being able to apply it and nobody finds out. On Enterprise there is *automatic tuning* to fix379 regressions automatically — **with supervision**, not as an autopilot.380- **Statistics**: `AUTO_CREATE_STATISTICS` and `AUTO_UPDATE_STATISTICS` enabled; manual381 updates after bulk loads and on large tables where the automatic threshold arrives late.382 `AUTO_UPDATE_STATISTICS_ASYNC` with judgement. The number 1 cause of a bad plan is an old383 statistic, not the optimiser.384- **Parameter sniffing**: a classic symptom (the same procedure, erratic performance). It is attacked385 in this order: statistics → rewriting the query → targeted `OPTIMIZE FOR`/`RECOMPILE` →386 *Query Store hints* (preferable to touching the code) → separating code paths.387 A global `WITH RECOMPILE` is an expensive sticking plaster.388389### 7.2 Indexes390391- **One *clustered* index per table, almost always**: the table *is* the clustered index.392 A **narrow, increasing, unique and immutable** key (an `int/bigint identity` or a `sequence`);393 **a random GUID as the clustered key** fragments and widens every nonclustered index394 (which includes it as a pointer) — vetoed unless justified by measurement. A *heap* (a table without a clustered395 index) is the exception, not the default.396- **Nonclustered**: by real query pattern, with `INCLUDE` to achieve measured **coverage**, and397 **filtered** for hot subsets. Every index is justified with a plan; every index398 costs on every `INSERT`/`UPDATE`/`DELETE` and on every backup.399- **Retirement**: `sys.dm_db_index_usage_stats` to detect indexes with no reads and with writes.400 Before dropping, **disable** and observe. Careful: usage statistics reset with the401 instance — do not decide on a short window.402- **Missing indexes**: the *missing indexes* DMVs are a **hint**, not an order. Applying their403 suggestions wholesale is one of the worst frequent practices: they overlap, duplicate and fatten404 the table. Consolidate by hand (`sp_BlitzIndex` helps).405- **Fragmentation and the myth of routine maintenance**: reorganising or rebuilding indexes every406 night **out of habit** is, on modern storage (SSD/NVMe/array with cache), almost always407 useless work that generates **tonnes of log** (and therefore huge log backups, AG traffic408 and I/O pressure) for a marginal improvement. Criteria:409 - What almost always matters is **updating the statistics**, not defragmentation.410 - Rebuild with high thresholds and only on large and genuinely fragmented indexes;411 **`ONLINE = ON` is Enterprise** — on Standard, a rebuild **blocks**: schedule it in a412 window or do not do it.413 - Watch the global `FILLFACTOR`: lowering it "to reduce fragmentation" wastes memory and I/O414 on every read.415416### 7.3 Blocking, isolation and **why RCSI is usually the answer**417418- SQL Server's default is `READ COMMITTED` **with locks**: readers block writers419 and vice versa. That is where most of the blocking attributed to "the database is slow" comes from, and where420 the `WITH (NOLOCK)` antipattern comes from, which **is not an optimisation: it is reading dirty data**, with421 duplicated or missing reads and, in real cases, error 601. **Forbidden as a general practice.**422- **`READ_COMMITTED_SNAPSHOT ON` (RCSI)** changes the database's default level to row423 versioning: **readers stop blocking writers** without changing a line of application424 code. It is, in practice, the right answer for the vast majority of OLTP workloads —425 the behaviour anyone coming from PostgreSQL or Oracle is already used to. **Its cost,426 declared**: row versioning in `tempdb` (size it), 14 extra bytes per versioned row,427 and a real semantic change —certain locks that today serialise accidentally disappear,428 so there is application logic that relied on them without knowing. Hence: **it is enabled with429 testing**, not hot on a Friday, and it requires momentary exclusive access to the database.430- `ALLOW_SNAPSHOT_ISOLATION` (explicit *snapshot* isolation) is different: a transaction with a fully431 consistent view and **update conflicts** the application must retry. It is used432 where it is needed, not by default.433- ***Deadlocks***: they are **captured**, not guessed — the `system_health` extended events434 session records them by default; read the **deadlock graph** to know which435 resources and in what order. Solution in order of preference: **consistent access order** →436 indexes that reduce the scope of the lock → shorter transactions → isolation level →437 **retry with backoff in the application** (always, as a net: a deadlock is a recoverable438 error, not a failure of the user's request).439- **Short transactions with no external interaction inside**: no HTTP calls and no waiting for440 a user with an open transaction. Set `LOCK_TIMEOUT` and command timeouts in the application.441- **`Optimized locking`** (2022+, and in Standard in 2025) reduces escalation and the number of locks:442 evaluate it, with the same before/after measurement discipline.443444### 7.4 T-SQL: antipatterns that kill plans445446- **Non-*SARGable* predicates**: a function on the column (`WHERE YEAR(date)=2026`,447 `WHERE UPPER(col)=…`), a computation on the column, `LIKE '%something'`. Rewrite to ranges.448- **Implicit type conversion** (`nvarchar` against `varchar`, text against number): it invalidates the449 index silently. It is the most expensive and easiest-to-fix performance bug.450- **User-defined scalar functions** in `SELECT`/`WHERE`: historically row-by-row451 execution. *Scalar UDF inlining* exists (all editions), but it does not cover every case:452 prefer inline table-valued functions (`iTVF`) or expressions.453- **Cursors and `WHILE` loops** where a set-based operation would fit.454- **`SELECT *`** in interfaces and views; **views nested on views** (the optimiser ends up455 with plans impossible to reason about).456- **`sp_executesql` with concatenated literals**: massive recompilation **and SQL injection**.457 Parameters always. It is a security veto as well as a performance one.458- `MERGE`: use with caution (a long history of bugs and blocking). Explicit `INSERT`/`UPDATE`459 with logical `ON CONFLICT` handling is usually more predictable.460- **Triggers** with non-obvious side effects and without set handling (`inserted`/`deleted`461 have **several rows**): a chronic source of logical corruption.462- Treat `NULL`, `ANSI_NULLS` and `SET` options consciously: they change plans and affect filtered463 and indexed indexes.464465## 8. Maintenance: what every instance needs466467Four jobs, not one fewer, all with an **alert when they fail** (a maintenance job that468fails silently is worse than not having it):4694701. **Integrity**: `DBCC CHECKDB` with `DATA_PURITY`, at a weekly cadence at minimum on databases that471 matter. **It is the only control that detects corruption**; without it, corruption is discovered the472 day you restore. On Enterprise there is parallel checking; on large databases, split by473 tables/filegroups or run it on a restored copy —which, incidentally, **tests the474 restore**: two controls for the price of one.4752. **Statistics** (priority) and **indexes** (with high thresholds, §7.2).4763. **Backups** full/differential/log according to the recovery model (§4).4774. **History purge**: `msdb` (backup and job history), Query Store, extended event478 sessions, old backup files. An unpurged `msdb` degrades the Agent itself.479480**Reference community solutions — status verified in August 2026**:481- **Ola Hallengren, *SQL Server Maintenance Solution*** (`MaintenanceSolution.sql`,482 `DatabaseBackup`, `DatabaseIntegrityCheck`, `IndexOptimize`): **alive and updated**, with declared support483 for SQL Server 2017, 2019, 2022 and **2025** plus Azure SQL MI. **It is the default**: you do not484 write your own backup and maintenance scripts unless there is a documented reason. It is distributed by485 dated versions, not by semver: download the latest from `ola.hallengren.com`.486- **dbatools** (PowerShell, >500 commands): **alive** — 2.8.2 published on the PowerShell Gallery in487 May 2026, with continuous activity in `dataplat/dbatools`. It is the right way to automate488 (`Install-DbaMaintenanceSolution`, migrations, inventory) rather than GUIs and hand-written scripts.489- **First Responder Kit** (`sp_Blitz`, `sp_BlitzIndex`, `sp_BlitzCache`, `sp_BlitzFirst`): **alive**,490 with the *"The First Responder Kit 2026"* release (2026-07-08) and an annual versioning model. Real warnings491 from the author himself: since April 2026 **it only supports SQL Server 2016 SP2 and later**,492 `sp_Blitz` **requires `sp_ineachdb`**, several scripts were deprecated (`sp_BlitzQueryStore` →493 `sp_QuickieStore`), and there was a release marked by the author as unreliable for containing a lot of494 AI-edited code. **Test before deploying**, especially with case-sensitive collation.495- **`sp_WhoIsActive`** (Adam Machanic) for live diagnosis.496- Cross-cutting rule: **any third-party script installed on a production instance is497 reviewed line by line** — it runs with high privileges inside the engine.498499## 9. Security500501- **Authentication**: **integrated mode (Windows/Kerberos) by default**; SQL authentication only502 when there is no alternative (Linux, containers, third parties), with passwords from the secrets503 manager. **Everything relating to Kerberos, SPN, delegation, managed service accounts504 (gMSA/dMSA), GPO and Tier 0 is delegated entirely to `windows-server-ad-standards`** — here only505 what the engine requires is declared: a dedicated service account with no domain privileges, a **correct506 SPN** (without it the connection silently falls back to NTLM and Kerberos and delegation are lost),507 and no domain administrative account running the service.508- **`sa`**: disabled and renamed; **never** as an application account. No application login509 in `sysadmin` or in `db_owner`: minimum permissions per schema, ideally via510 procedures or defined database roles.511- **Roles**: use server and database roles (including user-defined server roles) instead of granting to512 individual principals. Periodically review `sysadmin`,513 `securityadmin`, `CONTROL SERVER` and `db_owner` membership — it is the access review that514 nobody does until the audit.515- **Surface**: `xp_cmdshell` **disabled** (and absent on Linux); *CLR* disabled unless516 needed and never `UNSAFE`; *Ad Hoc Distributed Queries* disabled; **SQL Browser** off517 where it is not needed; **port 1433 never exposed to the Internet or to the user network**518 (`firewall-policy-standards`), and **encryption in transit mandatory** (`Encrypt=True` with519 certificate validation in the connection string — a `TrustServerCertificate=True` in520 production voids the protection).521- **Linked servers**: permanent trust bridges. Inventoried, with the least-privileged522 account and reviewed; never mapping to an administrative account.523- **Encryption at rest**: **TDE is available in Standard and Enterprise** (not in Express) — the myth524 that "TDE is Enterprise only" is out of date and has justified unnecessary purchases. TDE protects525 files and backups **at rest**, not from a user with permissions. **The certificate/DEK is backed up526 and held outside the server**: losing it is losing the data (§4). For column-level sensitive527 data, **Always Encrypted** (also in Standard, even with secure enclaves on Windows),528 with the key outside the engine — it is the only protection against the DBA themselves. Keys and their529 lifecycle, in `cryptography-pki-standards`/`secrets-management-standards`.530- **Auditing**: *SQL Server Audit* (server and database auditing, available in all531 editions in 2025) over what matters —permission changes, access to classified data,532 use of privileged accounts—, with output to the SIEM. Do not audit everything: nobody reads it and it costs.533- **Patching**: SQL Server is served through **Cumulative Updates** (since 2017 there are no Service Packs).534 Apply CUs on a cadence, in a window, with a planned rollback; security bulletins arrive535 through Patch Tuesday. In 2026 significant engine vulnerabilities have been published —among them536 critical deserialisation RCEs reaching from SQL Server 2016 SP3 through 2025, and elevation537 of privilege to `sysadmin`— which reinforces two things: **patch** and **do not grant privileges that538 turn an elevation into a disaster**. Triage and the window are set by539 `vulnerability-management-standards`.540541## 10. Versions and support — verified in a primary source542543Data taken from the lifecycle pages on learn.microsoft.com (August 2026). **It is the data544that ages fastest: always re-verify.**545546| Version | Start | End of *mainstream* support | End of extended support |547|---|---|---|---|548| SQL Server 2016 | 2016-06-01 | 2021-07-13 | **2026-07-14 — already expired** |549| SQL Server 2017 | 2017-09-29 | 2022-10-11 | **2027-10-12** |550| SQL Server 2019 | 2019-11-04 | 2025-02-28 | **20551552…(truncated)