You are on page 1of 30

(c) Microsoft Corporation 2013

This Sample Code is provided for the purpose of illustration only and is not
intended to be used in a production environment. THIS SAMPLE CODE AND
ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE. We grant You a nonexclusive, royalty-free right to use
and modify the Sample Code and to reproduce and distribute the object code
form of the Sample Code, provided that You agree: (i) to not use Our name,
logo, or trademarks to market Your software product in which the Sample Code
is embedded; (ii) to include a valid copyright notice on Your software product in
which the Sample Code is embedded; and (iii) to indemnify, hold harmless, and
defend Us and Our suppliers from and against any claims or lawsuits, including
attorneys’ fees, that arise or result from the use or distribution of the Sample
Code.

This posting is provided "AS IS" with no warranties, and confers no rights. Use
of included script samples are subject to the terms specified at
http://www.microsoft.com/info/cpyright.htm.

http://blogs.msdn.com/b/arvindsh 1
(c) Microsoft Corporation 2013

http://blogs.msdn.com/b/arvindsh 2
(c) Microsoft Corporation 2013

http://blogs.msdn.com/b/arvindsh 3
(c) Microsoft Corporation 2013

Note: This chart will typically flip around (at least the cost does) for applications already
in PRODUCTION. In those cases, it is probably cheapest to add hardware!

http://blogs.msdn.com/b/arvindsh 4
(c) Microsoft Corporation 2013

http://blogs.msdn.com/b/arvindsh 5
(c) Microsoft Corporation 2013

Sample Script

-- Let's try again with a LIKE, but with a partial value in the Literal. You will notice that
this query uses an index seek
SELECT *
FROM Person.Person
WHERE LastName LIKE 'Smi%'
GO

-- Next, switch to a total wildcard query observe what happens – an index scan
SELECT *
FROM Person.Person
WHERE LastName LIKE '%mi%‘

Full text indexes might be a more appropriate choice in the ‘total wildcard’ case.

http://blogs.msdn.com/b/arvindsh 6
(c) Microsoft Corporation 2013

Serial execution plan behavior is documented at http://msdn.microsoft.com/en-


us/library/ms191007.aspx

Sample Script for SchemaBinding issue is available from


http://blogs.msdn.com/b/sqlprogrammability/archive/2006/05/12/596424.aspx

Sample script for serial execution (you need a multi-CPU machine for this)

Use [tempdb]
GO

CREATE FUNCTION dbo.ComputeNum(@i int)


RETURNS int
-- WITH SCHEMABINDING
BEGIN
RETURN @i * 2 + 50
END
GO

-- use estimated graphical plan here

-- First without the UDF you can see a parallel scan is in place
select sum(S1.OrderQty + S2.OrderQty)
from AdventureWorks.sales.SalesOrderDetail S1,

http://blogs.msdn.com/b/arvindsh 7
(c) Microsoft Corporation 2013

AdventureWorks.sales.SalesOrderDetail S2
where S1.productid = 997
GO

-- Then with the UDF. Show estimated plan only. Note the absence of any parallelism in
the operators.
-- NOTE: this query is NOT functionally equivalent to the above. However, in terms of data
access the raw cost is of a similar magnitude.
select tempdb.dbo.ComputeNum(S1.OrderQty + S2.OrderQty)
from AdventureWorks.sales.SalesOrderDetail S1,
AdventureWorks.sales.SalesOrderDetail S2
where S1.productid = 997
GO

DROP FUNCTION dbo.ComputeNum


GO

http://blogs.msdn.com/b/arvindsh 7
(c) Microsoft Corporation 2013

RSPCV = Royal Society for Prevention of Careless Variable declaration


Pun on RSPCA = Royal Society for Prevention of Cruelty to Animals

Row structure (reference


http://blogs.msdn.com/b/sqlserverstorageengine/archive/2006/06/23/644607.aspx)

Status bits
Fixed length data size
Fixed length data
Total number of columns
Null bitmap
Variable length column count
Variable length column offsets
Variable length data
Varchar(2) usage is documented http://msdn.microsoft.com/en-
us/library/Dd193263(v=VS.100).aspx

http://blogs.msdn.com/b/arvindsh 8
(c) Microsoft Corporation 2013

Sample script

USE TEMPDB
GO

CREATE TABLE SmallVariableStringSizes


(
Varchar1Col varchar(1)
)
GO

SET NOCOUNT ON
INSERT SmallVariableStringSizes VALUES ('a')
GO 100000

-- Next, deal with the fixed size allocation

CREATE TABLE SmallFixedStringSizes


(
Char1Col char(1)
)
GO

SET NOCOUNT ON
INSERT SmallFixedStringSizes VALUES ('a')
GO 100000

-- Compare row sizes, you will see the SmallVariableStringSizes is actually bigger!
select avg_record_size_in_bytes, *
from sys.dm_db_index_physical_stats(db_id(), object_id('SmallVariableStringSizes'), null,
null, 'detailed')

select avg_record_size_in_bytes, *
from sys.dm_db_index_physical_stats(db_id(), object_id('SmallFixedStringSizes'), null,
null, 'detailed')
Go

http://blogs.msdn.com/b/arvindsh 8
(c) Microsoft Corporation 2013

Sample script

USE AdventureWorks
GO

DECLARE @Name NVARCHAR (50)

SELECT @Name = Name


FROM Production.Product P
WHERE P.ProductModelID = 14

-- we think we have an unique value, but did we really check?


-- NOTE: the IIF syntax will only work on SQL 2012 and above
SELECT IIF(@@ROWCOUNT > 1, 'Warning; Multiple values', 'OK')

http://blogs.msdn.com/b/arvindsh 9
(c) Microsoft Corporation 2013

Sample script

-- Though the syntax below will be accepted by SQL, it offers no guarantees


CREATE VIEW TestOrderBy
AS
SELECT TOP 100 PERCENT BusinessEntityID, Title, FirstName, LastName
FROM Person.Person
ORDER BY LastName
GO

-- The above 'appears' to work, but does it


-- really guarantee order? Let's see:
SELECT * FROM TestOrderBy
GO

-- Let's try again with an explicit ORDER BY,


-- note the SORT operator now
SELECT * FROM TestOrderBy
ORDER BY LastName

http://blogs.msdn.com/b/arvindsh 10
(c) Microsoft Corporation 2013

Only the paranoid survive: book by Andy Grove


http://en.wikipedia.org/wiki/Andrew_Grove

Defense in depth:

• Platform level screening (URLScan)


• App level screening (regex based checks)
• Strong passwords
• Use Windows authentication
• Minimum privilege (no usage of SYSADMIN or DB_OWNER role members.)
• Strong audit mechanism

Sample script

USE TEMPDB
GO

CREATE TABLE ToBeDropped


(
i int
)
GO

CREATE PROCEDURE InquiryProc

http://blogs.msdn.com/b/arvindsh 11
(c) Microsoft Corporation 2013

(
@LName varchar(50)
)
AS
DECLARE @SQL NVARCHAR(4000)

-- Use the parameter as-is; results will be seen later :-)


SET @SQL = CONCAT('SELECT * FROM AdventureWorks.Person.Person
WHERE LastName = ''', @LName, '''')

Print @SQL

EXECUTE (@SQL)
GO

EXEC InquiryProc 'Singh' -- should get back 2 rows

-- check for the 'important' table, it is still there.


select * from ToBeDropped

-- next try to 'inject' bad SQL


EXEC InquiryProc 'Singh''; DROP TABLE ToBeDropped; SELECT '''

-- check for the 'important' table, it is gone!


select * from ToBeDropped

-- so how does one mitigate this stuff?


-- DEFENSE in DEPTH is the key
-- firstly, apply URLScan at the web server to
-- stop 'malformed' HTTP requests
-- secondly, use app code to screen for keywords
-- thirdly in the SQL layer use a CLR UDF to screen
-- for such keywords in input parameter
-- reference link: http://blogs.msdn.com/b/mike/archive/2008/10/15/how-to-configure-
urlscan-3-0-to-mitigate-sql-injection-attacks.aspx

http://blogs.msdn.com/b/arvindsh 11
(c) Microsoft Corporation 2013

Row goals are explained at


http://blogs.msdn.com/b/queryoptteam/archive/2006/03/30/564912.aspx

Sample script

USE AdventureWorks
GO

SET STATISTICS IO ON
GO

-- First the traditional approach, on SQL 2005 AdventureWorks database this will scan
(due to lack of a supporting index)
DECLARE @rowcount int
SELECT @rowcount = COUNT(*)
FROM Person.Contact
WHERE LastName = 'Diaz'

IF (@rowcount > 0)
BEGIN
Print 'has rows'
END
GO

http://blogs.msdn.com/b/arvindsh 12
(c) Microsoft Corporation 2013

-- Next, the optimized version with EXISTS. This will also ‘scan’ but the scan stops after
the first matching row. So it turns out to be much more performant.
-- You can also validate this from the logical reads count in the messages window
IF EXISTS (SELECT *
FROM Person.Contact
WHERE LastName = 'Diaz')
BEGIN
Print 'has rows'
END
GO

http://blogs.msdn.com/b/arvindsh 12
(c) Microsoft Corporation 2013

WYSIWYG = What you see is what you get

Sample script

-- Does this ORDER BY really guarantee anything?


-- Note that in SQL 2008 and below, this would
-- actually insert a sort operator without any logical benefit
-- repeat this step twice, first in SQL 2008 and then in 2012
-- SQL 2012 seems to 'optimize out' the Sort operator
:Connect .\sql2008

SELECT Title, FirstName, LastName


INTO #PTemp
FROM AdventureWorks.Person.Person
ORDER BY FirstName
GO

SELECT * FROM #PTemp


GO

http://blogs.msdn.com/b/arvindsh 13
(c) Microsoft Corporation 2013

Sample script

-- BUT the minute you apply a function like RTRIM or LTRIM:


SELECT BusinessEntityID, LastName
FROM Person.Person
WHERE LTRIM(LastName) = 'Smith'
GO

-- for a moment, compare the row estimate between


-- the LTRIM above and the RTRIM below
SELECT FirstName, LastName
FROM Person.Person
WHERE RTRIM(LastName) = 'Smith'
GO

-- Try adding a computed column and persist it


-- we are using LTRIM for this case because we saw more skewed C.E. in that case
ALTER TABLE Person.Person
ADD TrimmedName as LTRIM(LastName)
GO

CREATE NONCLUSTERED INDEX IDX_TrimLName


ON Person.Person (TrimmedName)
GO

http://blogs.msdn.com/b/arvindsh 14
(c) Microsoft Corporation 2013

-- Compare with these now


-- compare with something with smaller C.E.
-- seeks even if we have to lookup
-- this is due to the 'tipping point' effect
SELECT BusinessEntityID, LastName
FROM Person.Person
WHERE LTRIM(LastName) = 'Singh'
GO

-- rewrite the query in another way to use the computed column explicitly:
SELECT BusinessEntityID, TrimmedName
FROM Person.Person
WHERE TrimmedName = 'Smith'
GO

http://blogs.msdn.com/b/arvindsh 14
(c) Microsoft Corporation 2013

http://blogs.msdn.com/b/arvindsh 15
(c) Microsoft Corporation 2013

Sample script

USE TEMPDB
GO

-- Create a copy of the Person table in TEMPDB


SELECT BusinessEntityID, Title, FirstName, MiddleName, CAST (LastName AS
VARCHAR(50)) LastName
INTO PersonTemp
FROM AdventureWorks.Person.Person
GO

-- Create a CL index on LastName, this will support range queries pretty well
CREATE CLUSTERED INDEX CL_PersonTemp ON PersonTemp(LastName)
GO

-- Turn on actual graphical execution plan from SSMS

-- Now simulate a 'good client' query


DECLARE @LName VARCHAR(50) = 'Smith'

SELECT *
FROM PersonTemp
WHERE LastName = @LName

http://blogs.msdn.com/b/arvindsh 16
(c) Microsoft Corporation 2013

GO

-- Compare it with a 'bad client' query


DECLARE @LName NVARCHAR(50) = N'Smith'

SELECT *
FROM PersonTemp
WHERE LastName = @LName
GO

-- This problem is quite common with JDBC drivers which send all strings as Unicode by
default
-- reference http://sqlcat.com/sqlcat/b/msdnmirror/archive/2010/04/05/character-data-
type-conversion-when-using-sql-server-jdbc-drivers.aspx

http://blogs.msdn.com/b/arvindsh 16
(c) Microsoft Corporation 2013

http://blogs.msdn.com/b/arvindsh/archive/2011/02/15/option-recompile-redux-a-k-a-
parameter-embedding-optimization-not-working.aspx

http://blogs.msdn.com/b/arvindsh 17
(c) Microsoft Corporation 2013

Isolation level note:


http://connect.microsoft.com/SQLServer/feedback/details/243527/sp-reset-connection-
doesnt-reset-isolation-level

http://blogs.msdn.com/b/arvindsh 18
(c) Microsoft Corporation 2013

If you are a Premier customer then you can avail the service from PFE ‘T-SQL Patterns
and Practices Review’ – contact your TAM for details (http://www.microsoft.com/en-
us/microsoftservices/premier_support.aspx)

You can also contact me via. my blog: http://blogs.msdn.com/b/arvindsh

http://blogs.msdn.com/b/arvindsh 19
(c) Microsoft Corporation 2013

Slide Commentary: Windows Store App Labs are being offered at Tech Ed for the first
time. Developers who are in the process of building an app or have already built an app,
can get exclusive time with experts to avail design, testing and technical support. The
App Lab houses some of the best in class hardware to enable app reviews and testing.

http://blogs.msdn.com/b/arvindsh 20
(c) Microsoft Corporation 2013

http://blogs.msdn.com/b/arvindsh 21
(c) Microsoft Corporation 2013

http://blogs.msdn.com/b/arvindsh 22
(c) Microsoft Corporation 2013

http://blogs.msdn.com/b/arvindsh 23
(c) Microsoft Corporation 2013

http://blogs.msdn.com/b/arvindsh 24