Quantcast
Channel: Brent Ozar Unlimited®
Viewing all articles
Browse latest Browse all 3153

Your Views Aren’t The Problem. Your Code Is.

$
0
0

“I hate views,” the DBA said. “They kill performance. And nested views are even worse.”

Wrong. I’ll prove it.

I’m going to use the 50GB StackOverflow2013 database, and I’ll start by creating a couple of indexes on the Users table to help our queries, then create a view on the Users table:

USE StackOverflow2013;
GO
DropIndexes;
CREATE INDEX DisplayName ON dbo.Users(DisplayName);
CREATE INDEX Location ON dbo.Users(Location);
GO
CREATE OR ALTER VIEW dbo.vwUsers AS
  SELECT * FROM dbo.Users;
GO

First, compare plans for the table and the view.

If I run two identical queries looking for users in a specific city:

SET STATISTICS IO, TIME ON;

SELECT *
  FROM dbo.Users
  WHERE DisplayName = 'Brent Ozar'
    AND Location = 'San Diego, CA, USA';

SELECT *
  FROM dbo.vwUsers
  WHERE DisplayName = 'Brent Ozar'
    AND Location = 'San Diego, CA, USA';

Both query plans use the index, estimate the same number of rows, and work the same way:

Both queries do the same number of reads, and take the same amount of time:

There’s no monster hiding under your bed.

Same thing with nested views:
there’s nothing innately wrong with them.

Let’s go deep and create a whole series of nested Babushka dolls ten layers deep:

CREATE OR ALTER VIEW dbo.vwUsers_Nesty1 AS
  SELECT * FROM dbo.vwUsers;
GO
CREATE OR ALTER VIEW dbo.vwUsers_Nesty2 AS
  SELECT * FROM dbo.vwUsers_Nesty1;
GO
CREATE OR ALTER VIEW dbo.vwUsers_Nesty3 AS
  SELECT * FROM dbo.vwUsers_Nesty2;
GO
CREATE OR ALTER VIEW dbo.vwUsers_Nesty4 AS
  SELECT * FROM dbo.vwUsers_Nesty3;
GO
CREATE OR ALTER VIEW dbo.vwUsers_Nesty5 AS
  SELECT * FROM dbo.vwUsers_Nesty4;
GO
CREATE OR ALTER VIEW dbo.vwUsers_Nesty6 AS
  SELECT * FROM dbo.vwUsers_Nesty5;
GO
CREATE OR ALTER VIEW dbo.vwUsers_Nesty7 AS
  SELECT * FROM dbo.vwUsers_Nesty6;
GO
CREATE OR ALTER VIEW dbo.vwUsers_Nesty8 AS
  SELECT * FROM dbo.vwUsers_Nesty7;
GO
CREATE OR ALTER VIEW dbo.vwUsers_Nesty9 AS
  SELECT * FROM dbo.vwUsers_Nesty8;
GO
CREATE OR ALTER VIEW dbo.vwUsers_Nesty10 AS
  SELECT * FROM dbo.vwUsers_Nesty9;
GO

And then query the outermost one. It still uses the index, still estimates row counts correctly, and still does just 6 page reads in 0ms.

If you’re purely using views for the purpose of abstraction, you’re fine. You can use them for assigning permissions, for example, and you don’t run into any problems at all – until you start getting fancy with the code.

The problem isn’t the view.
The problem is what you put in it.

Let’s go back to a simple non-nested view and start putting some business logic. For example, let’s say we want to hide users who’ve left a comment that got highly downvoted:

CREATE OR ALTER VIEW dbo.vwUsers_Filtered AS
  SELECT *
  FROM dbo.Users u
  WHERE NOT EXISTS (SELECT * 
                    FROM dbo.Comments c 
                    WHERE c.UserId = u.Id 
                    AND c.Score < 0)
GO

SELECT *
  FROM dbo.vwUsers_Filtered
  WHERE DisplayName = 'Brent Ozar'
    AND Location = 'San Diego, CA, USA';
GO

Now, all of a sudden, the query takes longer to execute and does over a million logical reads, because it needs to scan the Comments table. The execution plan shows that it’s a hot mess and desperately needs an index:

But the problem still isn’t the view, as evidenced by the fact that if we inline that code into a single query, not referencing the view, we get the same execution plan and behavior:

Views aren’t killing performance.
That code sucks by itself, or in a view.

Stop blaming views for crappy code. If you copy/paste all the code from the views and inline it directly into a single query, you’re likely to recoil in horror. The problem isn’t the views: the problem is that someone put bad code into a view, and therefore made it reusable.

Views are great for reusability.

If you choose to reuse crappy code, well, that says less about the view, and more about you.


Viewing all articles
Browse latest Browse all 3153