It’s hard to set absolute rules about, “Feature X should absolutely never be used.”
However, there are some features that set off alarm bells when I see them. Usually, when I start asking more questions about when we’re using those particular features, I get answers of, “Oh, I didn’t know that was a problem.” As we have a bigger discussion, it leads to the piece of code failing the code review, and going back to the drawing board for improvements.
- Joining to table-valued user-defined functions: if it’s a multi-statement function, its underlying work doesn’t show up in execution plans, and it’s single-threaded, running one row at a time. I’ll pop open the plan to just double-check if it’s multi-statement or inline, and heaven forbid it calls another function.
- Joining to table variables: even with 2019’s improved row estimations, table variables still don’t get statistics on their contents, and inserting data into them is still single-threaded. There are a few edge case uses that we discuss in Fundamentals of TempDB, especially around reducing recompilations, but generally when I see table variables in code, it’s because someone didn’t know about their weaknesses. (The weaknesses of table variables, I mean, not their own weaknesses. Although now that you say it that way…)
- CROSS JOIN: sure, there are legitimate reasons why you might want every row from a table with no filtering whatsoever, but it’s fairly unusual. These two words set off an alarm bell that make me look more closely.
- Multiple joins to the same CTE: CTEs can be fine, although a temp table is often a better fit. The thing that raises flags during a code review is seeing the same CTE referred to multiple times, like FROM cte JOIN cte JOIN cte, each with a different alias. That CTE will show up multiple times in the execution plan, hitting the underlying tables repeatedly.
- The kitchen sink design pattern: WHERE (CustomerId = @CustomerId OR @CustomerId IS NULL) is tough for SQL Server to optimize, and we typically change that over to dynamic SQL or judicious use of RECOMPILE hints.
- SELECT (things) INTO #TempTable: I love temp tables, and I think they get a bad rap, and even SELECT INTO can have advantages over explicitly creating a table. However, when I see SELECT INTO, it’s usually because someone was working quickly, and they didn’t want to take the time to write CREATE TABLE and figure out all the datatypes. For example, during a recent review, the query author was copying an entire table into TempDB, and then doing filtering rows there – thinking that they were minimizing the amount of work they were doing in the user database.
- Creating indexes on a temp table: this sounds like a good thing – after all, who doesn’t like indexes? – but most of the time when I see it and I start asking questions, I get answers like, “I just guessed that the index might help.” That triggers us to spend a little more time experimenting, and also leads to teachable moments about how that affects temp table caching.
- WITH (NOLOCK): I keep demoing that it gets random results, and I know you might find this hard to believe, but not everyone reads my blog. (I know, right?) There are indeed places where random results are just fine, but most of the time when I see this red flag and I ask questions, the query author just didn’t know how dangerous this hint was. Bonus points for trying to use it in an UPDATE statement.
- BEGIN TRAN & COMMIT with no error handling – when I see this, I ask, “So you’re doing an explicit transaction because it’s important to you that all of this stuff gets committed, or not at all, right? And you’re expecting to have the transaction fail every now and then and you want to roll back, right? So, uh, what happens to your app when that happens?” If you care enough to specify a transaction in the database, then you need to keep going and specify how to handle the errors, too. Erland Sommarskog’s Error and Transaction Handling post is a great place to start.
I have absolutely given the thumbs-up to code that had more than one of these anti-patterns – heck, this stuff is in the First Responder Kit – but they’re all signs that I’m gonna look a little more closely at your code and ask questions, and you should probably ask those questions, too.