Welcome to MSDN Blogs Sign in | Join | Help

Deadlock Troubleshooting in SQL Server Analysis Services ( SSAS )

A deadlock is a special type of situation wherein two or more transactions are waiting for the other to finish, and thus neither ever does and no one can proceed. Deadlocks can be often considered as normal in a high concurrent update environment. When a deadlock is detected, some systems (such as SQL Server) will choose the least expensive process to rollback to resolve the deadlock.

 

Deadlocks not only happen in OLTP systems but can also happen in an OLAP environment. If you suspect you are having some deadlock issues with SQL Analysis Services,  you can follow the example below to trouble shoot the problem.

The example in the blog here is taken out from a SSAS 2005 server, but the same concept and steps should be applicable in SSAS 2008 as well.

The best way to trouble shoot a deadlock in SSAS is to capture the whole scenario within a SQL profiler trace and then analyze the trace offline.

By default, the “Deadlock” event is not selected. You will need to explicitly select the Deadlock event under Locks within the Trace Properties (see screen shot #1 below)

 

Screen shot #1

image

Additional to the Deadlock Event, please make sure that the “Command Begin” and “Command End” events are also included. You can add other events you are interested in.  

(If you need detail information on how to use SQL Server Profiler to Monitor Analysis Services, please check out the BOL: http://msdn.microsoft.com/en-us/library/ms174946(SQL.90).aspx)

Once you have the trace captured, you can open up the trace offline with the profiler. The first thing to look for is the “Deadlock” under the EventClass (see screen shot #2).  If you see one, the deadlock event usually is followed by the following error

Transaction errors: Aborting transaction on session <SPID>. Transaction errors: The lock operation ended unsuccessfully because of deadlock.

Screen shot #2

clip_image002

The deadlock graph is captured in the TextData column of the deadlock event (see screen shot #3). It is easier to look at the whole description by coping the TextData and paste it on to an XMLA windows.

 

Screen shot #3

clip_image003

Here is an example deadlock graph:

<DeadlockGraph>

  <VICTIM>

    <LOCK_TRANSACTION_ID>0DA3B625-2632-4BC8-BF42-8BD04C0F40E5</LOCK_TRANSACTION_ID>

    <SPID>84947</SPID>

  </VICTIM>

  <LOCKS>

    <Lock>

      <LOCK_OBJECT_ID>

        <Object>

          <DatabaseID>SQL FoodMart_2008_12_29</DatabaseID>

          <CubeID>SQL FoodMart</CubeID>

          <MeasureGroupID>Internet Sales</MeasureGroupID>

        </Object>

      </LOCK_OBJECT_ID>

      <LOCK_ID>2C768E8D-90B3-410A-B0B9-808A99787203</LOCK_ID>

      <LOCK_TRANSACTION_ID>0DA3B625-2632-4BC8-BF42-8BD04C0F40E5</LOCK_TRANSACTION_ID>

      <SPID>84947</SPID>

      <LOCK_TYPE>2</LOCK_TYPE>

      <LOCK_STATUS>1</LOCK_STATUS>

    </Lock>

    <Lock>

      <LOCK_OBJECT_ID>

        <Object>

          <DatabaseID>SQL FoodMart_2008_12_29</DatabaseID>

          <CubeID>SQL FoodMart</CubeID>

          <MeasureGroupID>Retail Sales</MeasureGroupID>

        </Object>

      </LOCK_OBJECT_ID>

      <LOCK_ID>3FE9E9BF-5341-4269-989F-9A7E8FA2166E</LOCK_ID>

      <LOCK_TRANSACTION_ID>0DA3B625-2632-4BC8-BF42-8BD04C0F40E5</LOCK_TRANSACTION_ID>

      <SPID>84947</SPID>

      <LOCK_TYPE>2</LOCK_TYPE>

      <LOCK_STATUS>0</LOCK_STATUS>

    </Lock>

    <Lock>

      <LOCK_OBJECT_ID>

        <Object>

          <DatabaseID>SQL FoodMart_2008_12_29</DatabaseID>

          <DataSourceViewID>SQL Foodmart DW</DataSourceViewID>

        </Object>

      </LOCK_OBJECT_ID>

      <LOCK_ID>FD240063-75BD-4348-9E7D-F2B8903E27E1</LOCK_ID>

      <LOCK_TRANSACTION_ID>94406B70-4078-4B35-8AFF-0120AF3BF8A7</LOCK_TRANSACTION_ID>

      <SPID>84858</SPID>

      <LOCK_TYPE>2</LOCK_TYPE>

      <LOCK_STATUS>1</LOCK_STATUS>

    </Lock>

    <Lock>

      <LOCK_OBJECT_ID>

        <Object>

          <DatabaseID>SQL FoodMart_2008_12_29</DatabaseID>

          <CubeID>SQL FoodMart</CubeID>

          <MeasureGroupID>Internet Sales</MeasureGroupID>

        </Object>

      </LOCK_OBJECT_ID>

      <LOCK_ID>B8177860-45F0-40EE-AD0F-A2867C1851B4</LOCK_ID>

      <LOCK_TRANSACTION_ID>94406B70-4078-4B35-8AFF-0120AF3BF8A7</LOCK_TRANSACTION_ID>

      <SPID>84858</SPID>

      <LOCK_TYPE>4</LOCK_TYPE>

      <LOCK_STATUS>0</LOCK_STATUS>

    </Lock>

    <Lock>

      <LOCK_OBJECT_ID>

        <Object>

          <DatabaseID>SQL FoodMart_2008_12_29</DatabaseID>

          <CubeID>SQL FoodMart</CubeID>

          <MeasureGroupID>Retail Sales</MeasureGroupID>

        </Object>

      </LOCK_OBJECT_ID>

      <LOCK_ID>94F8C292-41D2-43B1-B9BC-929B4FD953E7</LOCK_ID>

      <LOCK_TRANSACTION_ID>94406B70-4078-4B35-8AFF-0120AF3BF8A7</LOCK_TRANSACTION_ID>

      <SPID>84858</SPID>

      <LOCK_TYPE>4</LOCK_TYPE>

      <LOCK_STATUS>1</LOCK_STATUS>

    </Lock>

  </LOCKS>

</DeadlockGraph>

Before we go on, let look at some of the items  highlighted above.

“LOCK_STATUS” is the status of a lock. It is either 0 or 1.  0 means “waiting" and 1 means “granted"

The possible LOCK TYPES of a lock are:

 

 

 

 

 

 

 

LOCK_READ             

0x0000002

read       - for processing

 

 

LOCK_WRITE             

0x0000004

write     - for processing

 

 

LOCK_COMMIT_READ   

0x0000008     

commit - shared

 

 

LOCK_COMMIT_WRITE  

0x0000010     

commit - exclusive

 

 

 

 

 

 

 

Two SPID are involved in the deadlock example. They are <SPID>84947</SPID> and <SPID>84858</SPID>.

Now, with the information about different lock types and lock status, we can paint a better picture.

 

(A)   Process 84947 is holding a read lock on “Internet Sales” but it is waiting on a read lock on “Retail Sales” before it can proceed.

(B)   Process 84858 is holding a write lock on “Retail Sales” but it is waiting on a write lock on “Internet Sales” before it can proceed

 

A is waiting on B and B is waiting on A. This is a classic deadlock situation.

To understand why we are getting to this stage, we need to find out what commands are running by A and B at the time of the deadlock.

 

We know that the SPID are 84947 and 84858. We can use the numbers within the profiler to filter out the commands that are issued by these specific SPID (see screen shot #4). We are interested in the commands found immediate before the deadlock error.

 

Screen shot #4

clip_image004

In the scenario here, we found the following:

Process 84947 is processing a Partition under measure group “Retail Sales” at the time of deadlock.

<Batch xmlns="http://schemas.microsoft.com/analysisservices/2003/engine">

  <ErrorConfiguration xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <KeyErrorLimit>-1</KeyErrorLimit>

  </ErrorConfiguration>

  <Parallel>

    <Process xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

      <Object>

        <DatabaseID>SQL FoodMart_2008_12_29</DatabaseID>

        <CubeID>SQL FoodMart</CubeID>

        <MeasureGroupID>Retail Sales</MeasureGroupID>

        <PartitionID>Partition Y2008</PartitionID>

      </Object>

      <Type>ProcessFull</Type>

      <WriteBackTableCreation>UseExisting</WriteBackTableCreation>

    </Process>

  </Parallel>

</Batch>

 

And process 84858 is processing a list of dimension objects.

<Batch xmlns="http://schemas.microsoft.com/analysisservices/2003/engine">

  <Process xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ddl2="http://schemas.microsoft.com/analysisservices/2003/engine/2" xmlns:ddl2_2="http://schemas.microsoft.com/analysisservices/2003/engine/2/2">

    <Object>

      <DatabaseID>SQL FoodMart_2008_12_29</DatabaseID>

      <DimensionID>Products</DimensionID>

    </Object>

    <Type>ProcessUpdate</Type>

    <WriteBackTableCreation>UseExisting</WriteBackTableCreation>

  </Process>

  <Process xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ddl2="http://schemas.microsoft.com/analysisservices/2003/engine/2" xmlns:ddl2_2="http://schemas.microsoft.com/analysisservices/2003/engine/2/2">

    <Object>

      <DatabaseID>SQL FoodMart_2008_12_29</DatabaseID>

      <DimensionID>DiscountCard Point Level</DimensionID>

    </Object>

    <Type>ProcessUpdate</Type>

    <WriteBackTableCreation>UseExisting</WriteBackTableCreation>

  </Process>

  <Process xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ddl2="http://schemas.microsoft.com/analysisservices/2003/engine/2" xmlns:ddl2_2="http://schemas.microsoft.com/analysisservices/2003/engine/2/2">

    <Object>

      <DatabaseID>SQL FoodMart_2008_12_29</DatabaseID>

      <DimensionID>Customers</DimensionID>

    </Object>

    <Type>ProcessUpdate</Type>

    <WriteBackTableCreation>UseExisting</WriteBackTableCreation>

  </Process>

  <Process xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ddl2="http://schemas.microsoft.com/analysisservices/2003/engine/2" xmlns:ddl2_2="http://schemas.microsoft.com/analysisservices/2003/engine/2/2">

    <Object>

      <DatabaseID>SQL FoodMart_2008_12_29</DatabaseID>

      <DimensionID>DiscountCard Award Points</DimensionID>

    </Object>

    <Type>ProcessUpdate</Type>

    <WriteBackTableCreation>UseExisting</WriteBackTableCreation>

  </Process>

  <Process xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ddl2="http://schemas.microsoft.com/analysisservices/2003/engine/2" xmlns:ddl2_2="http://schemas.microsoft.com/analysisservices/2003/engine/2/2">

    <Object>

      <DatabaseID>SQL FoodMart_2008_12_29</DatabaseID>

      <DimensionID>Stores</DimensionID>

    </Object>

    <Type>ProcessUpdate</Type>

    <WriteBackTableCreation>UseExisting</WriteBackTableCreation>

  </Process>

</Batch>

 

Now we know that although the product supports parallel processing multiple partitions, the product does not support parallel processing dimensions mixing with partitions in two separate transactions. Here we can see parallel processing dimensions and partitions can potential run into a deadlock situation.  To avoid something similar happen again, we should process all the dimension objects first prior moving on to partition processing.

C S John Lam | Premier Field Engineer - SQL Analysis Services

Posted by sqlpfebl | 1 Comments
Filed under:

SQL Server memory models (Part I)

You may have questioned why, on 64 bit enterprise Edition, DBCC MEMORYSTATUS reports non zero “AWE allocated” memory even if ‘AWE enabled’ option is not enabled. A short description of SQL Server memory model will help to answer.

Here is an example of a DBCC MEMORYSTATUS output on a 32GB X64 Enterprise SQL Server 2005:

 

Memory Manager                  KB

------------------------------ --------------------

VM Reserved                    33962976

VM Committed                   547508

AWE Allocated                  29843712

Reserved Memory                1024

Reserved Memory In Use         0

There are 3 available memory allocation types driven by the following condition:

 

  • MMAwe
    • Enterprise Edition
    • Lock Page In Memory
    • TF 835 not enabled

 

  • MMConventional
    • Standard edition

At SQL Server startup you can find messages symptomatic of the chosen memory model based on the conditions exposed previously.

  • AWE model

2009-07-01 11:24:46.330 Server       Detected 4 CPUs. This is an informational message; no user action is required.

2009-07-01 11:24:46.400 Server       Using locked pages for buffer pool.

2009-07-01 11:24:46.400 Server       Using dynamic lock allocation.

  • Large page model

2009-07-01 11:24:46.330 Server       Detected 4 CPUs. This is an informational message; no user action is required.

2009-07-01 11:24:46.400 Server       Using large pages for buffer pool.

2009-07-01 11:24:46.400 Server       Using dynamic lock allocation.

  • Conventional model

2009-07-01 11:46:24.990 Server       Detected 4 CPUs. This is an informational message; no user action is required.

2009-07-01 11:46:25.070 Server       Using dynamic lock allocation.

 

First of all memory functions are standard Windows APIs and only two.

  • VirtualAlloc
    • All Models
    • Use MEM_COMMIT for conventional model
    • Use MEM_PHYSICAL for AWE model
    • Use MEM_LARGE_PAGE for Large model

 

  • AllocateUserPhysicalPages 
    • AWE Model
    • It will show AWE allocated pages in MEMORYSTATUS or any DMV reporting memory clerk allocations.

 

To sum up these are the default behaviors:

  • Enterprise Edition with Lock Page in Memory
    • Large pages are used for buffer pool. (c.f. errorlog)
    • Large Page Support is not enabled.
    • AllocatePhysicalPages is used

 

  • Standard Edition
    • Large Pages are not used for buffer pool.
    • Large Page Support is not enabled
    • AllocateUserPhysicalPages is not used

 

 

  • Sp_configure ‘awe enabled’ option
    • Is simply ignored on 64 bit editions

In conclusion if you use 64 bit Enterprise Edition with locked pages enabled the engine does use one of the same underlying APIs (AllocateUserPhysicalPages) as is used by AWE in order to acquire the locked pages. Consequently, if you look at DBCC MEMORYSTATUS or memory clerk output (sys.dm_os_memory_clerks.awe_allocated_kb) it will still show a non zero AWE allocated number.

There have been changes brought to Standard Edition I will expose in a following post.

 

Lionel Pénuchot – Senior SQL Premier Field Engineer

Posted by sqlpfebl | 0 Comments
Filed under:

Side effects of storing an Analysis Services partition data in a non-default location

In 2005 and 2008, SQL Server Analysis Services allows users to store partition data in a non-default location. That is something we could not do in 2000 version. To store a partition data in a non-default location means that you can have the partition storage location set to some folder other than the default data folder (see the screen shot below). Some users choose to use the feature because it can off load some IO to different drives. It helps balancing IO activities. Others choose to use it simply because they don’t have enough disk space in the default location.

clip_image002

Indeed, storage location is a wonderful feature that provides us flexibility.  However, in order to make good use of this feature option, you  need to be aware its side effects:

1. Synchronization: In normally scenario, synchronization can perform incremental synch between 2 servers. That's means only the data that is affected will be synchronized. However, if your partitions storage are at non-default locations. Every time you sync, it will sync all the partitions data regardless any changes at all. This known behavior can have negative performance impact, especially for some very large databases.

2. Backup/Restore: if you have non-default location partitions within a database backup, you will need to restore the database to another non-default location. You can't restore its partitions back to the default data file folder. The only way to move the database back to the  default file location is to redeploy the project without setting the storage location in each partition properties. And then re-process the whole database to get back all the data files.

If you have accumulated couple hundred GB of partition data, reprocessing is not always that easy. Knowing the side effects ahead, it can help you make a better architecture decision.

C S John Lam | Premier Field Engineer - SQL Analysis Services

Posted by sqlpfebl | 0 Comments
Filed under:

How to use Partition Processing Destination Task to pump data into a SSAS partition via SSIS

To load and process the data into Analysis Services, SQL Integration Services provides two data flow components: Partition Processing Destination and Dimension Processing Destination. However, I found very few users know how to use them or use them correctly.  Some said the features look nice but can’t find any good example to follow. That makes many users stay away from using the features.

This blog, I am to going to write an example to show how to use Partition Processing Destination. In general, the same concept can be applied in Dimension Processing Destination as well. The whole example is based on the sample data set Adventure Works DW 2008.

Our example scenario:  To pump the data from a relational table directly to partition [Reseller_Orders_2001] under [Reseller Orders] measure group in Adventure Works DW

The source SQL (is taken from the partition properties):

    SELECT [dbo].[FactResellerSales].[ProductKey],[dbo].[FactResellerSales].[OrderDateKey],

[dbo].[FactResellerSales].[DueDateKey],[dbo].[FactResellerSales].[ShipDateKey],

[dbo].[FactResellerSales].[ResellerKey], [dbo].[FactResellerSales].[EmployeeKey],

[dbo].[FactResellerSales].[PromotionKey],[dbo].[FactResellerSales].[CurrencyKey],

[dbo].[FactResellerSales].[SalesTerritoryKey],[dbo].[FactResellerSales].[SalesOrderNumber],

[dbo].[FactResellerSales].[SalesOrderLineNumber],[dbo].[FactResellerSales].[RevisionNumber],

[dbo].[FactResellerSales].[OrderQuantity],[dbo].[FactResellerSales].[UnitPrice],

[dbo].[FactResellerSales].[ExtendedAmount],[dbo].[FactResellerSales].[UnitPriceDiscountPct],

[dbo].[FactResellerSales].[DiscountAmount],[dbo].[FactResellerSales].[ProductStandardCost],

[dbo].[FactResellerSales].[TotalProductCost],[dbo].[FactResellerSales].[SalesAmount],

[dbo].[FactResellerSales].[TaxAmt],[dbo].[FactResellerSales].[Freight],

[dbo].[FactResellerSales].[CarrierTrackingNumber],[dbo].[FactResellerSales].[CustomerPONumber]

FROM [dbo].[FactResellerSales]

WHERE OrderDateKey <= '20011231'

 

Steps:

1. Draft a “Data Flow Task” on to the Control Flow tab.

2. Double click the Data Flow Task to open Data Flow tab.

3. Create a OLE DB Source pointing to your Adventure Works DW 2008 relational database

4. Select “SQL Command” for access method

5. Copy and paste the SQL statement above into the dialog box

6. Close it

7. Drag the “Partition Processing Destination” Task over

8. Connect the data flow task from OLE DB source to Partition Processing task.

9. Create a data source within the destination task to point to your SSAS server.

10. Select the partition [Reseller_Orders_2001] under [Reseller Orders] measure group.

11. Select one of the processing methods. In this example, we pick Full processing

12. Map the input columns with the partition attributes (see screen shot #1)

13. At the end, we will find we don’t have any columns that can map to Geograph.Geography Key and Reseller Order Count. If we leave them unmapped (<ignore>, any attempt to execute the task, the following error is seen:

Error "Pipeline processing can only reference a single table in the data source view"

The error message itself is indeed little unclear and little misleading.

The point is: we can’t leave any attributes unmapped even the UI allows us to select <ignore> in the field but it does not mean the columns are ok to ignore.

Screen shot #1

clip_image001[10]

Then, why we don’t have a column for Geography.Geography Key and Reseller Order Count.  It is because is Geography is a referenced dimension. It does not link to the fact table directly. Reseller Order Count is a distinct count measure base on SalesOrderNumber but the column is used already in mapping to Reseller Sales Order Details.Reseller Sales Order.Key(0). The UI does not allow mapping a single column to two different attributes.

We need to do something here to work around the limitations:

1. For referenced dimension key “Geography.Geography Key”, we extend the query input include the relevant column. PLUS uncheck the materialize checkbox of the referenced dimensions, it will work around this limitation (see screen shot #2 below)

2. For Reseller Order Count, we select the same column twice but give them a different column alias.

Remark: The "materialize" is a performance feature targeting for reference dimension.  Unchecking the materialize checkbox may impact the cube query performance. You can learn more about this feature in the section of "Reference relationships" in SQL Server 2005 Analysis Services Performance Guide.

 

Screen shot #2

clip_image001[12]

We rewrite the SQL statement base on the workaround above into following: 

 

SELECT

DimGeography.GeographyKey,

[dbo].[FactResellerSales].[SalesOrderNumber] as resellerordercount,

[dbo].[FactResellerSales].[ProductKey],[dbo].[FactResellerSales].[OrderDateKey],

[dbo].[FactResellerSales].[DueDateKey],[dbo].[FactResellerSales].[ShipDateKey],

[dbo].[FactResellerSales].[ResellerKey], dbo].[FactResellerSales].[EmployeeKey],

[dbo].[FactResellerSales].[PromotionKey],[dbo].[FactResellerSales].[CurrencyKey], [dbo].[FactResellerSales].[SalesTerritoryKey],

[dbo].[FactResellerSales].[SalesOrderNumber],     [dbo].[FactResellerSales].[SalesOrderLineNumber],

[dbo].[FactResellerSales].[RevisionNumber],      [dbo].[FactResellerSales].[OrderQuantity],[dbo].[FactResellerSales].[UnitPrice],      [dbo].[FactResellerSales].[ExtendedAmount],

[dbo].[FactResellerSales].[UnitPriceDiscountPct],      [dbo].[FactResellerSales].[DiscountAmount],

[dbo].[FactResellerSales].[ProductStandardCost],      [dbo].[FactResellerSales].[TotalProductCost],[dbo].[FactResellerSales].[SalesAmount],

[dbo].[FactResellerSales].[TaxAmt],[dbo].[FactResellerSales].[Freight],      [dbo].[FactResellerSales].[CarrierTrackingNumber],[dbo].[FactResellerSales].[CustomerPONumber]

FROM [dbo].[FactResellerSales], DimReseller, DimGeography

WHERE

       FactResellerSales.ResellerKey=DimReseller.ResellerKey and        DimReseller.GeographyKey=DimGeography.GeographyKey and

      OrderDateKey <= '20011231'

 

We replace the query in step #5 with this new SQL and then remap all the columns again (see screen shot #3)

Screen shot #3

clip_image003

Now, the task will pump the data directly from the source into the target partition.

C S John Lam | Premier Field Engineer - SQL Analysis Services

Posted by sqlpfebl | 3 Comments
Filed under:

SQL High CPU scenario troubleshooting using sys.dm_exec_query_stats and RING_BUFFER_SCHEDULER_MONITOR ring buffer in sys.dm_os_ring_buffers

I ran into a scenario a while ago that I thought I would share, where we were troubleshooting high CPU on  SQL Server.  The sporadic High CPU ( 90+ % )  pretty much brought the box down to its knees for a period of 5-10 minutes minutes or so, and then cleared itself up. We determined from performance monitor that SQL was the one consuming all of that CPU. Initially I tried to draw a correlation to times that it was happening, but wasn’t able to as the times were random. My initial thought was to capture a profiler and get the culprit and  tune the query in question. In this case though even running a profiler capturing batch level events was pretty expensive as it was a very chatty server and  we had about 350 MB trace files created every minute. Given that we did not know the timeframe the problem would occur, and given the problem period was pretty small running a profiler continually to try to capture an occurrence of the problem was not an option.

We then opted to try the Performance Dashboard for SQL 2005 which is an awesome tool (downloadable from the SQL 2005 Feature Pack), but the primary problem we were facing was that we did not know when the problem would occur, and when we did, by the time we got on any tools the problem would clear itself out. It was random and short lived.

The other thing we knew was that there weren't any long running statements per say as we tried to run some filters to capture statements that took longer than some amount of CPU time and did not come up with much, so this was more the effect of multiple executions of a particular statement during that timeframe than a single execution of a statement.

I reverted to the DMV’s in SQL 2005 to help collect the necessary data and was able to solve the problem just using the DMV's itself . Given that I did not know when the problem could occur and I did not want to capture the data all the time,  I had a script to conditionally capture data based on the CPU information in the Scheduler Monitor ring buffer.  I used the sys.dm_os_ring_buffers DMV and specifically the records of type 'RING_BUFFER_SCHEDULER_MONITOR' which  are captured once every 60 seconds and give you the CPU utilization from the SQL process perspective besides other information. Using the script below, I was able to see if the last 2 ring buffer records had CPU time greater than a particular threshold and if so, I captured the top statements and plans in a table and database that I created. Capturing the DMV information such as this was much less impactful to the system and could be kept on for hours/days unlike profiler in this case.

 

The script below should work for both SQL 2005 and SQL 2008.

-- This script runs in an infinite loop every 30 seconds ( can be invoked with SQLCMD or management Studio)

-- @SQLCPUThreshold_Percent specifies what CPU threshold we are monitoring

-- If for 2 successive Ring buffer snapshots ( 2 minutes), the CPU is above the threshold we specified,

-- We then collect the top 25 Statements along with their plans in the Database and table we created.

-- Database/Table creation script not included.

Declare @SQLCPUThrehold_Percent int

Set @SQLCPUThrehold_Percent = 75

WHILE (1 = 1)

BEGIN

      SELECT TOP 2

      CONVERT (varchar(30), getdate(), 126) AS runtime,

                record.value('(Record/@id)[1]', 'int') AS record_id,

                record.value('(Record/SchedulerMonitorEvent/SystemHealth/SystemIdle)[1]', 'int') AS system_idle_cpu,

                record.value('(Record/SchedulerMonitorEvent/SystemHealth/ProcessUtilization)[1]', 'int') AS sql_cpu_utilization

      into #tempCPU

      FROM sys.dm_os_sys_info inf CROSS JOIN (

      SELECT timestamp, CONVERT (xml, record) AS record

      FROM sys.dm_os_ring_buffers

      WHERE ring_buffer_type = 'RING_BUFFER_SCHEDULER_MONITOR'

      AND record LIKE '%<SystemHealth>%') AS t

      ORDER BY record.value('(Record/@id)[1]', 'int') DESC

 

-- If the IF statement is satisfied, last 2 Ring buffer records had CPU > threshold so we capture the plans

if ( (select count(*) from #tempCPU) = (select count(*) from #tempCPU where sql_cpu_utilization >@SQLCPUThrehold_Percent))

begin

-- insert top 25 Statements and plans by CPU into the table

 

      insert into Tbl_troubleshootingPlans

      SELECT TOP 25   getdate() as runtime,

                        qs.Execution_count as Executions,

                        qs.total_worker_time as TotalCPU,

                        qs.total_physical_reads as PhysicalReads,

                        qs.total_logical_reads as LogicalReads,

                        qs.total_logical_writes as LogicalWrites,

                        qs.total_elapsed_time as Duration,

                        qs.total_worker_time/qs.execution_count as [Avg CPU Time],

                                    substring (qt.text,qs.statement_start_offset/2,(case when qs.statement_end_offset = -1 then len (convert (nvarchar(max), qt.text)) * 2

                                    else qs.statement_end_offset end - qs.statement_start_offset)/2) as query_text,

                        qt.dbid as DBID,

                        qt.objectid as OBJECT_ID,

                                    cast ( query_plan as xml) as XMLPlan

      FROM sys.dm_exec_query_stats qs

      cross apply sys.dm_exec_sql_text(qs.sql_handle) as qt

      CROSS APPLY sys.dm_exec_query_plan(plan_handle)

      ORDER BY TotalCPU DESC

 

end

drop table #tempCPU

waitfor delay '0:00:30'

end

go

 

Once we got that output data from the script in question , we were able to figure out the TOP CPU statement in the problem time with a query such as  the one below substituting the correct times. The time consumed by this one statement was significantly larger than the others during that period and had to be our suspect as during “good” run times, that statement never made it to the top CPU list.

select runtime,Executions,TotalCPU,LogicalReads,Duration,[Avg CPU Time], query_text from tbl_TroubleshootingPlans

where runtime between '2008-11-03 06:00:00.530' and '2008-11-03 06:06:00.530'

order by TotalCPU desc

Note the times in sys.dm_exec_query_stats are generally in Microseconds ( http://msdn.microsoft.com/en-us/library/ms189741(SQL.90).aspx )

If you notice here, the average CPU time was 165 ms in a "bad" run, and on a good run was more like 5 ms so individually you would never see the statement on the radar unless you look at cumulative executions.

Executions

TotalCPU

LogicalReads

Duration

Avg CPU Time ( Microseconds)

DBID

ObjectID

query_text

4333

719013385

661450233

10485137538

165938

6

750625717

select s.SipNotifyCseq     as SipNotifyCseq,   ………..

And then were able to also get the Plan in question from the XML Plan that we saved in our table ( which in 2005 can be saved as a .sqlplan file and opened with Management Studio to get the graphical showplan).

The issue ended up being a Parameter sniffing issue (Parameter Sniffing: http://blogs.msdn.com/queryoptteam/archive/2006/03/31/565991.aspx )

I was able to reproduce the good and bad plan without even having the full database backup just using the Clone database ( http://blogs.msdn.com/psssql/archive/2009/07/08/attach-of-the-clone-databases.aspx ). Also from the XML Plan, we were able to get  the list of compile parameters  and from the statistics we could see that there was a data skew in the table.

Bad Plan

Now this particular plan was compiled for a particular set of parameters and was the right plan for those parameters ( aka @Authz = NULL)  . However due to a data skew that existed, the highlighted index scan was detrimental to performance if executed with any a Non-NULL @AuthZ parameter. In fact the join order in the plan changed as well.

 

|--Compute Scalar(DEFINE:([Expr1009]=(1), [Expr1010]=NULL, [Expr1011]=datediff(second,[@CurrentTime],[RTC].[dbo].[BatchSubParent].[ExpiresAt]), [Expr1012]=NULL, [Expr1013]=(1)))
       |--Sort(ORDER BY:([f].[Fqdn] ASC))
            |--Parallelism(Gather Streams)
                 |--Nested Loops(Inner Join, OUTER REFERENCES:([e].[FrontEndId]))
                      |--Nested Loops(Inner Join, OUTER REFERENCES:([RTC].[dbo].[BatchSubParent].[SubscriberId], [RTC].[dbo].[BatchSubParent].[Epid]))
                      |    |--Nested Loops(Inner Join, OUTER REFERENCES:([RTC].[dbo].[BatchSubParent].[BatchId]))
                      |    |    |--Filter(WHERE:([RTC].[dbo].[BatchSubParent].[ExpiresAt]>[@CurrentTime])

                      |    |    |    |--Nested Loops(Inner Join, OUTER REFERENCES:([RTC].[dbo].[BatchSubParent].[BatchId])
                      |    |    |         |--
Index Scan(OBJECT:([RTC].[dbo].[BatchSubParent].[UQ_BatchSubParent]) WHERE:([RTC].[dbo].[ BatchSubParent].[EventId] as [sp].[EventId] = @EventID)
                      |    |    |         |--Clustered Index Seek(OBJECT:([RTC].[dbo].[BatchSubParent].[PK_BatchSubParent]), SEEK:([RTC].[dbo].[BatchSubParent].[BatchId]=[RTC].[dbo].[BatchSubParent].[BatchId]) LOOKUP ORDERED FORWARD)

                     |    |    |--Clustered Index Seek(OBJECT:([RTC].[dbo].[BatchSubChild].[PK_BatchSubChild] AS [sc]), SEEK:([sc].[SubscribedId]=(24915) AND [sc].[BatchId]=[RTC].[dbo].[BatchSubParent].[BatchId]),  WHERE:([@_AuthZ] IS NULL OR [RTC].[dbo].[BatchSubChild].[AuthZ] as [sc].[AuthZ]=[@_AuthZ) ORDERED FORWARD)
                      |    |--Clustered Index Seek(OBJECT:([RTC].[dbo].[Endpoint].[PK_Endpoint] AS [e]), SEEK:([e].[OwnerId]=[RTC].[dbo].[BatchSubParent].[SubscriberId] AND [e].[Epid]=[RTC].[dbo].[BatchSubParent].[Epid]),  WHERE:([RTC].[dbo].[Endpoint].[ExpiresAt] as [e].[ExpiresAt]>[@InvalidEndpointTime]) ORDERED FORWARD)
                      |--Clustered Index Seek(OBJECT:([RTC].[dbo].[FrontEnd].[PK_FrontEnd] AS [f]), SEEK:([f].[FrontEndId]=[RTC].[dbo].[Endpoint].[FrontEndId] as [e].[FrontEndId]) ORDERED FORWARD)

 

Bad Plan Compile Parameters:

<ParameterList>

             <ColumnReference Column="@_AuthZ" ParameterCompiledValue="NULL" />

              <ColumnReference Column="@_EventId" ParameterCompiledValue="(1)" />

              <ColumnReference Column="@_SubscribedId" ParameterCompiledValue="(14467)" />

</ParameterList>

Good Plan

We also captured a “good” plan when things were fine for this statement in question and as you see the plan is different and we do a seek instead of a scan. Also examining the parameters that the plan was compiled with you clearly see that @Authz is not a NULL.

|--Sort(ORDER BY:([f].[Fqdn] ASC))
            |--Nested Loops(Inner Join, OUTER REFERENCES:([e].[FrontEndId]))
                 |--Nested Loops(Inner Join, OUTER REFERENCES:([sp].[SubscriberId], [sp].[Epid]))
                 |    |--Nested Loops(Inner Join, OUTER REFERENCES:([sc].[BatchId]))
                 |    |    |--Clustered Index Seek(OBJECT:([RTC].[dbo].[BatchSubChild].[PK_BatchSubChild] AS [sc]), SEEK:([sc].[SubscribedId]=(24915)),  WHERE:([RTC].[dbo].[BatchSubChild].[AuthZ] as [sc].[AuthZ]='A') ORDERED FORWARD)
                 |    |    |--Clustered Index Seek(OBJECT:([RTC].[dbo].[BatchSubParent].[PK_BatchSubParent] AS [sp]), SEEK:([sp].[BatchId]=[RTC].[dbo].[BatchSubChild].[BatchId] as [sc].[BatchId]),
  WHERE:([RTC].[dbo].[BatchSubParent].[ExpiresAt] as [sp].[ExpiresAt]>[@CurrentTime] AND [RTC].[dbo].[BatchSubParent].[EventId] as [sp].[EventId]=(0)) ORDERED FORWARD)
            
   |    |--Clustered Index Seek(OBJECT:([RTC].[dbo].[Endpoint].[PK_Endpoint] AS [e]), SEEK:([e].[OwnerId]=[RTC].[dbo].[BatchSubParent].[SubscriberId] as [sp].[SubscriberId] AND [e].[Epid]=[RTC].[dbo].[BatchSubParent].[Epid] as [sp].[Epid]),  WHERE:([RTC].[dbo].[Endpoint].[ExpiresAt] as [e].[ExpiresAt]>[@InvalidEndpointTime]) ORDERED FORWARD)
                 |--Clustered Index Seek(OBJECT:([RTC].[dbo].[FrontEnd].[PK_FrontEnd] AS [f]), SEEK:([f].[FrontEndId]=[RTC].[dbo].[Endpoint].[FrontEndId] as [e].[FrontEndId]) ORDERED FORWARD)

 

Good Plan Compile Parameters:

<ParameterList>

              <ColumnReference Column="@_AuthZ" ParameterCompiledValue="'A'" />

              <ColumnReference Column="@_EventId" ParameterCompiledValue="(0)" />

              <ColumnReference Column="@_SubscribedId" ParameterCompiledValue="(24915)" />

</ParameterList>

    

The general solutions to parameter sniffing class of problems include

- Option Recompile  with or without an additional hint specifying typical parameters for compilation

- Dynamically executing the statement with Exec or sp_executesql

- Having Nested Sub procedures based on values of the parameters and/or logic in the proc

- Plan Guides

Plan Guides to the rescue

Given that the code of the procedure could not change as it was an application that did not support any schema changes, we had to find a way external to the procedure and try to get it to compile with its typical parameters. SQL 2005  and 2008  have a feature called Plan Guides where we can Freeze the plan of this statement to be the “good” plan that is based on hinting typical parameter values at compile time.

http://msdn2.microsoft.com/en-us/library/ms190417.aspx

http://msdn2.microsoft.com/en-us/library/ms189296.aspx

-- Create the Plan Guide

sp_create_plan_guide

@name = N'MyStoredProcName_Guide',

@stmt = N'select s.SipNotifyCseq     as SipNotifyCseq,

               s.SipCallId         as SipCallId,

……..', -- Statement within the proc causing high CPU

@type = N'OBJECT',

@module_or_batch = N'dbo.MyStoredProcName',

@params = NULL,

@hints = N'OPTION (OPTIMIZE FOR (@_SubscribedId=24915,@_EventId=0,@_AuthZ=''A''))'

Of course longer term that stored procedure in question would have to be changed to account for typical parameters, but in the interim, we got the system back on its feet!

 

-Denzil Ribeiro, SQL Dedicated Premier Field Engineer

Posted by sqlpfebl | 0 Comments
Filed under:

Sum of a SQL Analysis Services calculated measure within a Reporting Services report gives #Error

Summing a SQL Analysis Services calculated measure result within a Reporting Services report gives #Error but the same calculated measure output can be summed within Analysis Services perfectly fine.

Environment: SQL Server 2008 Analysis Services and Reporting Services + SP1, Report builder 2.0 sp1. This should be applicable to all SQL 2008 and 2005  Reporting Services versions prior as well.

 

If you look at  a report builder report, an expression of " =Sum(Fields!highsales.Value)" on a Analysis Services calculated member gives #Error. The zeros in column "highsales" are numeric values.

The report (see screen shot #1 below) is generated base on the following MDX:

 

with

member measures.[highsales] as

iif ([Measures].[Internet Sales Amount]> 20000000,

measures.[internet sales amount], 0)

member measures.[highsales2] as

iif ([Measures].[Internet Sales Amount]> 20000000,

measures.[internet sales amount], null)

select

{measures.highsales,measures.highsales2} on 0,

{[Product].[Category].[Category], [Product].[Category].[ssas total]} on 1

from [Adventure Works]

 

Screen shot #1

clip_image001

If we calculate the total within Analysis Services using MDX, it gives the correct result (screen shot #2).

Screen shot #2

clip_image002

Why does Reporting Services report return an error?

To find out more about the #Error, we can try the same report in BIDS and take a look at the warnings shown in the "Error List" pane.  It should contain a detailed error message indicating why the #Error occurred.  This information, unfortunately, is not currently surfaced in Report Builder 2.0. 

The warning we can see from BIDS

Warning               1              [rsAggregateOfMixedDataTypes] The Value expression for the textrun 'Textbox8.Paragraphs[0].TextRuns[0]' uses an aggregate function on data of varying data types.  Aggregate functions other than First, Last, Previous, Count, and CountDistinct can only aggregate data of a single data type.                C:\sumZeroGivesError.rdl               

The error message indicates Reporting Services is receiving different data types within the column "highsales" and Reporting Services cannot sum over incompatible types.

To see what are the actual data types seen by Reporting Services, we can add a new table to the report bound to the data set. In the detail group scope, put the following expression in a TextBox value: =Fields!highsales.Value.GetType().Name

The output (Screen shot #4) shows the data type for the 0 values here are Int16 (or DOUBLE if we switch the 0 to 0.0).

Screen shot #4

clip_image003

Although we tend to think 0 and 28318144.6507 are "sum-able", the sum function in Reporting Services can only sum single type. We have two different data types.  That's why an error is raised

Ref: See the footnote on the Type column of the Aggregate Functions table in the Report Definition Language Specification (http://www.microsoft.com/australia/servers/sql/technologies/reporting/rdlspec.mspx):

For all aggregates other than First, Last and Count, the data type of the aggregated expression is expected to be fixed.  If values (other than null) are encountered of multiple data types, it is an error.

Solution Suggestion

In order to fix the problem, we can convert the field to a particular type before passing the value to sum.  For example, if we have all the numbers in integer, we can use "=Sum(CInt(Fields!Foo.Value))".  In the example here, we have the number in decimal, we use "=Sum(CDec(Fields!highsales.Value))" to resolve the issue (Screen shot #5).

Screens shot #5

clip_image004

 

C S John Lam | Premier Field Engineer - SQL Analysis Services

What needs to be done to be able to edit DTS 2000 packages on a Windows 2008 or Vista Machine ?

This question has often been asked by customers who have a lot of DTS packages and want to continue running these packages on SQL 2008 servers running on Windows 2008 or Vista machines while they work on redesigning the packages to use the new features of SSIS in SQL 2008 .

The steps in Books online work if the Operating System is Windows 2003 or Windows XP . For Windows 2008 or Vista , the following steps will need to be used to be able to successfully open and edit DTS 2000 packages .

1. Download the X86 Package for Microsoft SQL Server 2005 Backward Compatibility Components.

2. Download the X64 Package for Microsoft SQL Server 2005 Backward Compatibility Components.(This step is only required if you wish to edit DTS 2000 package on a X86 machine (Windows 2008 or Vista)

3. Download the X86 package for Microsoft SQL Server 2000 DTS Designer Components

4. Logon to the server where you would like to be able to edit the DTS 2000 package and run the package downloaded from Step2 (assuming that this will be Windows 2008 with SQL 2008 in 64 bit . Make sure the X86 package is in the same folder as the X64 package . Setup needs to copy files from the X86 package)  IF the machine already has the BC components for SQL 2005 installed ,setup will prompt you if you wish to remove or repair , choose to repair. This step is not required if the machine is X86

5. Now run the package from step 3 . IF there is a version already installed on the machine , you will be prompted to upgrade or cancel , choose to upgrade.

6. Depending the Architecture of the OS , choose from a) or b)

a) For X86 Machines , do the following

1. Open a CMD prompt with Admin privileges , and type "Set LANGID =1033" . ( Depending on the locale , you might need to change the value to the ID for the local language , 1033 is for US English)

2. Execute the following commands

Copy %Program Files%\Microsoft SQL Server\80\Tools\Binn\semsfc.dll  %Program Files%\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE\semsfc.dll
copy %Program Files%\Microsoft SQL Server\80\Tools\Binn\sqlgui.dll  %Program Files%\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE\sqlgui.dll
copy %Program Files%\Microsoft SQL Server\80\Tools\Binn\sqlsvc.dll  %Program Files%\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE\sqlsvc.dll
copy %Program Files%\Microsoft SQL Server\80\Tools\Binn\Resources\%lang_id%\semsfc.rll  %Program Files%\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE\Resources\%LANGID%\semsfc.rll
copy %Program Files%\Microsoft SQL Server\80\Tools\Binn\Resources\%lang_id%\sqlgui.rll  %Program Files%\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE\Resources\%LANGID%\sqlgui.rll
copy %Program Files%\Microsoft SQL Server\80\Tools\Binn\Resources\%lang_id%\sqlsvc.rll  %Program Files%\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE\Resources\%LANGID%\sqlsvc.rll
copy %Program Files%\Microsoft SQL Server\80\Tools\Binn\semsfc.dll  %Program Files%\Microsoft Visual Studio 9.0\Common7\IDE\semsfc.dll
copy %Program Files%\Microsoft SQL Server\80\Tools\Binn\sqlgui.dll  %Program Files%\Microsoft Visual Studio 9.0\Common7\IDE\sqlgui.dll
copy %Program Files%\Microsoft SQL Server\80\Tools\Binn\sqlsvc.dll  %Program Files%\Microsoft Visual Studio 9.0\Common7\IDE\sqlsvc.dll
copy %Program Files%\Microsoft SQL Server\80\Tools\Binn\Resources\%LANGID%\semsfc.rll  %Program Files%\Microsoft Visual Studio 9.0\Common7\IDE\Resources\%LANGID%\semsfc.rll
copy %Program Files%\Microsoft SQL Server\80\Tools\Binn\Resources\%LANGID %\sqlgui.rll  %Program Files%\Microsoft Visual Studio 9.0\Common7\IDE\Resources\%LANGID%\sqlgui.rll
copy %Program Files%\Microsoft SQL Server\80\Tools\Binn\Resources\%LANGID%\sqlsvc.rll  %Program Files%\Microsoft Visual Studio 9.0\Common7\IDE\Resources\%LANGID%\sqlsvc.rll

b) For X64 Machines , replace %Program Files% from the above commands with %ProgramFiles(x86)% . Rest remains the same.

Now launch SSMS.exe and attempt to open a .dts file. Similarly, from BIDS, see if you can embed a DTS 2000 package in a Execute DTS 2000 package task. Make sure to change the setting for Run64BitRuntime to False from the Solution properties. This is because BIDS is 32 bit and the default on X64 machines is to run the debugger in X64 bit mode. DTS 2000 runtime exists only in X86 mode.

SQL 2000 has not been tested on Windows 2008 or Vista and so has limited or no support on these versions of the Operating System. The DTS 2000 Designer is a SQL2000 component, so the support for this component is tied to the support policy for SQL 2000.

Prashant Bhat, SQL Server Dedicated Support Engineer

Posted by sqlpfebl | 0 Comments
Filed under:

The Case of the Phantom Spid: Troubleshooting and Resolving an Orphaned DTC Transaction

Several weeks ago one of our customers encountered an issue in which an index maintenance job that had always completed successfully, began failing with each nightly run. While troubleshooting the issue, the customer ran the DBCC OPENTRAN command against the affected database and discovered that another session, which had been running for several days, might be blocking the index maintenance job and causing it to fail. After repeated attempts at killing the long running session failed, the customer contacted our team and asked for our assistance in resolving the issue.

The first thing we asked the customer to do was to run the DBCC OPENTRAN command again against the affected database to confirm that the long running session was still active. As expected, the long running session (spid 178) was still active and had a transaction start time of several days in the past that coincided with the time the index maintenance job began failing.

We then asked the customer to run a query against the sys.dm_exec_sessions DMV to get the login time for spid 178. Surprisingly, the login time for spid 178 was only seconds in the past, and the login time was changing with each run of the sys.dm_exec_sessions query. This made it clear that the spid 178 returned by the DBCC OPENTRAN command was not the same spid 178 as the ones returned by the sys.dm_exec_sessions query.

So what was the origin of this phantom spid? To answer this question, we had to find a record of phantom spid 178 or its associated transaction somewhere on the server. We started our search by querying the sys.dm_tran_active_transactions and sys.dm_tran_session_transactions DMVs, but didn’t have any luck finding records related to phantom spid 178. But when we queried the sys.dm_tran_database_transactions DMV, we found a record in the affected database with a database_transaction_begin_time that matched the start time of phantom spid 178’s transaction exactly. Feeling confident that we had found phantom spid 178’s transaction record, we saved the transaction_id of the record.

Next, we queried the sys.syslockinfo compatibility view for any records with a req_transactionid matching the transaction id that we saved from the previous query. This paid off as the query returned several records matching the saved transaction id. But the req_spid value of these records was -2 and not 178! Was this the wrong transaction? No. SQL Server uses a req_spid value of -2 to designate an orphaned DTC transaction.

So how could we terminate a spid that didn’t exist? The answer is we couldn’t. But what we could do was terminate the unit of work (UOW) associated with the orphaned transaction using the KILL command. Luckily a DTC transaction’s UOW is found in the req_transactionUOW column of the sys.syslockinfo compatibility view. And so our final step in resolving this customer issue was to execute the KILL command against the UOW that we retrieved from the sys.syslockinfo records associated with the offending transaction id. As a result, the phantom spid released the objects it had locked and was never heard from again.

Chris Miller, SQL Server Dedicated Support Engineer

Posted by sqlpfebl | 2 Comments
Filed under:

Possible performance implications when using string parameters in Reporting Services

In Reporting Services String Parameters are of type Unicode. This could have unwanted side effects if comparing with a varchar ( or non-unicode data type) on the data source due to conversions that could occur from the non-Unicode column to the Unicode column as the resulting query plans may use scan’s instead of seeks. This is applicable to both SQL 2005 and SQL 2008.

 

 Let us consider the following table which the report is based on

 

Create table TempProducts

(

  ProductID int Primary Key,

  [Name] varchar(100),

  ProductNum varchar(20),

  StockLevel int,

  ListPrice float,

)

Go

Create index IndProductName on TempProducts([Name])

go

 

 Let’s consider a Dataset with a query below, where @Products is a String Multi-value parameter in Reporting Services.

 

select Name from TempProducts

where Name in (@Products)

 

 When the report is run, and say we select 2 values, these are passed as Nvarchar back to the Engine as Reporting Services treats the string parameters are Unicode. If you run a profiler on the backend, you will see the query below passing Nvarchar values in the in clause.

 

 select Name from TempProducts

where Name in (N'Half-Finger Gloves, M',N'Full-Finger Gloves, M')

 

 If you look at the plan, it is doing an Index Scan though we have an index on the Name column

 

StmtText

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

  |--Index Scan(OBJECT:([AdventureWorks].[dbo].[TempProducts].[IndProductName]),  WHERE:(CONVERT_IMPLICIT(nvarchar(100),[AdventureWorks].[dbo].[TempProducts].[Name],0)=N'Full-Finger Gloves, M' OR CONVERT_IMPLICIT(nvarchar(100),[AdventureWorks].[dbo].[TempP

 

 

 The scan is chosen   due to the Data-type precedence  (http://msdn.microsoft.com/en-us/library/ms190309.aspx ) , when doing the comparison between a varchar and an Nvarchar data-type, the varchar has to be converted into Nvarchar ( data type lower on the precedence has to be converted into the data type which is higher on the precedence scale). Due to this conversion, we could get a plan that has a “Scan” rather than seek

 

This is not necessarily a problem for small tables or datasets, but could pose a larger problem for large tables where we opt for a scan instead of a seek.

 

 You have several approaches to work-around this if you deem the time taken by the dataset or query to be your bottleneck. The key is here to ensure before making a change that time to retrieve the data is your problem.

 

 a.      Use a dynamic Dataset

 

Basically what you are doing here in the dataset in reporting services is converting the values to literal values before passing it to the Database and building the query at runtime as an expression.

 ="select b.Prodname,a.* from ProductOrders a inner join testproduct b on a.ProductID = b.id and b.Prodname in ("

& "'" & Join(Parameters!Products.Value,"','") & "'

If you run a profiler, this now gets passed as:

               select Name from TempProducts where Name in ('Half-Finger Gloves, M','Full-Finger Gloves, M')

 

The resultant plan is:

 

StmtText

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 

|--Index Seek(OBJECT:([AdventureWorks].[dbo].[TempProducts].[IndProductName]), SEEK:([AdventureWorks].[dbo].[TempProducts].[Name]='Full-Finger Gloves, M' OR [AdventureWorks].[dbo].[TempProducts].[Name]='Half-Finger Gloves, M') ORDERED FORWARD)

 

 

       b.     Use a Stored procedure

 

In case of a Single value Parameter, you can create a procedure that takes as input a varchar parameter instead of an nvarchar and hence the conversion is done prior to actually running the select statement. In the case of a multi-value parameter you can have a proc take a comma delimited string and then construct the where clause.

 

c.      Convert the underlying data-type: 

 

Of course you won’t convert the underlying column’s data-type just because a single report possibly runs into this problem, rather would convert it to Unicode if that column would merit such a conversion based on the application in question.

 

 

-Denzil Ribeiro, SQL Dedicated Premier Field Engineer

Posted by sqlpfebl | 2 Comments
Filed under:

Enabling/Disabling Report builder 2.0 on SSRS 2008 in SharePoint integrated mode

Report Builder 2.0 on SQL 2008 and ClickOnce

I have got the "how to enable Report builder 2.0 ClickOnce" question more than once and hence thought I would post it.

In order to get Report builder 2.0 Clickonce,you need the New Sharepoint Add-in which was just released after Sp1 ( and need SQL 2008 Sp1) - This is part of the April 2009 Feature Pack:

http://www.microsoft.com/downloads/details.aspx?FamilyID=58edd0e4-255b-4361-bd1e-e530d5aab78f&displaylang=en

  - Install the ReportBuilder Click Once - RB2ClickOnce.msi

  - Install the New Sharepoint Add-in for Reporting Services - rsSharepoint_x64.msi  ( Readme: http://download.microsoft.com/download/7/0/1/701890C9-2990-4AB2-A41B-21F7152A3082/Readme_rsaddin.htm )

- If you go to Central Admin, you can make sure that the Report Builder Launch URL is:

/_vti_bin/ReportBuilder/ReportBuilder_2_0_0_0.application

Another common question I get is on how to disable Report builder in SharePoint integrated mode...

Disabling Report Builder from Sharepoint

a. Disabling Report Builder from the "New" Menu in SharePoint

Go into the library-->Document Settings and see these options and disable the "Report Builder Report" and the "Report Builder Model" as content types.

This will disable the "New Report Builder Report" and the "New Report Builder Model" Items from appearing.

clip_image002

b.   Disabling the Open in ReportBuilder menu option  : - This is done by disabling the EnableReportDesignClientDownload property in Management Studio --> Advanced Properties of the Report Server.

clip_image004

To disable the EnableReportDesignClientDownload  property,  open the Advanced Server Properties page

1.   Start Management Studio and connect to a report server instance.

2.  Right-click the report server node, and select Properties. Click Advanced to open the properties page. See Server Properties (Advanced Page) - Reporting Services for a description of each property.

c. Disabling Report Builder from the Actions Menu ( "Edit in Report Builder" ) when you click on the report  : That is a permission driven action. If the user has permissions to Edit the document in SharePoint, that option will appear.

-Denzil Ribeiro, Sr. SQL Server Dedicated Premier Field Engineer

Posted by sqlpfebl | 0 Comments
Filed under:

Three Usage Scenarios for sys.dm_db_index_operational_stats

There are several dynamic management objects (DMOs) that I use on an ongoing basis in performance tuning scenarios. However, I’ve lately gained an increased appreciation of the sys.dm_db_index_operational_stats DMO.   This 44 column (as of SQL 2008) DMO returns data at a database-object-index-partition granularity and tracks an assortment of allocation, latching, locking and I/O related statistics.  

 

Similar to sys.dm_db_index_physical_stats which you can use to track fragmentation, sys.dm_db_index_operational_stats requires input arguments in order to return a result set – expecting either a numeric value or NULL for database_id, object_id, index_id, and partition_id.  If you designate NULL values for a parameter, it will be treated as “all inclusive” based on the specified scope.  Also note that if you provide an invalid ID, it will be treated like a NULL, again returning results in an “all inclusive” manner based on the specified scope.

 

This blog post describes three usage scenarios where I’ve recently found sys.dm_db_index_operational_stats to have been very helpful.  One key point to keep in mind is that these operational statistics are accumulated since the last SQL Server instance restart, and may not be representative of your workload statistics if the SQL Server instance has not been running for a significant period of time.  So if you are looking to use the following techniques, make sure your representative workloads have been executed since the last restart.

 

Identifying Top Objects Associated with Lock Contention

Let’s say you are troubleshooting a report from the application team on perceived slow-down of an application.  You use the Waits and Queues methodology, and your analysis reveals blocking as your primary bottleneck.  If you have a significant number of database objects, you can use sys.dm_db_index_operational_stats to efficiently identify tables associated with a significant amount of blocking.  Relevant columns from this DMV include row_lock_wait_count, row_lock_wait_in_ms, page_lock_wait_count, and page_lock_wait_in_ms.  (On a side note – you can also identify latching waits through this DMV via the page_latch_wait_count, page_latch_wait_in_ms, page_io_latch_wait_count, and page_io_latch_wait_in_ms columns.)

 

The following query demonstrates identifying the top 3 objects associated with waits on page locks:

 

SELECT      TOP 3

            OBJECT_NAME(o.object_id, o.database_id) object_nm,

            o.index_id,

            partition_number,

            page_lock_wait_count,

            page_lock_wait_in_ms,

            case when mid.database_id is null then 'N' else 'Y' end as missing_index_identified

FROM sys.dm_db_index_operational_stats (db_id(), NULL, NULL, NULL) o

LEFT OUTER JOIN (SELECT DISTINCT database_id, object_id

                         FROM sys.dm_db_missing_index_details) as mid

      ON mid.database_id = o.database_id and mid.object_id = o.object_id

ORDER BY page_lock_wait_count DESC

 

* There is an extra feature added to this query, compliments Denzil Ribeiro, where he suggested adding a lookup against sys.dm_db_missing_index_details to validate if there were any missing indexes identified with the high page lock wait indexes.  This serves as a little extra information that may point you to a means of reducing locking contention.

 

Lock Escalations

You can use sys.dm_db_index_operational_stats to track how many attempts were made to escalate to table locks (index_lock_promotion_attempt_count), as well as how many times escalations actually succeeded (index_lock_promotion_count).  The following query shows the top three objects with the highest number of escalations:

 

SELECT      TOP 3

            OBJECT_NAME(object_id, database_id) object_nm,

            index_id,

            partition_number,

            index_lock_promotion_attempt_count,

            index_lock_promotion_count

FROM sys.dm_db_index_operational_stats

      (db_id(), NULL, NULL, NULL)

ORDER BY index_lock_promotion_count DESC

 

 

Page Split Tracking

Excessive page splitting can have a significant effect on performance.  The following query identifies the top 10 objects involved with page splits (ordering by leaf_allocation_count and referencing both the leaf_allocation_count and nonleaf_allocation_count columns).  The leaf_allocation_count column represents page splits at the leaf and the nonleaf_allocation_count represents splits at the non-leaf levels of an index:

 

SELECT      TOP 10

            OBJECT_NAME(object_id, database_id) object_nm,

            index_id,

            partition_number,

            leaf_allocation_count,

            nonleaf_allocation_count

FROM sys.dm_db_index_operational_stats

      (db_id(), NULL, NULL, NULL)

ORDER BY leaf_allocation_count DESC

 

Summary

There are more usage scenarios to be generated from this DMO; however these are the three areas that I’ve utilized sys.dm_db_index_operational_stats most recently (and with a positive outcome).  It is an excellent way to clearly identify objects involved with specific contention issues, including blocking, lock escalation, and page split issues.

 

One final point related to the overall topic of performance tuning, if you haven’t already done so, check out the “Troubleshooting Performance Problems in SQL Server 2008” white paper, a highly recommended read.

 

Joe Sack, Dedicated Support Engineer - SQL Server

Posted by sqlpfebl | 1 Comments
Filed under:

Chart behavior difference in SQL Reporting Services 2008

There are some changes in behavior in Reporting Services Charts with regards to “Empty Points” between SQL 2005 and SQL 2008  (http://msdn.microsoft.com/en-us/library/dd207051.aspx)

Let us consider the following Dataset:

create table #TestChart

(

  Runtime datetime,

  SensorReading int,

  SensorName varchar(20)

)

insert into #TestChart values('2009-01-30 1:30:00',165,'SENSOR_A')

insert into #TestChart values('2009-01-30 1:31:00',145,'SENSOR_B')

insert into #TestChart values('2009-01-30 5:30:00',135,'SENSOR_A')

insert into #TestChart values('2009-01-30 5:31:00',185,'SENSOR_B')

insert into #TestChart values('2009-01-30 9:30:00',165,'SENSOR_A')

insert into #TestChart values('2009-01-30 9:31:00',115,'SENSOR_B')

insert into #TestChart values('2009-01-30 13:30:00',115,'SENSOR_A')

insert into #TestChart values('2009-01-30 13:31:00',125,'SENSOR_B')

insert into #TestChart values('2009-01-30 17:30:00',155,'SENSOR_A')

insert into #TestChart values('2009-01-30 17:31:00',165,'SENSOR_B')

insert into #TestChart values('2009-01-30 21:30:00',145,'SENSOR_A')

insert into #TestChart values('2009-01-31 1:30:00.130',135,'SENSOR_A')

select * from #TestChart

If we had a Chart in SQL 2005  based on this data it would look like the image below:

SQL 2005 Chart

clip_image001

This was incorrect behavior. To give an analogy, let’s say that you only have data for Mon, Tues, Fri, on one instrument, and you have data for Wed and Thursday on the second instrument.  If you try to plot the days along the X axis,  due to the fact that the chart is going to show 5 days worth of data, each instrument now effectively has 5 days worth of data that could be shown.  The chart builds its own internal data structure to support that view.  If there isn’t data present for a particular point , it is considered as an EmptyPoint.  You simply shouldn’t be drawing a line that goes from Tuesday to Friday for the first instrument because it doesn’t have any data for Wed or Thursday.  For a line chart, a line will be drawn only across 2 or more contiguous points.

For the Dataset above,

Intersection calculated internally:

currentdttm ( X-axis)

1/30/09 1:30 AM

1/30/09 1:31 AM

1/30/09 5:30 AM

1/30/09 5:31 AM

1/30/09 9:30 PM

1/31/09 1:30 AM

             

Sensor_A

EmptyPoint

145

EmptyPoint

185

EmptyPoint

EmptyPoint

Sensor_B

165

EmptyPoint

135

EmptyPoint

145

135

Now the chart cannot plot a line without 2 contiguous values, and the only 2 contiguous values are the ones highlighted above for the same series.

Attached is an RDL that demonstrates my point. With the dataset above the chart would appear as below given that there were only 2 contiguous points it could plot.

SQL 2008 Chart

clip_image002

While it is a different behavior from 2005, the 2005 behavior is the incorrect behavior.  If you look at Excel, Excel will plot the chart the same way as SQL 2008.  SQL 2005 had quite a different behavior as it connected every point for the Series group irrespective if they were contiguous or not. SQL 2008 does expose properties to control what to do on an EmptyPoint which is a property of the Series. If we want to revert to the 2005  behavior, we would have to manipulate the properties of the EmptyPoint

In order to have the behavior that existed in SQL 2005,  you have to change the Color property below with an expression below which basically is assigning the color based on the SeriesGroup.

=IIF(Fields!SensorName.Value="SENSOR_B", "#FCB441","#418CF0")

Also change all the other properties of the EmptyPoint to match those of the series.

By default, empty points are calculated by taking the average of the previous and next data points that contain a value. You can change this so that all empty points are inserted at zero

In the Properties pane, expand the CustomAttributes node.

Set the EmptyPointValue property. To insert empty points at an average of the previous and next data points, select Average. To insert empty points at zero, select Zero.

clip_image003

Resulting Chart will look like SQL 2005 Chart if that is necessary.

clip_image004

-Denzil Ribeiro, Sr. SQL  Dedicated Premier Field Engineer

Posted by sqlpfebl | 0 Comments
Filed under:

Introducing the SQL Server Premier Field Engineer Blog

This is the first post for the SQL Server Premier Field Engineer blog.  This first post won’t be technical; but moving forward you’ll see us post various SQL Server field observations, findings and best practices from our large and diverse Premier Field Engineer team.

I do want to start off by thanking and acknowledging Adam Machanic for prompting the idea of starting this team blog.  It took a few months to get the ball rolling, but it is unlikely that this blog would have been created as soon as it was without his initial inquiry. 

So who are we and what do we do?  Microsoft Premier Field Engineers are responsible for proactively supporting customers on site and remotely.  We provide training and guidance.  We also are deployed to customer sites across the world when critical situations are involved. 

There are two types of Premier Field Engineers (PFEs) – Dedicated and Transactional.  Dedicated PFEs (of which I am one), are assigned directly to customers on a long term basis.  Some of us have a single customer; others have up to four or five.  Dedicated PFEs are sometimes embedded on site with their customers, and others are engaged remotely.  

Transactional PFEs typically work shorter term engagements (anywhere from a few hours to a few weeks) and can be engaged to perform workshops, health checks, risk assessment programs, supportability reviews, and are often engaged in order to respond to critical situations.

This is a shared team blog which will be open to the entire SQL Server Premier Field Engineer team.  You’ll see various authors posting over time, and you’ll get a diverse perspective on interesting problems encountered, prescriptive guidance, and conceptual overviews.  This blog is scoped to SQL Server, but this is in itself is a vast subject area.  Expect the authors to post on the SQL engine, Clustering, Replication, Analysis Services, Reporting Services, and SQL Server Integration Services.

Please feel free to post comments and ask us questions, and we look forward to the ongoing community dialog.

Joe Sack

Premier Field Engineer, Microsoft

Posted by sqlpfebl | 3 Comments
 
Page view tracker