Here’s what we discussed:
(The common phrase is to “have your cake and eat it too”, but did you ever think about how that’s backwards, and it doesn’t make any sense? It actually makes a lot more sense in the opposite order – as in, eat your cake and have it too – because that represents the best-of-both-worlds scenario. Have your cake and eat it too implies that you would have it, and then eat it, but once you eat it, you can no longer have it, so – okay, I’ll stick with teaching database stuff. Moving on.)
Let’s use the Users table from the Stack Overflow database, and let’s pretend that the Users.Location column is stored as JSON rather than letting users type in whatever they want. Let’s pretend we make them pick their Country, Province, and City. Then, let’s use one of my Database Animations to illustrate the pain of querying it without the JSON index, and then the cake-y-ness of the added index:
▶ Watch the animated version of Json Index
Before we have the JSON index, we have to do:
The JSON index turns the work into a simple index-seek-and-key-lookup – the same awesome, ground-shaking power and speed you’re used to having from traditional table designs & indexes. You get to eat your cake (not bothering to design your table structures), and have it too (get fast querying – as long as you’re willing to design indexes to support your queries.)
However, this only works if:
After all, Microsoft makes it sound practically unlimited in the CREATE JSON INDEX documentation. The first example they use looks like this:
CREATE JSON INDEX json_content_index ON dbo.Users (UserAttributes);
In that example, EVERYTHING we stuff into that JSON junk drawer gets indexed – including in our example above, whether they want Theme:Dark or Theme:Light. However, that ends up creating an index on every attribute we put in our JSON. The size growth is explosive, and it’s not unusual to see the index size be a multiple of the size of the JSON data we’re storing. (And you thought JSON data was size-inefficient!) There aren’t any warnings about that in the documentation, but trust me, the overhead is spectacular. If we stored the entire Users table columns all in JSON, and then ran Microsoft’s style of create-index, check out the size overhead:
The table itself is only 3.9GB, but the JSON index atop that data is 6GB – almost double the size of the table! Plus, if you modify any attribute on that table – even attributes you don’t filter on, like Theme = Dark vs Light – then you pay a performance penalty to keep the Theme’s index updated.
Instead, what you wanna do is index only the specific attributes that you’re going to regularly filter on, like the example I use in the animation:
CREATE JSON INDEX json_content_index ON
dbo.Users ('$.Location.Country', '$.Location.Province', '$.Location.City');
That way you get the best of all worlds: fast performance on the filters you use, low insert/update/delete overhead for the index maintenance on those indexed attributes, and for any other properties you wanna save in JSON, you get schema-less design, fast iteration on the app side, and low overhead on the database side.
Where does the 5-10 number come from? My Fundamentals of Index Tuning class, where I explain the overhead that each additional index costs in terms of insert/update/delete performance. The more copies of the table that you add, the slower your inserts/updates/deletes go, and you start hitting the point where your users notice long blocking delays under concurrent workloads. In that class, I talk about when you can exceed that 5-10 number, and when you may need to go even lower on your number of indexes in order to get the ingestion speed you need. See you in class!
Note: I generated the animations in this post with Claude Code, but all of the text & demos are completely written by me.
]]>If you don’t set the program names, you’ll either end up with empty strings, or your dev tool’s default name, like Core .Net SqlClient Data Provider, which doesn’t tell you jack.
It’s up to the developers to set the program name in code or in their connection string. Just tack it on to the end of your connection string like this:
server=MyServer;database=MyDatabase;Integrated Security=SSPI;Application Name=My Incredible Application;
And then in your monitoring tools, you’ll be able to group sessions together by application.
One question that comes up a lot afterwards is, “I’m looking at the plan cache and Query Store – what application and what user ran this query?” Unfortunately SQL Server can’t/won’t store that since a query might conceivably be run thousands of times per second, all from different servers/apps/users. If you need to track down who’s running a query, your best bet will be to set up an Extended Events trace, or if it’s a long-running query, do sampling of sp_BlitzWho or sp_WhoIsActive to table periodically.
]]>Here’s what we discussed: