Transcript Slide 1
10 DOs and 10 DON’Ts
for the SQL Server CLR
Matt Whitfield
Who am I?
• A geek, mostly
• I hang about on
ask.sqlservercentral.com
• I provide (now free-of-charge)
SQL Server tools
• I help run the #SQLSoton user
group
• I work for a small company in
Southampton
What’s this presentation about?
• The CLR is a hugely untapped resource in
SQL Server
• I’m on a bit of a mission to make people
more aware of what you can do and how
you can do it best
• This presentation addresses the ‘how you
can do it best’ bit…
…at a ‘starter’ level
So what is the CLR anyway?
• CLR = Common Language Runtime
• In the SQL Server world, we use the term
CLR to refer to database objects that are
implemented in a CLR language (C#,
VB.NET …)
• We can make procedures, functions, userdefined types, user-defined aggergates
and triggers
DO #1:
Know your transactions
• The easiest way to wrap your transactions
in the CLR is using the
TransactionScope() class
• If you are just using a single connection,
then a local transaction will be issued
• However, if you connect using a different
connection string (even in the same
database) a distributed transaction will be
enlisted
DON’T #1:
Add strings together
• Basic concept in all .NET code
• string is Immutable
– An instance of a string cannot be changed
– Special compiler support for strings can make
it look like you can change it
• Adding strings together places load on the
managed heap and garbage collector
• Use StringBuilder instead
DO #2:
Know structs v classes
• In .NET, there are two types of object
– Reference types
– Value types
• Reference types are passed around by
their reference – functions will operate on
the same object
• Value types are passed around in their
entirety – functions will operate on a copy
of the object
DO #2:
Know structs v classes
• What does that mean?
DEMO TIME
Any volunteers?
DON’T #2:
Go overboard
• The CLR support in SQL Server is a great
programming tool
• It allows you to perform an extremely wide
variety of operations
• It also, therefore, allows you to do dumb
stuff
• Don’t start trying to implement all your
procedures in CLR for the sake of it
DON’T #2:
Go overboard
• Don’t wrap your T-SQL in a CLR
procedure that just passes that T-SQL
back to the engine (i.e. don’t make CRUD
procedures in the CLR)
• Don’t create CLR types when they don’t
really add value to the task at hand –
remember there is an overhead
DO #3:
Dispose your IDisposables
• If an object implements IDisposable, then it
expects you to call Dispose() on it when you are
finished
• The idea is that Dispose() gives the object the
chance to release unmanaged resources early –
before garbage collection
• You don’t necessarily know why an object is
IDisposable, so you need to trust that it is
necessary
• There is huge misconception about this
DO #3:
Dispose your IDisposables
• The easiest way to guarantee disposal is
through the using() {} block.
• When you exit the scope of a using block,
the resource that you allocated in that
block is guaranteed to be disposed, no
matter how you exit the block (Exception,
return)
• You can find incorrect examples of this all
over the web, MSDN included
DO #3:
Dispose your IDisposables
DEMO TIME
DON’T #3:
Forget that data scales
• Data is often bigger in your production
environment than in your test environment
• Don’t implement anything that assumes a
fixed size of data (e.g. a static array
without bounds checking)
• Don’t assume that an access method that
is fastest with a small amount of data will
be fastest with a large amount of data
DO #4:
Aim to write SAFE code
• Permission sets control what your CLR code can
do
• SAFE is a restrictive permission set, but
targeting it means that you cannot affect the
stability of the SQL Server Process
• EXTERNAL_ACCESS allows access to
resources outside of SQL Server, but be careful
not to introduce unnecessary waits
• Web service call in a trigger – I’m looking right at
you
DO #4:
Aim to write SAFE code
• UNSAFE is called unsafe for a reason.
• You need to be very sure about what you
are doing under the UNSAFE permission
set.
• How long will that static variable live for?
• What exactly is happening when another
thread is started?
DON’T #4:
Treat NULL as an afterthought
• NULLs happen
• Always assume that you will receive a NULL
value as a parameter or in data that you read
back from the database
• Use nullable types to wrap simple types in your
data structures if you need to
• The Sqlxxx types all represent NULL with the
IsNull property
• Nullable types represent NULL with the
HasValue property
DO #5:
Understand the GAC-lack
• Only ‘blessed’ assemblies can be loaded
from the GAC
• Other assemblies need to be loaded
through CREATE ASSEMBLY
• This can create a maintenance issue if
application layer code shares an assembly
with the database layer code
DO #5:
Understand the GAC-lack
•
•
•
•
•
•
•
•
•
Mscorlib.dll
System.Data.dll
System.dll
System.Xml.dll
System.Security.dll
System.Web.Services.dll
System.Data.SqlXml.dll
System.Transactions.dll
System.Configuration.dll
•
•
•
•
Microsoft.VisualBasic.dll
Microsoft.VisualC.dll
CustomMarshalers.dll
System.Data.OracleClient.dll
DON’T #5:
Use finalisers
• Finalisers are the .NET equivalent of
destructors – code which is fired when an
object is freed.
• In .NET, garbage collection does the free,
and so calls your finaliser for you
• This means that your class is always
promoted to a Generation 1 collection…
• What on earth is that?
DON’T #5:
Use finalisers
• Garbage collection is done in generations
0, 1 and 2
• Generation 0 is the cheapest, implying the
least work for the garbage collector
• A ‘dead’ object with a finaliser is always
skipped – guaranteeing promotion to
Generation 1
• All objects referenced by the object with
the finaliser are also kept alive
DO #6:
Understand managed memory
• Managed memory isn’t the same as Native
memory
• The managed memory used by the CLR
comes from the MemToLeave address
space
• Moving objects between native and
managed memory is called marshalling,
and it has a cost associated with it
DON’T #6:
Reinvent the wheel
• Does your boss like the ‘Just Do It’
attitude?
• It works for Nike, for coding not so much
• Take the time to find out if the function you
want already exists (it probably does)
• If you can’t find it, try community sites –
they are immensely helpful
DO #7:
Use the context connection
• If you need to get data from the database
that your code is running against…
…use the context connection
• Connection string is:
“context connection=true;”
• Connecting using a standard connection
requires elevation to
EXTERNAL_ACCESS
DON’T #7:
Access data in scalar functions
• CLR Scalar Functions can be a lot faster
than their T-SQL equivalents, particularly
for string manipulations and complex
procedural logic
• Accessing data in scalar functions is not
cool
• You will absolutely kill performance by
setting up a connection, running a query,
returning the result
DO #8:
Write efficient code
• If you are running CLR code in SQL Server, then
you are running it in the place within your
architecture that is hardest to scale
• It might be easy to write a brute-force algorithm,
but when will it become a problem?
• When you’re on holiday – and you certainly don’t
want to spend the time on re-writing the
algorithm to be more efficient then
• Put your code in a normal app to profile it
DON’T #8:
Over optimise
• Hang on… didn’t you just say to write
efficient code?
• Yes, but there’s a difference between
efficient and unintelligible
• Simple, well structured code often has
suitable performance characteristics
• Don’t spend 90% of the time on the last
10% performance gain
• Find and solve the major bottlenecks first
DO #9:
Understand boxing
• No, not the sport, or the day after
christmas
• We learned about value and reference
types earlier
• All objects can be treated the same way
• So how is a value type passed in a context
that expects a reference (e.g. List<T>)
• Boxing
DO #9:
Understand boxing
• Boxing is taking your value type, and
enclosing it in an Object instance, so it
becomes a reference type
• This has implications for equality
• 1 == 1, but (object)1 != (object)1
• Why?
• Because when boxed, each 1 has it’s own
box & reference, and these are not equal
DON’T #9:
Move the middle tier in
• We’ve established that sometimes it can
be useful to run middle tier code in the
database
• This should not be a default position
• Running middle tier code in the database
will limit your ability to scale-out – scaling
out the database layer is inherently more
complex than scaling out a middle tier.
DO #10:
Use Dictionary<TKey, TValue>
• Dictionary objects allow fast (close to
O(1)) access to a large list of objects
• Finding an object by key rather than
looping through is the exact equivalent of
doing an index seek rather than a scan
• When implementing objects for dictionary
access, over-ride the GetHashCode and
Equals methods of Object
DO #10:
Use Dictionary<TKey, TValue>
• Be careful, though
• If two objects would return true when
Equals was called between them, then
their GetHashCode calls must return the
same value
• This makes it unsuitable for fuzzy
matching (e.g. allowing tolerance between
float values)
DON’T #10:
Call Environment.Exit
• So Mladen Prajdic told me I should put this
in. I think he was joking but…
• This shows some of the dangers of
working in the UNSAFE permission set
• What happens when you call
Environment.Exit?
• It exits your procedure and rolls back?
DOs:
•
•
•
•
•
•
•
•
•
•
Know your transactions
Understand structs & classes
Dispose your IDisposables
Aim to write SAFE code
Understand the GAC
Understand managed memory
Use the context connection
Write efficient code
Understand boxing
Use Dictionary objects
DON’Ts:
•
•
•
•
•
•
•
•
•
•
Add strings together
Go overboard
Forget that data scales
Treat NULL as an afterthought
Use finalisers
Re-invent the wheel
Access data in scalar functions
Over-optimise
Move the middle tier in
Call Environment.Exit
Thanks!
• Any questions – drop me an email:
[email protected]
@atlantis_uk on twitter
• If you live near Southampton – come to the
#SQLSoton user group
• If not – find your local user group at Community
corner