© Elizabeth Noble 2020
E. NoblePro T-SQL 2019https://doi.org/10.1007/978-1-4842-5590-2_14

14. Logging

Elizabeth Noble1 
(1)
Roswell, GA, USA
 

During your time working with SQL Server, you will get a wide variety of requests from your company. Some of these requests may involve changing business logic or adding new functionality. There are other times that your company may want to track what happened in the past. There are a variety of third-party tools that can help track performance and other functionality related to database maintenance. However, your business may be interested in tracking data changes or understanding where something went wrong.

This type of logging related to data modifications or error handling can be implemented using T-SQL. When logging these types of changes, you have several options available to you. These options can include minimally logging activity all the way to logging the most granular of activities. You will also want to consider how this information will be accessed and used going forward. This will help you log information in a way that can be beneficial to your organization in the future. This will also prevent you from logging data that ends up never being used.

Data Modification

Choosing a method to track data modifications requires understanding what types of information your organization needs to track. You may want to only log information when something has changed. Depending on your business case, you may need to know not only when something changed but also what changed. There are also a couple options on how you can track these changes. You have the option to either use SQL Server to track these changes for you or you can create database objects that will log this information for you.

One of the things to consider when implementing any sort of logging is the performance overhead. This can be even more critical when tracking data modifications. You will want to make sure that you choose a logging method that allows you to both track the necessary changes and have the expected performance impact. As you may expect, the more detailed logging you implement, the greater the performance overhead.

Out of all the options discussed in this chapter, Change Tracking has the least amount of overhead. If you would like to record more detailed information and you are willing to increase resource utilization, then you may want to consider Change Data Capture. Another possible implementation of tracking change involves using database triggers. While the use of database triggers allows you to more finely tune what is logged and how it is logged, it can come at the cost of even higher resource utilization.

An intermediate option may be using SQL Server Audit depending on the type of data you want to log. SQL Server Audit can be used to track server or database-level activity. In the context of logging changes related to applications, you would want to focus on database audits. The database audit actions within SQL Server Audit can track changes related to all types of data activity including accessing or modifying data. SQL Server uses extended events to monitor the audit activity. The data that can be retrieved includes the time the action happened, information about the user that triggered the audit, and the object affected. It may also be possible to record the statement issued when the audit action took place.

Another minimal logging activity available with regard to data modifications involves logging the most recent time a record was changed and incrementing the number of changes that have occurred for that data record. This type of tracking is handled by Change Tracking within SQL Server. To use Change Tracking, you will first need to enable Change Tracking on the database. Throughout this book, we have been using the Menu database. I will need to run the T-SQL code shown in Listing 14-1 to enable Change Tracking on the Menu database.
ALTER DATABASE Menu
SET CHANGE_TRACKING = ON
(CHANGE_RETENTION = 2 DAYS, AUTO_CLEANUP = ON);
Listing 14-1

Enable Change Tracking on Menu Database

To enable Change Tracking on the Menu database, you will need to specify the database name and indicate that you want to turn Change Tracking on. The last line in the preceding T-SQL code is optional. These values indicate how long you would like to retain changes and if the retention history should be cleaned up automatically.

Once you have enabled Change Tracking in the Menu data, you are able to configure tables to use Change Tracking. The table where you will implement Change Tracking must have a primary key. If this table does not have a primary key, you will need to add one before you can enable Change Tracking on this table. In our case, I will be enabling Change Tracking on the dbo.Recipe table. Listing 14-2 shows the database code needed to enable Change Tracking on the dbo.Recipe table.
ALTER TABLE dbo.Recipe
ENABLE CHANGE_TRACKING
WITH (TRACK_COLUMNS_UPDATED = ON);
Listing 14-2

Enable Change Tracking on dbo.Recipe Table

Change Tracking is now enabled on the dbo.Recipe table. Change Tracking can either record that an entire data row has changed or specify that specific columns have changed. When you track column changes, SQL Server will record that a change happened and the specific column or columns that have changed. If you do not specify a value for TRACK_COLUMNS_UPDATED, SQL Server will use the default value of off.

After setting up Change Tracking, you may want to see what kind of information is available or has changed. While the changes in the table dbo.Recipe are tracked by SQL Server, this information is not available from a table that you can see in Object Explorer. Instead, you will need to access the CHANGETABLE associated with the table dbo.Recipe. Once Change Tracking has been enabled, records are initialized and can be found in the CHANGETABLE. The query in Listing 14-3 shows how you would see these initialized records.
SELECT rec.RecipeID,
      rec.RecipeName,
      rec.RecipeDescription,
      rec.IsActive,
      chng.RecipeID,
      chng.SYS_CHANGE_VERSION,
      chng.SYS_CHANGE_CONTEXT
FROM dbo.Recipe AS rec
      CROSS APPLY CHANGETABLE
      (VERSION Recipe, (RecipeID), (rec.RecipeID)) AS chng;
Listing 14-3

Query Initialized Records

This query will return information from the dbo.Recipe table and information from the CHANGETABLE. Table 14-1 shows an example of the initialized records.
Table 14-1

Initialized Records

RecipeID

SYS_CHANGE_VERSION

SYS_CHANGE_CONTEXT

1

NULL

NULL

2

NULL

NULL

3

NULL

NULL

4

NULL

NULL

The records in Table 14-1 are from the CHANGETABLE. There is a column for the RecipeID, the primary key on the table, and columns to track information about the changes. Since none of the data records have been modified, the values are all NULL.

I can modify the records in the table dbo.Recipe. In Listing 14-4, I have updated the DateModified.
UPDATE dbo.Recipe
SET DateModified = GETDATE()
WHERE RecipeID = 3;
Listing 14-4

Update DateModified

Now that a record has been modified, I can get a better idea of how information is being stored when it comes to Change Tracking. Listing 14-5 shows the query needed to find information about changes to dbo.Recipe that occurred since the last automatic cleanup.
SELECT RecipeID,
      SYS_CHANGE_OPERATION AS ChangeOperation,
      CHANGE_TRACKING_IS_COLUMN_IN_MASK
      (COLUMNPROPERTY
            (OBJECT_ID('Recipe'), 'RecipeName', 'ColumnId'),
      SYS_CHANGE_COLUMNS) AS NameChange,
      CHANGE_TRACKING_IS_COLUMN_IN_MASK
      (COLUMNPROPERTY
            (OBJECT_ID('Recipe'), 'RecipeDescription', 'ColumnId'),
      SYS_CHANGE_COLUMNS) AS DescChange,
      CHANGE_TRACKING_IS_COLUMN_IN_MASK
      (COLUMNPROPERTY
            (OBJECT_ID('Recipe'), 'IsActive', 'ColumnId'),
      SYS_CHANGE_COLUMNS) AS IsActiveChange,
      CHANGE_TRACKING_IS_COLUMN_IN_MASK
      (COLUMNPROPERTY
            (OBJECT_ID('Recipe'), 'DateCreated', 'ColumnId'),
      SYS_CHANGE_COLUMNS) AS CreateChange,
      CHANGE_TRACKING_IS_COLUMN_IN_MASK
      (COLUMNPROPERTY
            (OBJECT_ID('Recipe'), 'DateModified', 'ColumnId'),
      SYS_CHANGE_COLUMNS) AS ModifiedChange,
      SYS_CHANGE_CONTEXT
FROM CHANGETABLE
(CHANGES dbo.Recipe,0) as ChngTbl
ORDER BY SYS_CHANGE_VERSION
Listing 14-5

Find Changed Records

This version information is needed to find the state of the changes on this table. The query in Listing 14-5 shows you one way that you can use to access this information. Table 14-2 shows the results for the query in Listing 14-5.
Table 14-2

Change Tracking Result Set

RecipeID

Change Operation

Name Changed

Desc Changed

IsActive Changed

Created Changed

Modified Changed

3

U

False

False

False

False

True

These results are recorded from the update issued in Listing 14-4.

The RecipeID affected is listed in the first column of Table 14-2. This matches the same RecipeID updated in Listing 14-4. The ChangeOperation is listed as a U for update. This also matches the DML action in Listing 14-4. The final five columns in Table 14-2 were populated using specific functions to unmask the columns indicated in the Change Tracking tables. The function involved returns a true value if the column was modified and a false value if the data was not modified. In the case of Listing 14-4, the only column updated was the DateModified. This matches the result in Table 14-2. The columns NameChange, DescChanged, IsActiveChanged, and CreatedChanged are all false. These values were not changed. However, the column ModifiedChanged is true. This indicates that the column referenced, DateModified, was changed.

There are some potential issues regarding consistency when using Change Tracking. Storing the data does not affect the consistency of the data. The consistency of the data is affected as part of the data retrieval. You can do everything possible to make sure you are looking at the most recent data in the Change Tracking table. This includes checking for the last synchronized version in Change Tracking and confirming that this version is still available. However, if the version in Change Tracking is older than the retention period, this data may be cleaned up before the data can be retrieved. With Change Tracking, you may also have occasions where a data modification occurs after the last synchronization. This can cause additional versions or modified records to be returned. Any of these scenarios can affect the consistency of the data returned with Change Tracking. The best practice to minimize issues with consistency is to use the snapshot isolation level.

If you need to know more than the row or column that was changed and the number of changes that occurred for that record since implementing Change Tracking, you may want to consider Change Data Capture. Like Change Tracking, there are benefits and drawbacks of using Change Data Capture. The single largest benefit is that Change Data Capture will capture the details related to the data record that was changed.

For inserted data, you will be able to see exactly what was added to the table. Similarly, you will be able to see all the columns for a data record that was deleted. When updating data, you will be able to access both the data before and after the update. A downside is that each change to the table requires that at least one record is written to a tracking table. Another downside is that two SQL Server Agent jobs are created for each table that is tracked. As you increase the number of tables under Change Data Capture, the amount of resources used will increase as well. This can cause the performance hit associated with Change Data Capture to be severe enough to prevent you from implementing Change Data Capture.

If you decide to move forward with Change Data Capture, you can update a table in a database to use Change Data Capture. Before doing this, you will need to allow Change Data Capture to occur on your database. You can enable Change Data Capture per database by running the T-SQL code in Listing 14-6.
USE Menu;
GO
EXECUTE sys.sp_cdc_enable_db;
GO
Listing 14-6

Enable Database Tracking on Menu Database

Now that you have enabled Change Data Capture, you will want to choose a table where you are only concerned with knowing when a record has changed. I will implement Change Data Capture on the table dbo.Recipe. Before enabling Change Data Capture on this table, I will need to make sure that SQL Server Agent is running on this instance. In Listing 14-7, you can see the database code needed to implement Change Data Capture on this table.
USE Menu;
GO
EXECUTE sys.sp_cdc_enable_table
      @source_schema = 'dbo',
      @source_name = 'Recipe',
      @role_name = NULL;
GO
Listing 14-7

Enable Database Tracking on dbo.Recipe Table

At a minimum you will need to specify the table schema, table name, and the database roles that can access this data. If you specify NULL for the role name, the information recorded by Change Data Capture will be accessible to everyone. Once this table has been enabled for Change Data Capture, I will be able to see the new jobs created in SQL Server Agent. You will receive a message once the T-SQL code in Listing 14-7 completes. You can see an example of this message in Figure 14-1.
../images/480547_1_En_14_Chapter/480547_1_En_14_Fig1_HTML.jpg
Figure 14-1

Message Showing SQL Server Agent Jobs Created

The process of enabling Change Data Capture also creates several system tables in the cdc schema. You can see these tables in Figure 14-2, the system tables that were created to manage Change Data Capture.
../images/480547_1_En_14_Chapter/480547_1_En_14_Fig2_HTML.jpg
Figure 14-2

System Tables Created for Change Data Capture

The table that we will use to track changes on dbo.Recipe is the cdc.dbo_Recipe_CT table.

If you implement Change Data Capture on an empty table, you will be able to track when your data records have changed but you will also be able to track the number of changes that happen since the table was created. In Listing 14-8, I have written a script to insert records into dbo.Recipe.
INSERT INTO dbo.Recipe
(
      RecipeName,
      RecipeDescription,
      ServingQuantity,
      MealTypeID,
      PreparationTypeID,
      IsActive,
      DateCreated,
      DateModified
)
VALUES
(
      'Lee"s Hamburgers',
      'The best hamburgers',
      4,
      2,
      1,
      1,
      GETDATE(),
      GETDATE()
);
Listing 14-8

Insert Records into dbo.Recipe

This can work somewhat differently when adding Change Data Capture to an existing table. If you add Change Data Capture to an existing table, you will still be able to track when changes have happened to your data records, but you will only be able to see the number of data modifications that have happened since Change Data Capture was implemented.

Previously in Listing 14-8, I enabled Change Data Capture on the dbo.Recipe table. If I assume that the table was empty and I insert some records into the table, I will be able to determine when these records were added. Executing the query in Listing 14-9 will show you the records in the Change Data Capture table.
SELECT __$start_lsn,
      __$end_lSN,
      __$seqval,
      __$operation,
      __$update_mask,
      RecipeID,
      RecipeName,
      RecipeDescription,
      ServingQuantity,
      MealTypeID,
      PreparationTypeID,
      IsActive,
      DateCreated,
      DateModified,
      __$command_id
FROM cdc.dbo_Recipe_CT
Listing 14-9

Query the Change Data Capture Table for dbo.Recipe

You can see a subset of the information that has been tracked about the data records in Table 14-3.
Table 14-3

Change Tracking Output for dbo.Recipe

__$operation

RecipeID

RecipeName

DateCreated

__$command_id

2

1

Spaghetti

05/21/2019

1

It may not look like there is much information being recorded, but the information saved in this table can be quite helpful. The column __$operation indicates the action that occurred on this table. The __$operation will tell you which type of activity was performed. If the record was inserted, you will have one entry in cdc.dbo_Recipe_CT with the values that were inserted. If the record was deleted, you will have one entry in cdc.dbo_Recipe_CT with the values that were deleted. If the record was updated, you will have two entries in cdc.dbo_Recipe_CT table. The first record will have the values of the record before the update. The second record will have the values of the record after the update.

If the table had already existed and I had enabled Change Data Capture, you will not see those data records until they had been updated. Once these records are modified, they will show up in the cdc.dbo_Recipe_CT table. While Change Data Capture can be easy for setup and implementation, you should use caution when adding tables to Change Data Capture. Each table added to Change Data Capture causes two SQL Server Agent jobs to be created. There is also a significant amount of logging that occurs as a result of tracking changes on these tables. Both can cause SQL Server to use additional resources.

You can use Change Tracking, Change Data Capture, or database triggers to track data modifications. All these options have their own strengths and limitations. When choosing the right option for your organization, you will want to consider what type of data you need to record and the performance overhead you are willing to incur tracking these changes. If you need better performance, you will generally have to choose less functionality. The more information you would like to collect or the more you would like to customize logging data modifications will come at a cost of increased hardware utilization and potential performance overhead.

Error Handling

In addition to logging data modifications, you may also find that you would benefit from logging certain types of errors that occur within the database. While there are SQL Server specific errors that can occur, that is not the focus of this section. There are also errors that occur as a result of the application interacting with the database. Many of these errors can be logged outside of SQL Server as part of your application development. However, you may find that some issues need to be accessible from within SQL Server.

When considering error handling, you will not only want to consider how that information is logged but also how the application handles errors. One of the more common options from within SQL Server is to use a TRY... CATCH block. This code wraps the ability to try the T-SQL code and perform a specific action if that attempt failed. If the code succeeds, then the T-SQL code will execute as expected. Listing 14-10 shows a TRY… CATCH block.
CREATE PROCEDURE dbo.RecipeInsert
      @RecipeName VARCHAR(25),
      @RecipeDescription VARCHAR(50),
      @ServingQuantity TINYINT,
      @MealTypeID TINYINT,
      @PreparationTypeID TINYINT,
      @IsActive BIT,
      @DateCreated DATETIME2(7),
      @DateModified DATETIME2(7)
AS
BEGIN TRY
      BEGIN TRANSACTION
            INSERT INTO dbo.Recipe
            (
                  RecipeName,
                  RecipeDescription,
                  ServingQuantity,
                  MealTypeID,
                  PreparationTypeID,
                  IsActive,
                  DateCreated,
                  DateModified
            )
            VALUES
            (
                  @RecipeName,
                  @RecipeDescription,
                  @ServingQuantity,
                  @MealTypeID,
                  @PreparationTypeID,
                  @IsActive,
                  @DateCreated,
                  @DateModified
            )
      COMMIT TRANSACTION
END TRY
BEGIN CATCH
      ROLLBACK TRANSACTION
END CATCH
Listing 14-10

Try… Catch Block to Insert Recipe

This block of code will insert a record if there are no errors. However, if an error is encountered, then the transaction will be rolled back. There are some ways to integrate this with your application code so that the user is aware there was an error with the transaction. The goal of using this method is to prevent the application from crashing or prevent the end user from expecting the transaction to save correctly. If the user is aware the action has failed, they have the option to correct the issue and retry the action.

You may find that your application is able to write or update data in the database without any issues. However, you may have a process that sends data from one system to another. There may be infrastructure issues or inconsistencies with data types that can cause failures when sending data from one database object to another. These are also a type of error that you will want to determine how it should be handled. You will still want a method to handle these failures, but you may also need more instantaneous reporting that these records failed to be sent or received. Business today requires a constant state of uptime and successful interactions. You can control your effectiveness in responding to these issues by how you record failures when trying to process data.

Database tables that store information that need to be moved through a system often store a status type column in the data table. Whatever process updates, the status of the record may either have issues updating the record status or update the record status to a failed state. When there is not a large amount of data in the table, finding these failed records can be easy. This would usually require a search of records where the status has not changed in a specified period or where the records are in a failed or error status.

Depending on how we use the information about recipes, we may want to log when each recipe is prepared. We could have an application that would allow us to indicate when a recipe was started and completed. This information could be recorded with a recipe status. I would need to create a table to indicate the recipe history status. An example of the information can be seen in Table 14-4.
Table 14-4

Data Within the dbo.RecipeHistoryStatus Table

RecipeHistory

StatusID

RecipeHistory

StatusName

IsActive

DateCreated

DateModified

1

Started

True

05/21/2019

05/21/2019

2

Completed

True

05/21/2019

05/21/2019

3

Cancelled

True

05/21/2019

05/21/2019

4

Error

True

05/21/2019

05/21/2019

This table includes the status available when recording occurrences where someone has started preparing a recipe. To log each instance of a recipe being prepared, I would need to create a table to store information about when each recipe is prepared. There are several ways this could be recorded. For the sake of this chapter, I will create a single record each time a recipe is started. Once the recipe is started, the recipe can end up in a completed, cancelled, or error status. The query for storing the recipe history can be seen in Listing 14-11.
CREATE TABLE dbo.RecipeHistory
(
      RecipeHistoryID         INT         NOT NULL,
      RecipeID                SMALLINT    NOT NULL,
      RecipeHistoryStatusID   TINYINT     NOT NULL,
      DateCreated             DATETIME    NOT NULL,
      DateModified            DATETIME    ,
      CONSTRAINT pk_RecipeHistory_RecipeHistoryID
            PRIMARY KEY CLUSTERED (RecipeHistoryID),
      CONSTRAINT fk_RecipeHistory_RecipeID
            FOREIGN KEY (RecipeID)
            REFERENCES dbo.Recipe(RecipeID),
      CONSTRAINT fk_RecipeHistory_RecipeHistoryStatusID
            FOREIGN KEY (RecipeHistoryStatusID)
            REFERENCES
            dbo.RecipeHistoryStatus(RecipeHistoryStatusID)
);
Listing 14-11

Create dbo.RecipeHistory Table

This table can store each unique time a recipe is started. An example of the data stored in this table can be found in Table 14-5.
Table 14-5

Data Within the dbo.RecipeHistory Table

RecipeHistoryID

RecipeID

RecipeHistory

StatusID

DateCreated

DateModified

1

1

2

05/17/2019

05/17/2019

2

1

3

05/18/2019

05/18/2019

3

3

4

05/20/2019

05/20/2019

4

4

1

05/21/2019

05/21/2019

The records in Table 14-5 indicate recipes that have been started and their various statuses. The first record is for RecipeID of 1 with a status of completed. The second record is for RecipeID of 1 with a status of cancelled. The third record is for RecipeID of 3 with a status of error. The last record displayed is for RecipeID of 4 with a status of started.

When there is an error for any of the records, those records will have a RecipeHistoryStatusID of 4 in the dbo.RecipeHistory table. Initially the amount of data in this table will not be very large, and it will be easy to find the recent error records within this table. However, over time this table will grow to a considerable size. This can cause SQL Server to search through many records in order to find any recent error records. If we plan for the table dbo.RecipeHistory to grow to a size where it will be difficult to search, we may implement error logging differently. There may be other scenarios where you would want to keep all records that have errored in an error status, but you would also want to know what to be able to resolve any issues with recently errored records.

In either of these scenarios, it may be beneficial to create a table specifically for logging recent error records. Before creating this table, you may also want to consider how this table must be managed over time. Unlike the dbo.RecipeHistory table, you would want to make sure that this new table did not get too large. You would also want to only keep recent error records in this table. Keeping a small number of records in the table allows the table to be easily searched. The goal of this new table will only be to alert users of any recent error records. Considering the purpose of this table, you will also want to design a process to purge data from this table regularly.

If you choose to create an additional logging table for error records, you may end up creating a table like the one in Listing 14-12.
CREATE TABLE dbo.RecipeHistoryLog
(
      RecipeHistoryLogID      INT         NOT NULL,
      RecipeHistoryID         INT         NOT NULL,
      DateCreated             DATETIME    NOT NULL,
      DateModified            DATETIME    ,
      CONSTRAINT pk_RecipeHistoryLog_RecipeHistoryLogID
            PRIMARY KEY CLUSTERED (RecipeHistoryLogID),
      CONSTRAINT fk_RecipeHistoryLog_RecipeHistoryID
            FOREIGN KEY (RecipeHistoryID)
            REFERENCES dbo.RecipeHistory(RecipeHistoryID)
);
Listing 14-12

Create dbo.RecipeHistoryLog Table

Any record that errors on the dbo.RecipeHistory table can have a corresponding record entered to the table in Listing 14-12. In Table 14-5, there was an error record for RecipeHistoryID 2. If we had created the table in Listing 14-12 before that error record was created, we might see an entry like the one in Table 14-6.
Table 14-6

Data Within the dbo.RecipeHistoryLog Table

RecipeHistoryLogID

RecipeHistoryID

DateCreated

DateModified

1

2

05/18/2019

05/18/2019

Once we have a record in the dbo.RecipeHistoryLog table, we can generate an alert based on the existence of a record in this table. One option may be to have a stored procedure that is executed every 15 minutes. The purpose of this stored procedure may be to generate an email if there are any records found in the dbo.RecipeHistoryLog table. If you choose this method to create your alerts, you will want to also make certain that you remove data from this table regularly. In this case, you would also need a stored procedure to regularly remove data from this table.

Regardless of which method you choose to implement logging errors between your applications and SQL Server, you should make sure that these errors are tracked somewhere that can be accessible to multiple parties. One of the more difficult issues to troubleshoot is when there is no logging available. The goal of logging errors related to SQL Server is to allow individuals within your organization to quickly find where issues are occurring so that they can resolve them efficiently. You may choose to implement most of your error handling from the application. However, it may be possible to generate automated reports from within SQL Server for errors that need to be corrected promptly.

As part of your application development, you will want to consider what type of logging is necessary for your organization. In industries where there is the possibility for greater theft, it may be more important to track when a data modification occurs and who modified the data. Depending on the information needed will determine what type of logging you implement for data modifications. You will also want to consider how to manage errors associated with the database. These are not errors that are specific to SQL Server but are errors that occur as a result of the T-SQL code that has been written for use by your applications. After determining how to manage logging for your application, you may want to consider how to design your T-SQL code to be reusable.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.167.41