Worst Practice - Not Qualifying Objects With The Owner

,

When Andy recently added his latest piece in the Worst Practices series,

about adding a column without thinking, it made me think about a worst practice

I have been trying to combat for a long time. Maybe not as specific as most

other worst practices, but one of the worst practices I often encounter is plain

and simple laziness. One mistake that people often do just because they are lazy

is to not qualify the names of objects with owner name. I will discuss this

here, and in a later article I will discuss some other mistakes caused by

laziness.

Using qualified names

Always remember to qualify object names with the owner of the object. This is

good for performance for at least two reasons. The first is that when you

specify an object name without qualifying it with owner name, SQL Server will

first look for an object owned by the current logged on user. If not found, an

object with the same name but owned by dbo will be used. So if a user named John

is logged on to Northwind and executes SELECT OrderID FROM Orders, SQL Server

will look for the object John.Orders in Northwind, and when not found dbo.Orders

will be used. If he would have executed SELECT OrderID FROM dbo.Orders instead

SQL Server would not have needed to look for an object named John.Orders. This

might not affect performance in any notable way, but it is also important to

avoid using an unexpected object. Imagine an application that logs on as this

user John and executes a SELECT OrderID FROM Orders statement. This works fine

until one day someone logged in as John decides to create a table called

John.Orders (for whatever reason). Now the application will suddenly start

returning OrderIDs from the new table John.Orders, and this bug might be pretty

hard to find.

The other reason it is good for performance to qualify object names with

owner name is that there is a much better chance of SQL Server reusing execution

plans for statements written that way. For instance, if you are using

sp_executesql to execute dynamic SQL statements you must actually fully qualify

object names for SQL Server to reuse the execution plans. Fully qualifying an

object name means specifying all four parts of it, i.e.

server_name.database_name.owner_name.object_name. If you omit any of these a

default will be used. Default server and database is of course the server and

database where the query is executed, and default owner_name is as earlier

specified dbo. But even though a default is used, SQL Server will not reuse the

execution plan for statements specifying differently qualified object names. You

can see this for yourself in SQL Profiler by running the following small script.

Start Profiler and connect it to your server. Add all events in the Stored

Procedures event class and remove all other events.

DECLARE @sql nvarchar(200)
DECLARE @params nvarchar(100)
DECLARE @intvar int
-- Create statement using fully qualified names
SET @sql = N'SELECT OrderID, CustomerID, EmployeeID FROM dbo.Orders WHERE EmployeeID = @empid'
SET @params = N'@empid int'
SET @intvar = 3
EXEC sp_executesql @sql, @params, @empid = @intvar
-- Execute the same statement again, reusing the previous execution plan
-- Note SP:ExecContextHit in Profiler
SET @intvar = 4
EXEC sp_executesql @sql, @params, @empid = @intvar
-- Same statement but not using fully qualified names
-- Note there is no SP:ExecContextHit in Profiler
SET @sql = N'SELECT OrderID, CustomerID, EmployeeID FROM Orders WHERE EmployeeID = @empid'
SET @params = N'@empid int'
SET @intvar = 4
EXEC sp_executesql @sql, @params, @empid = @intvar

When you run the script you should see an SP:CacheInsert when the first query

is executed, but when it is executed again using only a changed value for @empid

you will instead see an SP:ExecContextHit. This means that SQL Server could

reuse the execution plan. Finally, when the third query is executed, you will

again see an SP:CacheInsert, even though the result of the query is exactly the

same as for the prior one. The reason is as I explained earlier that the second

statement does not qualify the name for Orders in the same way as the first

statement does.

Specifying the owner name is not just important when executing SELECT

statements. It is also important, arguably even more important, to qualify

objects with owner name when creating them. For instance, if John would create a

table without specifying the owner of it he would become the owner himself. This

means that any other user who wants to execute a SELECT statement against this

table must qualify the table name with his owner name when SELECTing from it. If

they would also always remember to qualify object names it might not be that

much of a problem, but since we know people are lazy (or do not know what it

means to qualify a name) they probably will not always do that. So when John

creates his table his statement should begin with CREATE TABLE dbo.TableName ...

There are of course exceptions when you really want to create a table with John

as owner, but in those cases he should specify that, i.e. CREATE TABLE

John.TableName ... Why is this important? Imagine that John creates this table

in a database on a test server and saves the statement in a script. Later, when

this table is to be created in the production database it might be someone else

that executes this script, and the table will get a different owner.

It is even more important to qualify names of objects used inside a stored

procedure. Otherwise objects that are not qualified with owner name and

referenced in SELECT, INSERT, UPDATE and DELETE statements will default to the

owner of the stored procedure, not the person executing the stored procedure.

Again, these objects might be those you want, but it is always better to

explicitly qualify the names. If the procedure is used as an access control

mechanism for data in tables (i.e. direct access to tables is restricted and all

users must use procedures that SELECT data from them) yu might run into another

problem. If those object names for the tables that are restricted are not

qualified in the procedure only the creator of the procedure will be allowed to

access them through it.

Finally, if you do not qualify object names used in procedures with the

statements CREATE/ALTER/DROP TABLE, TRUNCATE TABLE, CREATE/DROP INDEX, UPDATE

STATISTICS and DBCC commands you might cause havoc in the system. The reason for

this is that object names are resolved at run-time, using the name of the user

that executes the procedure as the default owner for objects that are not fully

qualified. Imagine that the user Jane has a table called Orders where she keeps

her order info. Now she needs a quick and easy way to empty it so she creates a

procedure like the one below:

CREATE PROCEDURE TruncateOrders
AS
BEGIN
  TRUNCATE TABLE Orders
END

She tries the procedure in a test environment and it works great, her Orders

table gets truncated so she moves the procedure to the production environment.

Then one day John needs to empty the data from Jane's Orders table, so he

executes the procedure (by running EXEC Jane.TruncateOrders). Guess what

happens? Well, if there is a table called John.Orders it would be truncated, and

if there is not, then dbo.Orders will be truncated since dbo is the default that

is used when SQL Server does not find an object owned by the current user.

So, for the reasons listed above I would recommend you to make it a habit to

always specify all objects names using the syntax owner_name.object_name.

And I am not just saying you should write your procedures this way, I want you

to make it a habit to always do it, even for quick ad-hoc queries that

you just execute once. If you make it a habit you will not forget it when it is

really necessary and important.

Rate

4.5 (2)

Share

Share

Rate

4.5 (2)