The ability to keep track of what’s being executed on a SQL Server instance, or to create a read-only copy of a database at a specific point in time can be very valuable if required by the solution you are developing. But a lesser-known fact about SQL Auditing and Database Snapshots is that not only do they work well on their own, but they also play nicely together.
Let’s say you work for that awesome company AdventureWorks, which embraces open data so much that you can download their corporate database on CodePlex here. That database contains some super-sensitive payroll data, and they’d like to keep track of everyone who has accessed it. To do this, you set up SQL Auditing to capture all SELECT operations on the
HumanResources.EmployeePayHistory table and log them to a file.
-- create a server audit USE [master] GO CREATE SERVER AUDIT [AWAudit] TO FILE ( FILEPATH = N'C:\Program Files\Microsoft SQL Server\MSSQL11.SQL2012\MSSQL\DATA') WITH (QUEUE_DELAY = 1000, ON_FAILURE = CONTINUE); GO -- * in a production system you'd probably want a more stringent ON_FAILURE -- value than "continue", but for demonstration purposes it's fine ALTER SERVER AUDIT [AWAudit] WITH (state = ON); -- create a database audit specification USE [AdventureWorks2012] GO CREATE DATABASE AUDIT SPECIFICATION [EmployeePayAuditSpec] FOR SERVER AUDIT [AWAudit] ADD (SELECT ON OBJECT::[HumanResources].[EmployeePayHistory] BY [public]) GO ALTER DATABASE AUDIT SPECIFICATION [EmployeePayAuditSpec] WITH (STATE = ON);
So now anytime anyone selects from that table, there will be a record of it:
-- select from the table being audited SELECT TOP (100) * FROM HumanResources.EmployeePayHistory; -- query the audit file SELECT DATEADD(hh, DATEDIFF(hh, GETUTCDATE(), CURRENT_TIMESTAMP), event_time) AS EventTime, session_server_principal_name, database_name, statement FROM sys.fn_get_audit_file('C:\Program Files\Microsoft SQL Server\MSSQL11.SQL2012\MSSQL\DATA\*',NULL,NULL);
So far, so good. Now let’s continue with our story and say that management wants to be able to run reports throughout the workday that show the state of the database at 6:00 am. You find the appropriate solution to be a database snapshot, so you setup a job to create a snapshot daily at 6:00 am, and another one to drop it late in the evening. All the reports can then query the snapshot and get the exact state of the database as it was at 6:00 that morning.
-- create snapshot CREATE DATABASE AdventureWorks2012_Snap ON (NAME = AdventureWorks2012_Data, FILENAME = 'C:\Program Files\Microsoft SQL Server\MSSQL11.SQL2012\MSSQL\DATA\AW2012.ss') AS SNAPSHOT OF AdventureWorks2012;
But what about that audit? Since a snapshot contains absolutely everything in the database at the moment it was created, it will have all that sensitive pay history data as well. How can you audit that?
The Free Lunch
The answer is that you don’t have to worry. A snapshot contains everything in the database at the moment it was created, and that doesn’t just apply to data – that goes for settings too, including audits. If someone was to query an audited table in the snapshot, this will be recorded by the audit just like on the base database. You’ll be able to tell the query was run on the snapshot by looking at the database name column:
-- query snapshot USE [AdventureWorks2012_Snap] GO SELECT TOP (100) * FROM HumanResources.EmployeePayHistory; -- query audit file SELECT DATEADD(hh, DATEDIFF(hh, GETUTCDATE(), CURRENT_TIMESTAMP), event_time) AS EventTime, session_server_principal_name, database_name, statement FROM sys.fn_get_audit_file('C:\Program Files\Microsoft SQL Server\MSSQL11.SQL2012\MSSQL\DATA\*',NULL,NULL);
But what if you don’t want the snapshot to be audited?
The Free Lunch is at Grandma’s House
I can only speak from personal experience, but whenever I go visit my grandmother, I go knowing that I will be eating whether I’m hungry or not. She loves cooking and watching her grandchildren eat, and – let’s face it – there’s just no way to say no to grandma.
Similarly, there’s no way to say no to a snapshot. However the database was configured the moment the snapshot was created will be reflected in the snapshot as well. It’s important to remember that database snapshots are read-only, so even if you try to disable the audit on a snapshot, it won’t work:
USE [AdventureWorks2012_Snap] GO ALTER DATABASE AUDIT SPECIFICATION [EmployeePayAuditSpec] WITH (STATE = OFF);
The only way to create a snapshot without the audit in this case would be to stop the audit, create the snapshot, and then restart the audit. This raises other issues, namely that you probably shouldn’t be stopping an audit of sensitive data like that.