New course: “SQL Server: Transact-SQL Basic Data Retrieval”

Today I learned that the video course I’ve been working on these last two months has been published on Pluralsight:

SQL Server: Transact-SQL Basic Data Retrieval

The general description from Pluralsight of this course is as follows:

“If you need to retrieve data from a SQL Server database then you need to know how to use the SELECT statement. Joe starts this course with the basics of a SELECT statement and its various sub-clauses, and progresses to how to select from multiple data sources in the same statement and a comprehensive section on the functions available to manipulate, aggregate, and convert data during the select operation. More then fifty demos help to give you a thorough understanding of how to perform these essential operations, all using a freely-available demo environment that you’re shown how to set up and configure. This course is perfect for developers who need to query SQL Server databases to retrieve data, from complete beginners through to more experienced developers who can use some of the modules as reference material. The information in the course applies to all versions from SQL Server 2005 onwards.”

As mentioned in the description, this course is for SQL Server 2005 and up – and I do cover new applicable functionality provided in SQL Server 2012 as well.

This was a fun course to work on because normally we teach in the 300 to 400 level. This particular course gave me an opportunity to cover the fundamentals and I’m definitely looking forward to recording more.

Memory Grant Execution Plan Statistics

SQL Server 2008 execution plans include an optional MemoryGrant attribute in the QueryPlan element – but SQL Server 2012 significantly expands on memory grant related statistics with the new MemoryGrantInfo element and associated attributes.

Here is an example of MemoryGrantInfo from an actual SQL Server 2012 execution plan:

<MemoryGrantInfo SerialRequiredMemory="5632" SerialDesiredMemory="11016" RequiredMemory="47368" DesiredMemory="52808" RequestedMemory="52808" GrantWaitTime="0" GrantedMemory="52808" MaxUsedMemory="4312" />

The unit of measurement is in KB – and in the below perfmon screenshot you can see that the “52808” value in the GrantedMemory attribute matches the Granted Workspace Memory (KB) performance monitor counter:

snaghtmla4ae6cb thumb Memory Grant Execution Plan Statistics

Examining the latest Showplan Schema (last updated March 2012) under the MemoryGrantType section, you’ll find a documentation element which states the following:

Provide memory grant estimate as well as actual runtime memory grant information. Serial required/desired memory attributes are estimated during query compile time for serial execution. The rest of attributes provide estimates and counters for query execution time considering actual degree of parallelism.”

To test the serial plan related memory stats, I re-ran my original query using MAXDOP 1 and got the following results in my actual execution plan:

<MemoryGrantInfo SerialRequiredMemory="1536" SerialDesiredMemory="1600" RequiredMemory="1536" DesiredMemory="1600" RequestedMemory="1600" GrantWaitTime="0" GrantedMemory="1600" MaxUsedMemory="328" />

The serial memory (desired and required) numbers shifted downwards.  My original MemoryGrantInfo was for a query referencing a columnstore index executing in batch mode (with parallelism, as required). 

Here is the original plan:

image thumb Memory Grant Execution Plan Statistics

By capping the parallelism to a MAXDOP 1, the QO still chose to reference the columnstore index scan operator, but the plan operator requirements changed and thus so did the overall memory requirements (including a change from a Hash Match to a Merge Join):

image thumb Memory Grant Execution Plan Statistics

The Granted Workspace Memory (KB) perfmon counter – which showed 1600 KB for the serial plan – matched the 1600 value for SerialDesiredMemory, DesiredMemory and RequestedMemory.

While the MemoryGrantInfo element doesn’t replace the need for the sys.dm_exec_query_memory_grants DMV when trying to evaluate request level concurrent memory grant activity, having this additional information in the execution plan is much more convenient when you’re trying to identify requirements scoped to a specific query.

SQL Server 2012’s RetrievedFromCache Attribute

SQL Server 2012 includes a new RetrievedFromCache attribute in the query execution plan.

Let’s say I execute the following query immediately after executing DBCC FREEPROCCACHE:

 
SELECT     p.ProductLine,
           SUM(f.SalesAmount) AS TotalSalesAmount
FROM [dbo].[FactInternetSales] AS f
INNER JOIN [dbo].[DimProduct] AS p ON
     f.ProductKey = p.ProductKey
GROUP BY p.ProductLine
ORDER BY p.ProductLine;

What value for RetrievedFromCache would you expect to see?  In this example, I saw the following attribute value (with the attribute highlighted and StmtSimple abridged for clarity):

<StmtSimple StatementCompId=”2″ StatementEstRows=”5″ StatementId=”1″ StatementOptmLevel=”FULL” StatementSubTreeCost=”57.0909″  RetrievedFromCache=”true”>

This value is also “true” for scenarios where you use sp_recompile on a module – as it just means that object will be recompiled on the next run and retrieved from cache.

What if I add a RECOMPILE query hint?

 
SELECT     p.ProductLine,
           SUM(f.SalesAmount) AS TotalSalesAmount
FROM [dbo].[FactInternetSales] AS f
INNER JOIN [dbo].[DimProduct] AS p ON
     f.ProductKey = p.ProductKey
GROUP BY p.ProductLine
ORDER BY p.ProductLine
OPTION (RECOMPILE);

This time, I saw a “false” for RetreivedFromCache:

<StmtSimple StatementCompId=”1″ StatementEstRows=”5″ StatementId=”1″ StatementOptmLevel=”FULL” StatementSubTreeCost=”57.0909″  RetrievedFromCache=”false”>

And what about scenarios where you have “optimize for ad hoc workloads” enabled for the SQL Server instance?

EXEC sp_configure 'show advanced options',1;

RECONFIGURE;

EXEC sp_configure 'optimize for ad hoc workloads',1;

RECONFIGURE;

I executed DBCC FREEPROCCACHE and execute the following query (which should be “stubbed” given the server option):

 
SELECT     p.ProductLine,
SUM(f.SalesAmount) AS TotalSalesAmount
FROM [dbo].[FactInternetSales] AS f
INNER JOIN [dbo].[DimProduct] AS p ON
f.ProductKey = p.ProductKey
GROUP BY p.ProductLine
ORDER BY p.ProductLine;
GO

Sure enough – the RetrievedFromCache is False:

<StmtSimple StatementCompId=”1″ StatementEstRows=”5″ StatementId=”1″ StatementOptmLevel=”FULL” StatementSubTreeCost=”57.0909″  RetrievedFromCache=”false”>

And if I execute the SELECT query a second time without clearing the cache, it turns to “true”:

<StmtSimple StatementCompId=”1″ StatementEstRows=”5″ StatementId=”1″ StatementOptmLevel=”FULL” StatementSubTreeCost=”57.0909″  RetrievedFromCache=”true”>

Now if I disable “optimize for ad hoc workloads” – what changes?

EXEC sp_configure 'optimize for ad hoc workloads',0; 
RECONFIGURE;

EXEC sp_configure 'show advanced options',0; 
RECONFIGURE;

As expected – after executing DBCC FREEPROCCACHE and executing the SELECT query, I see a RetrievedFromCache value of “true” in contrast to “false” when optimize for ad hoc workloads is enabled.

SpillToTempDb warning and SpillLevel’s mapping to single versus multiple pass

 

This post shows a few examples of Sort related SpillToTempDb execution plan warnings and the associated SpillLevel attribute. 

This blog post is based on SQL Server 2012, version 11.0.2316 and I’m using the AdventureWorksDW2012 database and creating a separate version of the FactInternetSales table called FactInternetSales_Spill:

SELECT ProductKey, OrderDateKey, DueDateKey, ShipDateKey, CustomerKey, PromotionKey, CurrencyKey, SalesTerritoryKey, SalesOrderNumber, SalesOrderLineNumber, RevisionNumber, OrderQuantity, UnitPrice, ExtendedAmount, UnitPriceDiscountPct, DiscountAmount, ProductStandardCost, TotalProductCost, SalesAmount, TaxAmt, Freight, CarrierTrackingNumber, CustomerPONumber, OrderDate, DueDate, ShipDate
INTO [dbo].[FactInternetSales_Spill]
FROM [dbo].[FactInternetSales];

I started off with 60,398 rows and no indexes.  I then created a clustered index and had the Include Actual Execution Plan enabled:

ALTER TABLE [dbo].[FactInternetSales_Spill] 
ADD  CONSTRAINT [PK_FactInternetSales_SalesOrderNumber_SalesOrderLineNumber_Spill] 
PRIMARY KEY CLUSTERED 
(
    [SalesOrderNumber] ASC,
    [SalesOrderLineNumber] ASC
)WITH (ONLINE = OFF) ON [PRIMARY];
GO

The associated execution plan had no spill warnings:

image thumb SpillToTempDb warning and SpillLevel’s mapping to single versus multiple pass

I pumped up the size of this table to 664,378 rows:

INSERT [dbo].[FactInternetSales_Spill]
(ProductKey, OrderDateKey, DueDateKey, ShipDateKey, CustomerKey, PromotionKey, CurrencyKey, SalesTerritoryKey, SalesOrderNumber, SalesOrderLineNumber, RevisionNumber, OrderQuantity, UnitPrice, ExtendedAmount, UnitPriceDiscountPct, DiscountAmount, ProductStandardCost, TotalProductCost, SalesAmount, TaxAmt, Freight, CarrierTrackingNumber, CustomerPONumber, OrderDate, DueDate, ShipDate)
SELECT ProductKey, OrderDateKey, DueDateKey, ShipDateKey, CustomerKey, PromotionKey, CurrencyKey, SalesTerritoryKey, LEFT(CAST(NEWID() AS NVARCHAR(36)),20), SalesOrderLineNumber, RevisionNumber, OrderQuantity, UnitPrice, ExtendedAmount, UnitPriceDiscountPct, DiscountAmount, ProductStandardCost, TotalProductCost, SalesAmount, TaxAmt, Freight, CarrierTrackingNumber, CustomerPONumber, OrderDate, DueDate, ShipDate
FROM [dbo].[FactInternetSales];
GO 10

Dropping and and re-creating the index, I still didn’t see spill warnings, so I pumped it up to 7,912,138 rows:

INSERT [dbo].[FactInternetSales_Spill]
(ProductKey, OrderDateKey, DueDateKey, ShipDateKey, CustomerKey, PromotionKey, CurrencyKey, SalesTerritoryKey, SalesOrderNumber, SalesOrderLineNumber, RevisionNumber, OrderQuantity, UnitPrice, ExtendedAmount, UnitPriceDiscountPct, DiscountAmount, ProductStandardCost, TotalProductCost, SalesAmount, TaxAmt, Freight, CarrierTrackingNumber, CustomerPONumber, OrderDate, DueDate, ShipDate)
SELECT ProductKey, OrderDateKey, DueDateKey, ShipDateKey, CustomerKey, PromotionKey, CurrencyKey, SalesTerritoryKey, LEFT(CAST(NEWID() AS NVARCHAR(36)),20), SalesOrderLineNumber, RevisionNumber, OrderQuantity, UnitPrice, ExtendedAmount, UnitPriceDiscountPct, DiscountAmount, ProductStandardCost, TotalProductCost, SalesAmount, TaxAmt, Freight, CarrierTrackingNumber, CustomerPONumber, OrderDate, DueDate, ShipDate
FROM [dbo].[FactInternetSales];
GO 120

Creating the clustered index on this larger table caused the spill warning to be raised for the Sort iterator (screen shot from the graphical plan, properties of the Sort iterator and the blurb from the XML showplan):

image thumb SpillToTempDb warning and SpillLevel’s mapping to single versus multiple pass

Now the “SpillLevel=”8” was interesting to me.  I was also running “old-school” profiler AND an extended events session at the time to see what they had to say about these warnings.

In profiler, I saw 9 sort warning events, 8 of which are “2 – Multiple pass”.  The single pass means the sort table was written to disk and only a single pass over the data was required for the sorted input (but as you see, there were multiple spill events).  The multiple pass means that, well, multiple passes over the spilled data were needed in order to obtain the sorted output:

image thumb SpillToTempDb warning and SpillLevel’s mapping to single versus multiple pass

As I expected, Extended Events tells me the same thing:

image thumb SpillToTempDb warning and SpillLevel’s mapping to single versus multiple pass

What was also interesting is the behavior if I set ONLINE = ON for my index creation:

ALTER TABLE [dbo].[FactInternetSales_Spill] 
ADD  CONSTRAINT [PK_FactInternetSales_SalesOrderNumber_SalesOrderLineNumber_Spill] 
PRIMARY KEY CLUSTERED 
(
    [SalesOrderNumber] ASC,
    [SalesOrderLineNumber] ASC
)WITH (ONLINE = ON) ON [PRIMARY];
GO

Each event sub class now shows as “Single Pass”:

image thumb SpillToTempDb warning and SpillLevel’s mapping to single versus multiple pass

But that’s not all… The execution plan for my “ONLINE=ON” index creation shows a spill level of “1” – not “8” or “9”.  So each single pass – even though it happens nine times, just shows up as just SpillLevel=”1”:

image thumb SpillToTempDb warning and SpillLevel’s mapping to single versus multiple pass

Now my index creation was executing with parallelism, and indeed 8 threads were involved the Sort iterator execution:

image thumb SpillToTempDb warning and SpillLevel’s mapping to single versus multiple pass

 

Removing parallelism from the picture, aside from my index creation taking significantly longer, I see only one spill warning now:

image thumb SpillToTempDb warning and SpillLevel’s mapping to single versus multiple pass

And setting the maximum degree of parallelism to “4” – I see a total of 5 warnings:

image thumb SpillToTempDb warning and SpillLevel’s mapping to single versus multiple pass

And the plan itself shows a spill level of “4”:

image thumb SpillToTempDb warning and SpillLevel’s mapping to single versus multiple pass

So the “multiple pass” increases the spill level one-for-one.  If I have multiple “single pass” events for the same sort, it shows up as a spill level “1”.

This was just for my particular scenario.  Have you seen other behaviors as well?  If so, please share here.

Off-Topic PSA: Beware of Cramming

Unrelated to cramming for certification exams, but back in March I started getting strange text messages in the middle of the night – sometimes multiple times a night.  The texts varied between celebrity gossip and animal trivia.  For example:

“Lindsay Lohan’s dad, Michael Lohan, and girlfriend Kate Major are expecting their first child together. For HELP call 18888906150”

The source of the text varied – showing numbers like 835-77, 770-50 and 258-70 – but the text always contained a “For HELP call 18888906150” part of the message.  I was worried to call the number, thinking I might get charged, and I was also worried about replying STOP, again because I’ve heard you could be charged (or worse – they find out they have a live person on the other end who they can spam more).

Unfortunately until this month, I didn’t pay this issue much attention.  My excuse?  When you’re woken up at 2AM – it is very annoying – but then by 6AM I’ve already forgotten that it happened or have other things to attend to.  In the meantime, the frequency of messages increased, and after getting spam texts multiple times last night, I finally researched the number and found out a few things. 

Turns out this was classic “cramming” – where a company places unauthorized charges on your telephone bill.  I reviewed my cell phone bill and found charges like the following:

8. 06/04 Multiple Types: 9.99 0.00 9.99
IQ30CALL8668611606
Short Code: 77050 ID: 35041
Provider: BuneUS
Contact: 1-800-331-0500
Renew Date: 07/03/2012

10. 05/18 Multiple Types: 9.99 0.00 9.99
MobibroAlerts
Short Code: 58497 ID: 36301
Provider: Mobibro
Contact: www.att.com/mobilepurchases
Renew Date: 06/17/2012

04/02 Multiple Types: 72139 9.99 0.00 9.99
gossiprage
Short Code: 72139 ID: 35821
Provider: Wise Media
Contact: 1-800-331-0500
Renew Date: 05/01/2012

Since March I’ve been charged by three different providers – $9.99 each for these nightly text messages.

Here is what I did, and we’ll see if it helps (and your method will vary, so I’m just sharing my experience so far):

  • I called AT&T and they revoked the charges that were over the last 60 days.  We’ll see about the other charges, but I may be out of luck on the other months that I didn’t notice.
  • I asked AT&T to block all third party charges.  They added a free service to block this from happening and gave me a PIN in order to authorize third party charges in the future.
  • I asked AT&T to unsubscribe me from the paid services.  We’ll see tonight if that worked.
  • I logged a complaint on http://www.fcc.gov/complaints (Jon recommended this)

My lessons learned?

  • If I’m getting strange text messages – don’t wait FIVE months to research.
  • Don’t assume that a spam text isn’t causing charges.  It probably is.
  • Get the third party charge block on any new cell phone line.

Again, this post is off-topic, but hopefully it will help others who encounter similar issues.

SQL Server Pro article–“Getting Started with Transactional Replication”

I wrote a beginner’s article for Transactional Replication which was published in the July 2012 edition of SQL Server Pro:

Getting Started with Transactional Replication

My last article with them (at the time SQL Server Magazine) was published way back in September 2002

It was my very first published item – called “Put the Hammer Down.”  This was around the time that I caught the writing bug and realized that it was a complimentary activity to SQL Server consulting.  It was also interesting to look at that old article and see where my opinions have shifted over time (for example – I don’t pay attention to average disk queue length anymore and the whole separation of data from log files is a much more nuanced discussion). 

But that one article back in 2002 got me started on the authoring and editing path – and I was thankful for that first opportunity.

AlwaysOn Architecture Guide: Building a High Availability and Disaster Recovery Solution by Using Failover Cluster Instances and Availability Groups

The SQL CAT team identified three common patterns for customers who were actively testing and deploying SQL Server 2012 high availability and disaster recovery solutions.  One pattern is to use a multi-site Failover Cluster Instance (FCI) for local high availability and disaster recovery.  A second pattern is to use AlwaysOn Availability Groups (AG) for local high availability and disaster recovery.  The third pattern involves using an AlwaysOn FCI for local high availability and AlwaysOn AGs for disaster recovery.

The plan, which as of today is now complete, was to write an architecture guide for each design pattern:

As with the last paper, a big thank you is in order to Sanjay Mishra (Microsoft) for inviting me to participate and to Glenn Minch (Microsoft) for keeping things rolling and managing all the moving parts (and also providing excellent feedback).

As with the AG for HA/DR paper, the technical reviewers provided invaluable feedback and perspectives – which is absolutely critical for a topic like this.  So a big thanks to the 13 reviewers – including David P. Smith (ServiceU), Brent Ozar (Brent Ozar PLF), Min He (Microsoft), Juergen Thomas (Microsoft), Mike Weiner (Microsoft), Prem Mehra (Microsoft),  Glenn Berry (SQLskills.com), Alexi Khalyako (Microsoft), Charles Mathews (Microsoft),  Amitabh Tamhane (Microsoft), Chuck Heinzelman (Microsoft), Gianluca Hotz (SolidQ) and Michael Steineke (Edgenet).

As an aside – one change in approach you may notice with these papers is that they are not hundreds of pages long.  The idea was to define the scope and keep things very focused.  If a related issue or concept warranted its own 30 pages (or even its own 10 pages), ideally that topic will soon get its own coverage in some way/shape/form (doesn’t have to be a paper, for example).

MSQL_DQ and PREEMPTIVE_COM_QUERYINTERFACE–Two Sides of the Same Coin

The MSQL_DQ wait type accumulates while waiting for a distributed query to complete and it is not necessarily indicative of an issue.

If MSQL_DQ is one of your top wait types accumulated on your SQL Server instance, you may also see an associated PREEMPTIVE_COM_QUERYINTERFACE wait type.  And actually, you may see that the accumulated values are identical between these two wait types.  PREEMPTIVE_COM_QUERYINTERFACE basically represents accumulated wait time for linked server queries and remote operations as well.

To illustrate, I’ll execute the following query that captures total wait stats before and after and returns the delta (I could use XEvents to do this too – but this is an isolated system and I only have one distributed query being executed at a time). 

I’ll execute a distributed query 50 times and we’ll see what accumulates:

-- “Before” state of waits
SELECT wait_type, waiting_tasks_count
INTO #before_waits
FROM sys.dm_os_wait_stats
WHERE wait_type IN ('PREEMPTIVE_COM_QUERYINTERFACE',
                    'MSQL_DQ');
GO

-- Distributed query executed 50 times
SELECT 'x'
FROM    OPENQUERY ( [JOSEPHSACK-PC\AUGUSTUS] ,'SELECT TOP 1 o1.name
                    FROM master.dbo.sysobjects AS o1' );
GO 50


-- “After” state of waits
SELECT wait_type, waiting_tasks_count
INTO #after_waits
FROM sys.dm_os_wait_stats
WHERE wait_type IN ('PREEMPTIVE_COM_QUERYINTERFACE',
                    'MSQL_DQ');
GO

SELECT    a.wait_type, 
        (a.waiting_tasks_count - b.waiting_tasks_count) AS accumulated_waiting_tasks_count
FROM #before_waits AS b
INNER JOIN #after_waits AS a ON
    b.wait_type = a.wait_type;
GO

DROP TABLE #before_waits;
DROP TABLE #after_waits

This returns the following (after the 50 result sets, of course):

image thumb MSQL DQ and PREEMPTIVE COM QUERYINTERFACE–Two Sides of the Same Coin

Why care? 

Because if MSQL_DQ is 25% of your accumulated wait time for the SQL Server instance – and PREEMPTIVE_COM_QUERYINTERFACE is also a matching 25% of your wait time, this doesn’t truly represent 50% of your overall wait time.  You may overlook wait types with lower accumulated wait times  – and in this scenario that may be a mistake.

AlwaysOn Architecture Guide: Building a High Availability and Disaster Recovery Solution by Using AlwaysOn Availability Groups

The new “AlwaysOn Architecture Guide: Building a High Availability and Disaster Recovery Solution by Using AlwaysOn Availability Groups” white paper was just published by Microsoft over the weekend.

I co-authored this white paper with Sanjay Mishra, Senior Program Manager in the Microsoft SQL Customer Advisory Team.

This paper covers one of the three common customer HA and DR design patterns discussed in Sanjay’s SQL Server 2012 AlwaysOn High Availability and Disaster Recovery Design Patterns post.  You can download the new white paper here.

A big thanks to Glenn Minch, the Content Program Manager for keeping the various aspects of the project going and also to the host of participating technical reviewers. The paper had 17 technical reviewers – including Lindsey Allen (MS), Juergen Thomas (MS), Mike Weiner (MS), Prem Mehra (MS), Yorihito Tada (MS), Curt Matthews (MS), Amitabh Tamhane (MS), Aditya Samant (MS), Daniel Janik (MS), Jimmy May (MS), David P Smith (ServiceU), Richard Waymire (SolidQ), Brent Ozar (Brent Ozar PLF), Wolfgang Kutschera (bwin.party), Paul S. Randal (SQLskills.com), Gianluca Hotz (SolidQ) and Ayad Shammout (Caregroup).

SQL Server 2012 Execution Plan’s NonParallelPlanReason

SQL Server 2012 introduces a “NonParallelPlanReason” attribute in the QueryPlan element in a query execution plan.  This attribute is not officially documented as of this writing, and so I was curious which scenarios this new functionality covered.

Take the following example query which runs in parallel on my test SQL Server instance:

SELECT     p.ProductLine,
SUM(f.SalesAmount) AS TotalSalesAmount
FROM [dbo].[FactInternetSales] AS f
INNER JOIN [dbo].[DimProduct] AS p ON
f.ProductKey = p.ProductKey
GROUP BY p.ProductLine
ORDER BY p.ProductLine;

A few details on my starting state:

  • I increased the number of rows in FactInternetSales to 123,695,104 rows.
  • Max degree of parallelism is “0”
  • Cost threshold for parallelism is “5”
  • Resource governor is not enabled beyond the default values
  • The table has a columnstore index on it (unrelated to the current post, but more an FYI)
  • My plan runs in parallel when unconstrained
  • The total cost of the aforementioned query is 57.0931

Within the query execution plan, NonParallelPlanReason does not show up when the query runs in parallel:

<QueryPlan DegreeOfParallelism=”8″ MemoryGrant=”52808″ CachedPlanSize=”64″ CompileTime=”10″ CompileCPU=”10″ CompileMemory=”424″>

What happens if I add OPTION (MAXDOP 1) to the query?   I see the following in the actual plan:

<QueryPlan DegreeOfParallelism=”0″ NonParallelPlanReason=”MaxDOPSetToOne” MemoryGrant=”1024″ CachedPlanSize=”32″ CompileTime=”7″ CompileCPU=”7″ CompileMemory=”384″>

If I remove the hint and use EXEC sp_configure ‘max degree of parallelism’, 1, I see the same attribute value:

<QueryPlan DegreeOfParallelism=”0″ NonParallelPlanReason=”MaxDOPSetToOne” MemoryGrant=”1024″ CachedPlanSize=”32″ CompileTime=”7″ CompileCPU=”7″ CompileMemory=”384″>

Next I was curious what would happen if I set max degree of parallelism back to 0 and bumped up the cost threshold for parallelism to a higher value, causing the query to execute with a serial plan? This time there was no NonParallelPlanReason attribute and a DegreeOfParallelism=”1”:

<QueryPlan DegreeOfParallelism=”1″ MemoryGrant=”1024″ CachedPlanSize=”24″ CompileTime=”6″ CompileCPU=”6″ CompileMemory=”384″>

What about setting processor affinity to just one core?  This time we see a new NonParallelPlanReason:

<QueryPlan DegreeOfParallelism=”0″ NonParallelPlanReason=”EstimatedDOPIsOne” MemoryGrant=”1024″ CachedPlanSize=”24″ CompileTime=”8″ CompileCPU=”8″ CompileMemory=”384″>

I’m sure there are other scenarios to explore and if you’ve seen other examples of values for NonParallelPlanReason, please post them in the comments.