Thursday, August 18, 2011
SQL SERVER AUDIT
Description: Shows what I look for when I take over existing servers.
Don't just hit F5 - read through each step to learn what I'm
checking for, and how to fix things when they indicate problems.
/*
Backups! First and foremost, before we touch anything, check backups.
Check when each database has been backed up. If databases aren't being
backed up, check the maintenance plans or scripts. If you don't have
scripts, check http://ola.hallengren.com.
*/
SELECT d.name, MAX(b.backup_finish_date) AS last_backup_finish_date
FROM master.sys.databases d
LEFT OUTER JOIN msdb.dbo.backupset b ON d.name = b.database_name AND b.type = 'D'
WHERE d.database_id NOT IN (2, 3) -- Bonus points if you know what that means
GROUP BY d.name
ORDER BY 2 DESC
/*
Where are the backups going? Ideally, we want them on a different server.
If the backups are being taken to this same server, and the server's RAID
card or motherboard goes bad, we're in trouble. We sort by media_set_id
descending because it's the primary key on the table, so it'll fly even
when MSDB is on really slow drives.
For more information about where your backups should go, check out:
http://www.brentozar.com/sql/backup-best-practices/
*/
SELECT TOP 100 physical_device_name, * FROM msdb.dbo.backupmediafamily ORDER BY media_set_id DESC
/*
Transaction log backups - do we have any databases in full recovery mode
that haven't had t-log backups? If so, we should think about putting it in
simple recovery mode or doing t-log backups.
*/
SELECT d.name, d.recovery_model, d.recovery_model_desc
FROM master.sys.databases d
LEFT OUTER JOIN msdb.dbo.backupset b ON d.name = b.database_name AND b.type = 'L'
WHERE d.recovery_model IN (1, 2) AND b.type IS NULL AND d.database_id NOT IN (2, 3)
/*
If there are any databases in full recovery mode with no t-log backups,
show the filesize of the according ldf.
*/
SELECT
d.name AS [db_name]
, f.name AS [ldf_name]
, f.physical_name
, f.size * 8 / 1024.00 AS [size_in_mb]
, d.recovery_model_desc
FROM sys.master_files f
JOIN sys.databases d ON f.database_id = d.database_id
LEFT OUTER JOIN msdb.dbo.backupset b ON d.name = b.database_name AND b.type = 'L'
WHERE d.recovery_model IN (1, 2) AND b.type IS NULL AND d.database_id NOT IN (2, 3) AND f.type = 1 -- only show log files
ORDER BY f.size DESC
/*
Is the MSDB backup history cleaned up? If you have data older than a couple
of months, this is a problem. You need to set up backup cleanup jobs.
For more information on why this can be a problem?
http://www.brentozar.com/archive/2009/09/checking-your-msdb-cleanup-jobs/
*/
SELECT TOP 1 backup_start_date, *
FROM msdb.dbo.backupset
ORDER BY backup_set_id ASC
/*
When was the last time DBCC finished successfully?
DBCC CHECKDB checks databases for corruption. You won't know
Script is from http://sqlserverpedia.com/wiki/Last_clean_DBCC_CHECKDB_date
To get sample corrupt databases - http://sqlskills.com/pastConferences.asp
*/
CREATE TABLE #temp (
ParentObject VARCHAR(255)
, [Object] VARCHAR(255)
, Field VARCHAR(255)
, [Value] VARCHAR(255)
)
CREATE TABLE #DBCCResults (
ServerName VARCHAR(255)
, DBName VARCHAR(255)
, LastCleanDBCCDate DATETIME
)
EXEC master.dbo.sp_MSforeachdb
@command1 = 'USE [?] INSERT INTO #temp EXECUTE (''DBCC DBINFO WITH TABLERESULTS'')'
, @command2 = 'INSERT INTO #DBCCResults SELECT @@SERVERNAME, ''?'', Value FROM #temp WHERE Field = ''dbi_dbccLastKnownGood'''
, @command3 = 'TRUNCATE TABLE #temp'
--Delete duplicates due to a bug in SQL Server 2008
;WITH DBCC_CTE AS
(
SELECT ROW_NUMBER() OVER (PARTITION BY ServerName, DBName, LastCleanDBCCDate ORDER BY LastCleanDBCCDate) RowID
FROM #DBCCResults
)
DELETE FROM DBCC_CTE WHERE RowID > 1;
SELECT
ServerName
, DBName
, CASE LastCleanDBCCDate
WHEN '1900-01-01 00:00:00.000' THEN 'Never ran DBCC CHECKDB'
ELSE CAST(LastCleanDBCCDate AS VARCHAR) END AS LastCleanDBCCDate
FROM #DBCCResults
ORDER BY 3
DROP TABLE #temp, #DBCCResults;
/*
If any databases have never experienced the magic of DBCC, consider doing that
as soon as practical. DBCC CHECKDB is a CPU & IO intensive operation, so
consider doing it after business hours. For more information:
http://www.sqlskills.com/blogs/paul/post/CHECKDB-From-Every-Angle-Consistency-Checking-Options-for-a-VLDB.aspx
For the demo, we'll run it on a small database right away. This won't work on
your machine unless you have a database named TimeTracking.
*/
--DBCC CHECKDB(TimeTracking)
/*
Maybe there were DBCC jobs that aren't running.
Speaking of which, are jobs failing, and who owns them?
*/
SET NOCOUNT ON
DECLARE @MaxLength INT
SET @MaxLength = 50
DECLARE @xp_results TABLE (
job_id uniqueidentifier NOT NULL,
last_run_date nvarchar (20) NOT NULL,
last_run_time nvarchar (20) NOT NULL,
next_run_date nvarchar (20) NOT NULL,
next_run_time nvarchar (20) NOT NULL,
next_run_schedule_id INT NOT NULL,
requested_to_run INT NOT NULL,
request_source INT NOT NULL,
request_source_id sysname
COLLATE database_default NULL,
running INT NOT NULL,
current_step INT NOT NULL,
current_retry_attempt INT NOT NULL,
job_state INT NOT NULL
)
DECLARE @job_owner sysname
DECLARE @is_sysadmin INT
SET @is_sysadmin = isnull (is_srvrolemember ('sysadmin'), 0)
SET @job_owner = suser_sname ()
INSERT INTO @xp_results
EXECUTE sys.xp_sqlagent_enum_jobs @is_sysadmin, @job_owner
UPDATE @xp_results
SET last_run_time = right ('000000' + last_run_time, 6),
next_run_time = right ('000000' + next_run_time, 6)
SELECT j.name AS JobName,
j.enabled AS Enabled,
sl.name AS OwnerName,
CASE x.running
WHEN 1
THEN
'Running'
ELSE
CASE h.run_status
WHEN 2 THEN 'Inactive'
WHEN 4 THEN 'Inactive'
ELSE 'Completed'
END
END
AS CurrentStatus,
coalesce (x.current_step, 0) AS CurrentStepNbr,
CASE
WHEN x.last_run_date > 0
THEN
convert (datetime,
substring (x.last_run_date, 1, 4)
+ '-'
+ substring (x.last_run_date, 5, 2)
+ '-'
+ substring (x.last_run_date, 7, 2)
+ ' '
+ substring (x.last_run_time, 1, 2)
+ ':'
+ substring (x.last_run_time, 3, 2)
+ ':'
+ substring (x.last_run_time, 5, 2)
+ '.000',
121
)
ELSE
NULL
END
AS LastRunTime,
CASE h.run_status
WHEN 0 THEN 'Fail'
WHEN 1 THEN 'Success'
WHEN 2 THEN 'Retry'
WHEN 3 THEN 'Cancel'
WHEN 4 THEN 'In progress'
END
AS LastRunOutcome,
CASE
WHEN h.run_duration > 0
THEN
(h.run_duration / 1000000) * (3600 * 24)
+ (h.run_duration / 10000 % 100) * 3600
+ (h.run_duration / 100 % 100) * 60
+ (h.run_duration % 100)
ELSE
NULL
END
AS LastRunDuration
FROM @xp_results x
LEFT JOIN
msdb.dbo.sysjobs j
ON x.job_id = j.job_id
LEFT OUTER JOIN
msdb.dbo.syscategories c
ON j.category_id = c.category_id
LEFT OUTER JOIN
msdb.dbo.sysjobhistory h
ON x.job_id = h.job_id
AND x.last_run_date = h.run_date
AND x.last_run_time = h.run_time
AND h.step_id = 0
LEFT OUTER JOIN sys.syslogins sl ON j.owner_sid = sl.sid
/*
Right up there with data integrity, security's really important.
Who else has sysadmin or securityadmin rights on this instance?
I care about securityadmin users because they can add themselves to the SA
role at any time to do their dirty work, then remove themselves back out.
Don't think of them as other sysadmins.
Think of them as users who can get you fired.
*/
SELECT l.name, l.denylogin, l.isntname, l.isntgroup, l.isntuser
FROM master.sys.syslogins l
WHERE l.sysadmin = 1 OR l.securityadmin = 1
ORDER BY l.isntgroup, l.isntname, l.isntuser
/*
Now would be an excellent time to open up a Word doc and start documenting
your findings, which helps you prove your worth as a DBA. And for every
SQL authentication user in that list, try logging in with a blank password.
In your Blitz document, if SA includes Builtin\Administrators, list the
server's local administrators.
*/
/*
Let's review some server-level security & configuration settings.
*/
EXEC dbo.sp_configure 'show advanced options', 1
GO
RECONFIGURE
GO
EXEC dbo.sp_configure
/*
Look for anything that's been changed from the default value.
What? You don't know the defaults by heart? Well, me neither.
In SSMS, go into the Object Explorer, then right-click on the server name.
Click Reports, Standard Reports, Server Dashboard, and then expand the
section Non Default Configuration Options. It'll show everything that
deviates from the defaults.
Below, I set advanced options off, but that's just for demo purposes.
You can leave that on if you like.
EXEC dbo.sp_configure 'show advanced options', 0
GO
RECONFIGURE
GO
*/
/*
Check for startup stored procedures. These live in the master database, and
they run automatically when SQL Server starts up. They're sometimes left
behind by ambitious auditors or evil employees.
For more information about startup stored procs, read:
http://www.mssqltips.com/tip.asp?tip=1574
*/
USE master
GO
SELECT *
FROM master.INFORMATION_SCHEMA.ROUTINES
WHERE OBJECTPROPERTY(OBJECT_ID(ROUTINE_NAME),'ExecIsStartup') = 1
/*
SQL Server 2008 & above only - is auditing enabled? If so, it might be
writing to an audit path that will fill up, or the server might be set to
stop if the file path isn't available. Let's see if there's any audits.
For a video explaining the SQL Server auditing options, check out:
http://sqlserverpedia.com/blog/sql-server-2008/guest-podcast-auditing-your-database-server/
*/
SELECT * FROM sys.dm_server_audit_status
/*
Server settings can be made outside of sp_configure too. The easiest way
to check out the service settings are to go into Start, Programs,
Microsoft SQL Server, Configuration Tools, SQL Server Configuration Manager.
Go there now, and drill into SQL Server Services, then right-click on each
service and hit Properties. The advanced properties for the SQL Server
service itself can hide some startup parameters.
Next, check Instant File Initialization. Take a note of the service account
SQL Server is using, and then run secpol.msc. Go into Local Policy, User
Rights Assignment, Perform Volume Maintenance Tasks. Double-click on that
and add the SQL Server service account. This lets SQL Server grow data
files instantly. For more info:
http://www.sqlskills.com/blogs/kimberly/post/Instant-Initialization-What-Why-and-How.aspx
There's a few more server-level things I like to check, but I use the SSMS
GUI. Go into Server Objects, and check out what's under Endpoints, Linked
Servers, Resource Governor, and Triggers. If any of these objects exist, you
want to research to find out what they're being used for.
*/
SELECT * FROM sys.endpoints WHERE type <> 2
SELECT * FROM sys.resource_governor_configuration
SELECT * FROM sys.server_triggers
SELECT * FROM sys.servers
/*
Then go into Management, Resource Governor - that's another landmine.
Now as long as we're in the SSMS GUI, let's set up Database Mail. It's
possible to script that out, but I like the GUI for that since I often use
wildly different parameters for different clients or departments.
Once Database Mail is set up, the below script will test it.
*/
EXEC msdb.dbo.sp_send_dbmail
@recipients = 'email@domain.com',
@body = @@SERVERNAME,
@subject = 'Testing SQL Server Database Mail - see body for server name';
GO
/*
After enabling Database Mail, the below script sets up a default set of
notifications for problems. In this section, replace these strings:
- 'The Database Administrator' - your name goes here
- 'YourEmailAddress@Hotmail.com' - your email
- '8005551212@cingularme.com' - your phone/pager's email address
*/
USE [msdb]
GO
EXEC msdb.dbo.sp_add_operator @name=N'The Database Administrator',
@enabled=1,
@weekday_pager_start_time=0,
@weekday_pager_end_time=235959,
@saturday_pager_start_time=0,
@saturday_pager_end_time=235959,
@sunday_pager_start_time=0,
@sunday_pager_end_time=235959,
@pager_days=127,
@email_address=N'YourEmailAddress@Hotmail.com',
@pager_address=N'8005551212@cingularme.com',
@category_name=N'[Uncategorized]'
GO
EXEC msdb.dbo.sp_add_alert @name=N'Severity 016',
@message_id=0,
@severity=16,
@enabled=1,
@delay_between_responses=60,
@include_event_description_in=1,
@job_id=N'00000000-0000-0000-0000-000000000000'
GO
EXEC msdb.dbo.sp_add_notification @alert_name=N'Severity 016', @operator_name=N'The Database Administrator', @notification_method = 7
GO
EXEC msdb.dbo.sp_add_alert @name=N'Severity 017',
@message_id=0,
@severity=17,
@enabled=1,
@delay_between_responses=60,
@include_event_description_in=1,
@job_id=N'00000000-0000-0000-0000-000000000000'
GO
EXEC msdb.dbo.sp_add_notification @alert_name=N'Severity 017', @operator_name=N'The Database Administrator', @notification_method = 7
GO
EXEC msdb.dbo.sp_add_alert @name=N'Severity 018',
@message_id=0,
@severity=18,
@enabled=1,
@delay_between_responses=60,
@include_event_description_in=1,
@job_id=N'00000000-0000-0000-0000-000000000000'
GO
EXEC msdb.dbo.sp_add_notification @alert_name=N'Severity 018', @operator_name=N'The Database Administrator', @notification_method = 7
GO
EXEC msdb.dbo.sp_add_alert @name=N'Severity 019',
@message_id=0,
@severity=19,
@enabled=1,
@delay_between_responses=60,
@include_event_description_in=1,
@job_id=N'00000000-0000-0000-0000-000000000000'
GO
EXEC msdb.dbo.sp_add_notification @alert_name=N'Severity 019', @operator_name=N'The Database Administrator', @notification_method = 7
GO
EXEC msdb.dbo.sp_add_alert @name=N'Severity 020',
@message_id=0,
@severity=20,
@enabled=1,
@delay_between_responses=60,
@include_event_description_in=1,
@job_id=N'00000000-0000-0000-0000-000000000000'
GO
EXEC msdb.dbo.sp_add_notification @alert_name=N'Severity 020', @operator_name=N'The Database Administrator', @notification_method = 7
GO
EXEC msdb.dbo.sp_add_alert @name=N'Severity 021',
@message_id=0,
@severity=21,
@enabled=1,
@delay_between_responses=60,
@include_event_description_in=1,
@job_id=N'00000000-0000-0000-0000-000000000000'
GO
EXEC msdb.dbo.sp_add_notification @alert_name=N'Severity 021', @operator_name=N'The Database Administrator', @notification_method = 7
GO
EXEC msdb.dbo.sp_add_alert @name=N'Severity 022',
@message_id=0,
@severity=22,
@enabled=1,
@delay_between_responses=60,
@include_event_description_in=1,
@job_id=N'00000000-0000-0000-0000-000000000000'
GO
EXEC msdb.dbo.sp_add_notification @alert_name=N'Severity 022', @operator_name=N'The Database Administrator', @notification_method = 7
GO
EXEC msdb.dbo.sp_add_alert @name=N'Severity 023',
@message_id=0,
@severity=23,
@enabled=1,
@delay_between_responses=60,
@include_event_description_in=1,
@job_id=N'00000000-0000-0000-0000-000000000000'
GO
EXEC msdb.dbo.sp_add_notification @alert_name=N'Severity 023', @operator_name=N'The Database Administrator', @notification_method = 7
GO
EXEC msdb.dbo.sp_add_alert @name=N'Severity 024',
@message_id=0,
@severity=24,
@enabled=1,
@delay_between_responses=60,
@include_event_description_in=1,
@job_id=N'00000000-0000-0000-0000-000000000000'
GO
EXEC msdb.dbo.sp_add_notification @alert_name=N'Severity 024', @operator_name=N'The Database Administrator', @notification_method = 7
GO
EXEC msdb.dbo.sp_add_alert @name=N'Severity 025',
@message_id=0,
@severity=25,
@enabled=1,
@delay_between_responses=60,
@include_event_description_in=1,
@job_id=N'00000000-0000-0000-0000-000000000000'
GO
EXEC msdb.dbo.sp_add_notification @alert_name=N'Severity 025', @operator_name=N'The Database Administrator', @notification_method = 7
GO
/* Specific Alert for Error 825
http://www.sqlskills.com/BLOGS/PAUL/post/A-little-known-sign-of-impending-doom-error-825.aspx
*/
EXEC msdb.dbo.sp_add_alert @name=N'Error 825',
@message_id=825,
@severity=0,
@enabled=1,
@delay_between_responses=60,
@include_event_description_in=1,
@job_id=N'00000000-0000-0000-0000-000000000000'
GO
EXEC msdb.dbo.sp_add_notification @alert_name=N'Error 825', @operator_name=N'The Database Administrator', @notification_method = 7
GO
/*
A few more checks at the server level. Go into the Windows Event Logs,
and review any errors in the System and Application events. This is where
hardware-level errors can show up too, like failed hard drives.
*/
/*
I don't like any surprises in the system databases. Let's check the list of
objects in master and model. I don't want to see any rows returned from
these four queries - if there are objects in the system databases, I want to
ask why, and get them removed if possible.
*/
SELECT * FROM master.sys.tables WHERE name NOT IN ('spt_fallback_db', 'spt_fallback_dev', 'spt_fallback_usg', 'spt_monitor', 'spt_values', 'MSreplication_options')
SELECT * FROM master.sys.procedures WHERE name NOT IN ('sp_MSrepl_startup', 'sp_MScleanupmergepublisher')
SELECT * FROM model.sys.tables
SELECT * FROM model.sys.procedures
/*
Alright, we're done with the server level! Let's check databases. We could
right-click on each database and click Properties, but it can be easier to
scan across the results of sys.databases. I look for any variations - are
there some databases that have different settings than others?
*/
SELECT * FROM sys.databases
/*
SQL 2008 & above only - are any databases encrypted? Transparent Data
Encryption is all too transparent. You won't notice that databases are
encrypted if you just glance at SSMS.
If this query returns any results, you need to start asking if the certificate
has been backed up and where the password is. If you don't have both the
cert and the password to unlock it, then the database can't be restored.
*/
SELECT d.name, k.*
FROM sys.dm_database_encryption_keys k
INNER JOIN sys.databases d ON k.database_id = d.database_id
ORDER BY d.name
/*
Are any of the databases using features that are Enterprise Edition only?
If a database is using something like partitioning, compression, or
Transparent Data Encryption, then I won't be able to restore it onto a
Standard Edition server.
*/
EXEC dbo.sp_MSforeachdb 'SELECT ''[?]'' AS DatabaseName, * FROM [?].sys.dm_db_persisted_sku_features'
/*
Data files - where are they? Are any on the C drive? We want to avoid that
because if they grow, they can fill up the OS drive, and that can lead to a
very nasty crash. Let's look at where the databases live. For tips on how to move databases off the C drive:
http://support.microsoft.com/kb/224071
In the results, also check the number of data and log files for all databases.
*/
select db_name(database_id), * from sys.master_files
/*
Check for triggers in any database. I can't change these right away, but I
want to know if they're present, because it'll help me troubleshoot faster.
If I didn't know the database had triggers, I probably wouldn't think to look.
*/
EXEC dbo.sp_MSforeachdb 'SELECT ''[?]'' AS database_name, o.name AS table_name, t.* FROM [?].sys.triggers t INNER JOIN [?].sys.objects o ON t.parent_id = o.object_id'
/*
We've hit the major pain points on reliability and security. Now let's do a
little poking around in performance. Let's query the server's wait stats,
which tell us what things the server has been waiting on since the last
restart. For more about wait stats, check out:
http://sqlserverpedia.com/wiki/Wait_Types
*/
SELECT *, (wait_time_ms - signal_wait_time_ms) AS real_wait_time_ms
FROM sys.dm_os_wait_stats
ORDER BY (wait_time_ms - signal_wait_time_ms) DESC
/*
Do we have any duplicate indexes? This query from Adam Machanic checks for
indexes on exactly the same fields. You have to run it in each database
you want to check, and it's extremely fast.
*/
select
t.name as tableName,
p.*
from sys.tables as t
inner join sys.indexes as i1 on
i1.object_id = t.object_id
cross apply
(
select top 1
*
from sys.indexes as i
where
i.object_id = i1.object_id
and i.index_id > i1.index_id
and i.type_desc <> 'xml'
order by
i.index_id
) as i2
cross apply
(
select
min(a.index_id) as ind1,
min(b.index_id) as ind2
from
(
select ic.*
from sys.index_columns ic
where
ic.object_id = i1.object_id
and ic.index_id = i1.index_id
and ic.is_included_column = 0
) as a
full outer join
(
select *
from sys.index_columns as ic
where
ic.object_id = i2.object_id
and ic.index_id = i2.index_id
and ic.is_included_column = 0
) as b on
a.index_column_id = b.index_column_id
and a.column_id = b.column_id
and a.key_ordinal = b.key_ordinal
having
count(case when a.index_id is null then 1 end) = 0
and count(case when b.index_id is null then 1 end) = 0
and count(a.index_id) = count(b.index_id)
) as p
where
i1.type_desc <> 'xml'
/*
Index fragmentation is the leading cause of DBA heartburn. It's a lot like
file fragmentation, but it happens inside of the database. The below script
shows fragmented objects that might be a concern.
This is an IO-intensive operation, so start by running it in a small database.
To run it for all databases, go to the line with this:
INNER JOIN sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL , NULL, N'Limited') ps
and replace it with:
INNER JOIN sys.dm_db_index_physical_stats (NULL, NULL, NULL , NULL, N'Limited') ps
But be careful - that can run VERY long on large database systems, like hours.
For more about index fragmentation, check out this page with a video:
http://sqlserverpedia.com/wiki/Index_Maintenance
*/
SELECT
db.name AS databaseName
, SCHEMA_NAME(obj.schema_id) AS schemaName
, OBJECT_NAME(ps.OBJECT_ID) AS tableName
, ps.OBJECT_ID AS objectID
, ps.index_id AS indexID
, ps.partition_number AS partitionNumber
, ps.avg_fragmentation_in_percent AS fragmentation
, ps.page_count
FROM sys.databases db
INNER JOIN sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL , NULL, N'Limited') ps
ON db.database_id = ps.database_id
INNER JOIN sys.objects obj ON ps.object_id = obj.object_id
WHERE ps.index_id > 0
AND ps.page_count > 100
AND ps.avg_fragmentation_in_percent > 30
ORDER BY databaseName, schemaName, tableName
OPTION (MaxDop 1);
/*
Finally, let's go back to the users and ask questions:
- Can the business operate if this server is down?
- How many employees have to stop working if this server goes down?
- Who should I call when the server goes down?
- Is this server covered by any security or compliance regulations?
We can use their answers to build a good backup & recovery solution.
Is there anything you think should be added here? You can edit this script!
Go to the script's home page:
http://sqlserverpedia.com/wiki/Audit_a_SQL_Server_Configuration
Log into SQLServerPedia (it's free to create an account). After you've
logged in, you'll see an Edit button at the top of the page. Click that,
and you can edit this script right now to add your own contributions!
*/
Subscribe to:
Post Comments (Atom)
1 comment:
Hi,
The SQL Server Audit feature enables you to audit server-level and database-level groups of events and individual events. SQL Server auditing components are intrinsically audited in a specific audit, and in these cases, audit events occur automatically because the event occurred on the parent object. Thank you...
Post a Comment