Archive

Archive for March, 2009

Exception (Mis)Handling

March 24th, 2009

Exception handling was originally created to try to give developers a way to separate out error handling so that it wouldn’t clutter up and distract from the core functionality trying to be accomplished. When done well, exception handling can provide a clean way to instrument and separate truly exceptional conditions from the core flow of your methods as well as a way to prevent nasty crashes and untraceable bugs. When done badly, exception handling can be misused for decision making, masking errors and bugs, and distracting developers from doing real work.

Go read these when you have time, and you have time if you are reading this – admit it.
Chapter 19 of CLR via C# and Guidelines: Exception Handling.

There is not a lot that can be said that isn’t said here about .NET exception handling that isn’t in the resources above, but I’ve picked up on a common anti-pattern that stems from a misunderstanding of what exception handling is. I call it the “safety-net catch”, and it goes a little something like this:

public bool Import()
{
    bool isSuccess = false;

    try
    {
        if (SomethingIsWrong())
            throw new ApplicationException("Start panic sequence now.");

        bSuccess = DoSomething(WithThis, AndThis);

        foreach (string databaseThing in theDatabase)
        {
            if (bSuccess) bSuccess = DoSomethingUseful(databaseThing);
        }
    }
    catch (Exception ex)
    {
        PublishException(ex);
        isSuccess = false;
    }
    return isSuccess;
}

In this example, DoSomethingUseful, DoSomething, and most likely PublishException all have this same pattern of a ‘catch all’ at the end. We aren’t getting much benefit from this style of exception handling as it merely serves to make sure that this method always returns bSuccess so that execution should continue with any failure.

I think it would be easier on everyone if this code was changed to simply not handle exceptions that it can’t actually handle. The top level threads of your application should have a “catch all” that publishes the exception, and then displays a nice message for the user in the case of a web application. So if you want to fail, just let it fail if you can’t recover from it. The contract of the method Import above doesn’t say that it doesn’t throw exceptions – if it can’t do its job it should throw or allow an exception to bubble up.

There are cases where you don’t want the code to break out in the case of any exception, but these are rare. In the case of row level handling where we are parsing a file and you don’t want row to ruin the whole file you can simply move exception handling down to that import piece. In this case non- exception based mechanisms can be used or you can throw a custom exception type or an exception with well understood semantics like InvalidOperationException or ArgumentException.

Please do:

  • Throw an exception in a method if you can’t do the method’s job. If you don’t know what the method should do because it does 23 things, 18 of which can happen if it isn’t given a valid value for AccountNumber, refactor until you have 23 methods, one of which throws InvalidArgumentException when it gets an invalid AccountNumber.
  • Only do a ‘catch’ if you are going to handle it (maybe the syntax should say handle). Just publishing or setting a variable doesn’t count in the pattern above.
  • If you for some reason catch and rethrow (like if you want to add to the exception object), do a throw and not a throw origEx to keep the call stack.
  • Remember that your most common ‘exceptional condition’ might be a database timeout or other database exception (System.Data.SqlClient.SqlException) and is not an ApplicationException but most likely can’t be recovered from anyway.

Please don’t do this:

try
{
    bc.Save(o);
    DB.UpdateProcessFlag(conn, Util.GetInt32(dr, "Key"), 1);
}
catch
{
    //Error on save, so mark this one as errored.
    DB.UpdateProcessFlag(conn, Util.GetInt32(dr, "Key"), -1);
}

There are many reasons that this .Save() could fail, and a SqlException or (ObjectReferenceException on dr) are going to be badly mishandled here. If you find yourself feeling like you need to use the try/catch mechanism for this sort of stuff perhaps take a step back and think about how to handle it in terms of design.

And please don’t do this unless you like hearing the sound of me dying inside. (Use the TryParse pattern instead)

try { o.Something = -aNumba; }
catch { o.Something = 0;  }
try { amt = Util.GetDecimal(dr, "FieldName"); }
catch { amt = 0; }

if (amt == 0) return;

.net, antipattern, c#

Triggers Part 3: FAQ and FOP continued

March 12th, 2009

How many triggers should you have per table?

Ideally zero. If you have any then there should be one. There is no guarantee on the ordering of trigger firings, they normally fire based on their age – newly-added triggers fire last. So if you have two triggers that both run on update, you could get into a recursion situation.
If you do have more than one, you have to set the trigger order via a call to sp_settriggerorder to avoid this recursion, or rewrite the trigger.


-- Test trigger recursion

if object_id('TestTriggerRecursion', N'U') > 0
    drop table TestTriggerRecursion

if object_id('tr_TestTriggerRecursionUpdatedDate', N'TR') > 0
    drop trigger dbo.tr_TestTriggerRecursionUpdatedDate

if object_id('tr_TestTriggerRecursionUpdatedBy', N'TR') > 0
    drop trigger dbo.tr_TestTriggerRecursionUpdatedBy
go

create table TestTriggerRecursion
(
    KeyID int primary key clustered
    , Payload varchar(max) NOT NULL
    , LastUpdatedDate datetime NULL
    , LastUpdatedBy sysname NULL
)
go

create trigger dbo.tr_TestTriggerRecursionUpdatedDate on dbo.TestTriggerRecursion
for insert, update
as

print 'tr_TestTriggerRecursionUpdatedDate ran'
update t set LastUpdatedDate = getdate()
from INSERTED i
join TestTriggerRecursion t on t.KeyID = i.KeyID

go

create trigger dbo.tr_TestTriggerRecursionMaintainAuditField on dbo.TestTriggerRecursion
for insert, update
as

print 'tr_TestTriggerRecursionUpdatedBy ran'
update t set LastUpdatedBy = SUSER_NAME()
from INSERTED i
join TestTriggerRecursion t on t.KeyID = i.KeyID

go

-- delete from TestTriggerRecursion
insert into TestTriggerRecursion values (1, 'test1', null, null)
insert into TestTriggerRecursion values (2, 'test2', null, null)
insert into TestTriggerRecursion values (3, 'test3', null, null)

select * from TestTriggerRecursion

-- fails with
/*

(0 row(s) affected)
Msg 217, Level 16, State 1, Procedure tr_TestTriggerRecursionUpdatedBy, Line 6
Maximum stored procedure, function, trigger, or view nesting level exceeded (limit 32).

*/

-- exec sp_settriggerorder @triggername = 'tr_TestTriggerRecursionUpdatedBy', @order='First'

How can I get a list of triggers and which tables they apply to?

select so.name as TableName
, st.name as TriggerName
from sys.triggers st
join sys.objects so on so.object_id = st.parent_id

What order do constraints and triggers run?

Constraints, then triggers. In addition it should be noted that the DML operation and the trigger code have separate execution plans, poor code that causes recompiles inside a trigger does not cause outside stored procedures to change. With statement-level compilation/caching in 2005 this is even more true. In addition it should be noted that constraint code is independent of DML operations as well.

Can you say “only fire this trigger if THIS column is updated”?

IF UPDATE(Field1) OR UPDATE(Field2)
BEGIN

END

Do triggers try to run ON UPDATE even if now rows have been affected?

Yes, which is why you should always bail if no rows are affected like so:

IF @@ROWCOUNT = 0
RETURN

As you see below, your entire trigger will fire even on a failed update.


-- Test trigger update behavior with zero rows
if object_id('TestTriggerUpdateBehavior', N'U') > 0
    drop table TestTriggerUpdateBehavior

if object_id('tr_TestTriggerUpdateBehaviorUpdatedDate', N'TR') > 0
    drop trigger dbo.tr_TestTriggerUpdateBehaviorUpdatedDate

create table TestTriggerUpdateBehavior
(
    KeyID int primary key clustered
    , Payload varchar(max) NOT NULL
    , LastUpdatedDate datetime NULL
    , LastUpdatedBy sysname NULL
)
go

create trigger dbo.tr_TestTriggerUpdateBehaviorUpdatedDate on dbo.TestTriggerUpdateBehavior
for update
as

print 'tr_TestTriggerUpdateBehaviorUpdatedDate ran'
update t set LastUpdatedDate = getdate()
from INSERTED i
join TestTriggerUpdateBehavior t on t.KeyID = i.KeyID

go

insert into TestTriggerUpdateBehavior values (1, 'test1', null, null)

update TestTriggerUpdateBehavior set Payload = 'testImpossibleUpdate'
where 1 = 2

select * from TestTriggerUpdateBehavior

So, when should you use triggers?

You should use triggers:

  • When you have a clear understanding of how they work
  • You have no other option
  • You have performance tested your code thoroughly
  • You have informed your storage folks, DBA folks, and your mother
  • You have prayed about it

In all honesty I’ve only seen a few clean uses for triggers:

  1. “audit trigger”: Audit mechanism for straight up insert/delete/update calls. You have tableA, and you want to log all changes to auditTableA - a “copy trigger” does this quite well.
  2. “refactor trigger”: A temporary bridge between two phases of a database refactor project. You are migrating data from schema A to schema B, but that last pesty bit of code hasn’t been changed. This release changed 80% of the code and put in a trigger to maintain the data or log to new tables for later testing the remaining 20%. The discipline required to push through the 20% and remove the trigger is rare, so this is sometimes dangerous.
  3. “trap trigger”: In a crisis, log where updates are coming from to a specific table. Remove trigger quickly thereafter.
  4. “evil trigger”: A trigger created for evil.

Where can I find more information about triggers?

Books online: Triggers.

sqlserver

Triggers Part 2: Facts and Frequently-Occuring-Problems (FOP)

March 12th, 2009

When are triggers fired, and how many times?

For your standard missionary position trigger, they are fired once per batch. So if you have an update statement that affects 57 rows, then the DML AFTER trigger fires *once* but the deleted and inserted magic tables have 57 rows in them. One common anti-pattern is not handling multiple values in the inserted/deleted tables.  Code to prove the firing behavior of triggers:

-- Test trigger firing cardinality
if object_id('TestTriggerFiring', N'U') > 0
    drop table TestTriggerFiring

if object_id('TestTriggerAudit', N'U') > 0
    drop table TestTriggerAudit

if object_id('tr_TestTriggerFiring', N'TR') > 0
    drop trigger dbo.tr_TestTriggerFiring

create table TestTriggerFiring
(
    KeyID int primary key clustered
    , Payload varchar(max) NOT NULL
    , LastUpdatedDate datetime NULL
    , LastUpdatedBy sysname NULL
)

create table TestTriggerAudit
(
    TriggerName sysname
    , DateFired datetime NOT NULL
    , ActionType varchar(10) NOT NULL
    , KeyID int NOT NULL
    , Stamp uniqueidentifier
)

go

create trigger dbo.tr_TestTriggerFiring on dbo.TestTriggerFiring
for insert, update
as

declare @MyGuid uniqueidentifier
set @MyGuid = newid()

insert into TestTriggerAudit (TriggerName, DateFired, ActionType, KeyID, Stamp)
select 'tr_TestTriggerFiring', getdate(), 'inserted', i.KeyID, @MyGuid
from INSERTED i

go

insert into TestTriggerFiring
select top 3 object_id, name, null, null
from sys.objects

go

insert into TestTriggerFiring values (-1, 'test4', null, null)
waitfor delay '00:00:001'
go
insert into TestTriggerFiring values (-2, 'test5', null, null)
waitfor delay '00:00:001'
go
insert into TestTriggerFiring values (-3, 'test6', null, null)
waitfor delay '00:00:001'
go

select * from TestTriggerAudit

What are the inserted/deleted magic tables? How do they work?

The inserted and deleted tables hold the date being changed. Depending on what you are doing they hold different data:

Insert
Inserted – holds the new data
Deleted – is empty

Update
Inserted – holds new data
Deleted – holds old data

Delete
Deleted – data being deleted
Inserted – is empty

The inserted and deleted tables are not indexed, so take care in querying them in the wrong way.

Given the logic above, if possible it is normally cleaner to not combine an insert/update trigger. If you have to, the below if my template for doing so:

IF OBJECT_ID('tr_Example_Update', 'TR') > 0
    DROP TRIGGER dbo.tr_Example_Update
GO

CREATE TRIGGER dbo.tr_Example_Update ON dbo.Test
FOR UPDATE, INSERT
AS 

IF @@ROWCOUNT = 0
RETURN

SET NOCOUNT ON
IF UPDATE(Field1) OR UPDATE(Field2)
BEGIN
    -- Update
    IF (SELECT COUNT(1) FROM DELETED) > 0
    BEGIN
        print 'Update logic'
    END
    ELSE
    BEGIN
        PRINT 'Insert logic'
    END
END

SET NOCOUNT OFF
GO

Are triggers fired on bulk insert?
No by default, but you can turn them on. Controlling Trigger Execution When Bulk Importing Data

Tune in for Triggers Part 3: FAQ and FOP continued

sqlserver

Triggers Part 1: Introduction to madness, plus whores

March 12th, 2009

According to the standard developer canon about databases you should avoid triggers like you avoid wearing your “I love goto statements” t-shirt at the company Christmas party (again).  Knowledge of triggers is verboten, almost as bad as saying you love cursors.  Why are they such a bad idea, and why does the DBA let the air out of your Oldsmobile Firenza tires whenever you write one?  Let’s explore these ideas, and leave you being grown up and driving a Oldsmobile for another day.

First, a brief overview of triggers if you aren’t familiar with them at all (lucky):

Triggers are blocks of T-SQL that are run upon the firing of certain events within SQL Server, the most common one being an insert, update, or delete to a rows in a table. They can also fire upon login, the creation of tables, and other system events. Triggers have certain constraints that they have to live with such as not being able to return results or perform certain operations such as CREATE DATABASE, and they are fed only certain input - @@ROWCOUNT, the inserted and deleted tables, the ambient transaction. They are typically complained at and about by DBAs and developers as being slow and hard to deal with.

Where do triggers come from?

When a whore and a demon love each other they make a baby, and that baby is a trigger with a cursor in it that handles it own transactions and savepoints whilst trying to send email via a call to the smtp service using xp_cmdshell.

Are triggers in the original relational model?

No, but neither are isolation levels, recovery models, or stored procedures. But never fear, very little that you have heard of is in the original relational model unless you married your sister and eat German chocolate bars in which case you are familiar with joined relations and relvars.

Why are triggers given such a bad name - aren’t they just a certain type of stored procedure that happens at a certain time?

Triggers are given a bad name for a few reasons:

What they are typically used for: They are quite powerful and it is easy to overuse them for things that are normally better supported by the DBMS in other places. The classic example of this is constraint or foreign key enforcement - a trigger can stop the insert/update from occurring or check the updated data right before the modification. Business logic can live in a trigger, so that it runs “just in time”. Another common pattern is to use a trigger to keep two separate databases in sync – every update to one causes an insert into another across linked server, etc. Triggers are dark-alleyed shortcuts and there are normally better paths to build the functionality you want.

How hard they are to work with:  Most developers don’t know how to properly code a trigger - there I said it.  In addition, they are added T-SQL surface area that doesn’t show up in most tracing mechanisms for laymen (Activity Monitor, syscomments, indirectly in execution plans, a lot of source control systems, etc).

They are slow: Triggers are inside a transaction doing an insert, update, delete.  This is the essential core of the database system - very close to the DBMS kernel in terms of your application usage.  Adding a trigger that takes half a second can bring your system to a messy halt.  In addition, in the past triggers populated the inserted and deleted tables via building a view from reading the transaction log.  This lead many DBAs to see non sequential behavior in their storage mechanisms that were built for sequential writes leading to even greater slowdowns.  As of SQL Server 2005 triggers are implemented via row versioning which thus makes use of the tempdb instead of reading the log, but DBAs never forget.  In either event, the behavior of triggers is simply different than other types of statements, leading DBAs to be even more unhappy - and they are pretty much unhappy in DisneyWorld.

Tune in for Triggers Part 2 Facts and Frequently-Occuring-Problems (FOP)

sqlserver

Read / Write by server and database

March 10th, 2009

Modified from Jason Massie’s post, here are queries to tell you your read/write ratio per server and database.  Interesting results if you are maintaining multiple databases/application and are acting under the assumption that they behave similarly:

SELECT
    CAST(SUM(user_seeks+user_scans+user_lookups) AS decimal)
    /
    CAST(SUM(user_updates) + SUM(user_seeks+user_scans+user_lookups) AS decimal)
AS ReadPercent
    , CAST(SUM(user_updates) AS decimal) /
    CAST(SUM(user_updates)
    + SUM(user_seeks + user_scans + user_lookups) AS decimal)
AS WriteRatio
FROM sys.dm_db_index_usage_stats

SELECT db.name
, CAST(SUM(user_seeks+user_scans+user_lookups) AS decimal)
/ CAST(SUM(user_updates)+SUM(user_seeks+user_scans+user_lookups) AS decimal)
* 100 AS ReadPercent
, CAST(SUM(user_updates) AS decimal)
/
CAST(SUM(user_updates)
    + SUM(user_seeks + user_scans + user_lookups) AS decimal)
    * 100 AS WriteRatio
FROM sys.dm_db_index_usage_stats t
JOIN sys.databases db on db.database_id = t.database_id
WHERE (user_updates + user_seeks + user_scans + user_lookups) > 0
GROUP BY db.name
ORDER BY db.name

sqlserver

?>?>