Search code examples
sql-serverdatabase-administration

Is there a mode/profile/permission that automatically rollbacks all requests submitted by a user?


At work, we have production databases on which developers have read permission. When developers have to fix something in the database, they must test their scripts in a copy of the production databases and then ask the DBA team to execute it in production.

Sometimes however, the data that must be fixed is not in the test databases. Developers then ask for a new copy of production databases, and this can take a lot of time.

Of course, we could grant them update permission and ask them to use BEGIN TRANSACTION / ROLLBACK, but it is too risky. Nobody wants that, not even the developers.

My question: is it possible to create a profile on SQL Server - or grant special permission - that would allow to execute update and delete commands but would always, no matter what the developer wrote, rollback after a GO or after the last command issued in a session?

This would be really helpful to test scripts before sending them to production.


Solution

  • You could create a sproc and give EXEC access to devs on that sproc only, SOLUTION #1 - SPROCS. This is probably the most elegant solution as you want them to have a simple way to run their query and also want to control their perms on the production environment. Example to execute a command would be: EXEC [dbo].[usp_rollback_query] 'master', 'INSERT INTO table1 SELECT * FROM table2

    SOLUTION #1

    USE [DATABASENAME]
    GO
    
    ALTER PROC dbo.usp_rollback_query
    (
        @db VARCHAR(128),
        @query NVARCHAR(max)
    )
    AS
    BEGIN
        DECLARE @main_query NVARCHAR(max) = 'USE [' + @db + ']
    
        ' + @query;
    
        BEGIN TRAN
            EXEC sp_executesql @main_query;
        ROLLBACK TRAN
    END
    

    If you can afford to have snapshot created and dropped each time, SOLUTION #2 - DB SNAPSHOTS is the best way to go about it. It's super fast, the only two drawbacks are that you need to kick people off the DB before you can restore and it will restore all changes made since the snapshot was created.

    SOLUTION #2

    -- CREATE SNAPSHOT
    CREATE DATABASE [DATABASENAME_SS1]
    ON
        (
            NAME = DATABASENAME,
            FILENAME = 'your\path\DATABASENAME_SS1.ss'
        ) AS SNAPSHOT OF [DATABASENAME];
    GO
    
    -- let devs run whatever they want
    
    -- CLOSE CONNECTIONS
    USE [master];
    GO
    ALTER DATABASE [DATABASENAME]
    SET SINGLE_USER
    WITH ROLLBACK IMMEDIATE;
    GO
    
    -- RETORE DB
    RESTORE DATABASE [DATABASENAME]
    FROM DATABASE_SNAPSHOT = 'DATABASENAME_SS1';
    GO
    
    -- CLEANUP SNAPSHOT COPY
    DROP DATABASE [DATABASENAME_SS1];
    

    I don't think ROLLBACK on each query is a good idea or a good design but if you have to go that route, you would need to use triggers. The limitation with triggers is that a DATABASE or SERVER level trigger can only be for DDL and not DML. Creating triggers on each TABLE object that you think is being altered is doable, however, the drawback here is that you need to know which tables are being modified and even then it's quite messy. Regardless please look at SOLUTION #3 - TABLE TRIGGERS below. To make this better you could create a role and check if the user is part of that role, then rollback.

    SOLUTION #3

    USE DATABASENAME
    GO
    
    ALTER TRIGGER dbo.tr_rollback_devs
       ON  dbo.table_name
       AFTER INSERT, DELETE, UPDATE
    AS 
    BEGIN
        SET NOCOUNT ON;
    
        IF SYSTEM_USER IN ('dev1', 'dev2')
            ROLLBACK
    END
    GO