When auditing SQL Server databases, one of the more common problems I see is that people add too many indexes to their tables. This has a significant performance impact on your SQL Server. First off, data inserts become slower for every update/delete/insert, because we have to maintain all indexes added for that specific table. Too many indexes also have an impact on your read performance. We all know SQL Server is pretty fast when it comes to reading data out of memory, but with a large amounts of indexes, corresponding data also needs to be stored inside your memory. This is what increases the amount of memory requiered to have the full table in memory.
If data has to be fetched from a disk, we also see a performance impact. While indexes are created to increase read performance, it may be badly influenced when adding indexes if the system is in memory pressure. To show you what the actual effects of having too many indexes are on your table, I created a few tests.
I created a script that inserts 100.000 rows into a table.
CREATE TABLE IndexMania
(
IndexManiaID int identity (1,1),
IndexColumn1 datetime,
IndexColumn2 char(1000),
IndexColumn3 char(500),
IndexColumn4 decimal (18,2),
IndexColumn5 int
)
You can see that I added a char (1000) and char (500) field to make sure I got a good amount of data to be inserted. If my data inserts are too small, SQL Server will make use of the lazy writer and not show any writes with the insert statement. By increasing the size of one row and importing 100.000 rows I bypass this issue. I am also clearing the SQL Server memory before every statement.
The indexes I created are the following
-- CLUSTERED INDEX ON ID
CREATE UNIQUE CLUSTERED INDEX [CI_IndexMania1] ON [dbo].[IndexMania]
(
[IndexManiaID] ASC
)
--- CREATE COVERING DATE INDEX
CREATE NONCLUSTERED INDEX [NCI_1] ON [dbo].[IndexMania]
(
[IndexColumn1] ASC
)
INCLUDE ( [IndexManiaID],
[IndexColumn2],
[IndexColumn3],
[IndexColumn4],
[IndexColumn5])
GO
-- 2 Additional NCI
CREATE NONCLUSTERED INDEX [NCI_2] ON [dbo].[IndexMania]
(
[IndexColumn2] ASC,
[IndexColumn3] ASC
)
INCLUDE ( [IndexManiaID],
[IndexColumn1],
[IndexColumn4],
[IndexColumn5])
CREATE NONCLUSTERED INDEX [NCI_3] ON [dbo].[IndexMania]
(
[IndexColumn4] ASC,
[IndexColumn3] ASC
)
INCLUDE ( [IndexManiaID],
[IndexColumn1],
[IndexColumn2],
[IndexColumn5])
Amount of writes (about the same for delete, insert and update)
INSERT
SQL Server Execution Times:
CPU time = 688 ms, elapsed time = 1299 ms.
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 19 ms.
UPDATE
SQL Server Execution Times:
CPU time = 407 ms, elapsed time = 623 ms.
SQL Server parse and compile time:
CPU time = 15 ms, elapsed time = 20 ms.
DELETE
SQL Server Execution Times:
CPU time = 468 ms, elapsed time = 1129 ms.
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 0 ms.
Amount of writes (about the same for Delete insert and update)
INSERT
SQL Server Execution Times:
CPU time = 1171 ms, elapsed time = 2745 ms.
SQL Server parse and compile time:
CPU time = 16 ms, elapsed time = 25 ms.
UPDATE
SQL Server Execution Times:
CPU time = 297 ms, elapsed time = 550 ms.
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 20 ms.
DELETE
SQL Server Execution Times:
CPU time = 734 ms, elapsed time = 3201 ms.
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 0 ms.
Amount of writes (about the same for Delete insert and update)
INSERT
SQL Server Execution Times:
CPU time = 1953 ms, elapsed time = 4575 ms.
SQL Server parse and compile time:
CPU time = 16 ms, elapsed time = 32 ms.
UPDATE
SQL Server Execution Times:
CPU time = 2375 ms, elapsed time = 5780 ms.
SQL Server parse and compile time:
CPU time = 16 ms, elapsed time = 23 ms.
DELETE
SQL Server Execution Times:
CPU time = 1718 ms, elapsed time = 7761 ms.
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 0 ms.
Amount of writes
INSERT
SQL Server Execution Times:
CPU time = 42468 ms, elapsed time = 47094 ms.
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 21 ms.
UPDATE
SQL Server Execution Times:
CPU time = 108063 ms, elapsed time = 125766 ms.
SQL Server parse and compile time:
CPU time = 10 ms, elapsed time = 10 ms.
DELETE
SQL Server Execution Times:
CPU time = 54922 ms, elapsed time = 73162 ms.
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 0 ms.
As seen in our test, we can conclude that each added index has an impact on our write performance and on the amount of data stored in memory to facilitate the index.
When inserting data to a heap table, we needed about 20020 writes to insert 100.000 records. There was almost no difference with the amount of writes while writing to a clustered index. When adding a covering index, the amount of writes doubled. This is normal because we will keep the data two times. When adding another two covering indexes, we are doing about 4 times the original amount of writes. There are some discrepancies in the amount of writes, but this is due to the fact that to execute some of the queries we needed to spill to TEMPDB. This gave a larger amount of writes than the amount of indexes increased. After multiple tests we can see that we will need 4 times the amount of writes purely for the indexes. (DELETE, UPDATE and INSERT)
When inserting data, we have seen that while inserting into a heap table we have an average of about 1 second, when adding a clustered index this increased to an average of 2-3 seconds. When adding 1 covering index this increased to 4-6 seconds, and with two additional covering indexes the amount of time exploded to a whopping 45 – 50 seconds to insert the same workload.
After inserting 100.000 rows into the heap table, we have seen that we use 156MB of the memory to facilitate this table. When adding the clustered index, this amount stays exactly the same. When adding 1 covering index, this doubled, which means we are now keeping 2 copies of the data inside the memory. When adding the 2 additional covering indexes, we see that we are keeping the same data 4 times inside our memory.
We should careful when adding additional indexes. This does not only have an impact on our insert/update/delete performance, but this also impacts the amount of memory used for that specific table. We want to be able to get all items out of our cache, which will be in nanoseconds instead of milliseconds when retrieving data from disk. So we have to keep in mind that every index that we add might take additional space inside your cache, removing other objects out of your cache and slowing them down.
In the example of today we were using covering indexes which is actually saying double my data. Keep in mind however that this is not a pledge to stop using indexes even covering indexes. Indexes will help your performance incredibly if used in the correct way. As a general recommendation I would say try to keep your indexes as small as possible and only include the fields you need to reduce the overhead of your index.
Thank you for reading and stay tuned!
Cookie | Duration | Description |
---|---|---|
ARRAffinity | session | ARRAffinity cookie is set by Azure app service, and allows the service to choose the right instance established by a user to deliver subsequent requests made by that user. |
ARRAffinitySameSite | session | This cookie is set by Windows Azure cloud, and is used for load balancing to make sure the visitor page requests are routed to the same server in any browsing session. |
cookielawinfo-checkbox-advertisement | 1 year | Set by the GDPR Cookie Consent plugin, this cookie records the user consent for the cookies in the "Advertisement" category. |
cookielawinfo-checkbox-analytics | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Analytics". |
cookielawinfo-checkbox-functional | 11 months | The cookie is set by GDPR cookie consent to record the user consent for the cookies in the category "Functional". |
cookielawinfo-checkbox-necessary | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary". |
cookielawinfo-checkbox-others | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Other. |
cookielawinfo-checkbox-performance | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookie is used to store the user consent for the cookies in the category "Performance". |
CookieLawInfoConsent | 1 year | CookieYes sets this cookie to record the default button state of the corresponding category and the status of CCPA. It works only in coordination with the primary cookie. |
elementor | never | The website's WordPress theme uses this cookie. It allows the website owner to implement or change the website's content in real-time. |
viewed_cookie_policy | 11 months | The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data. |
Cookie | Duration | Description |
---|---|---|
__cf_bm | 30 minutes | Cloudflare set the cookie to support Cloudflare Bot Management. |
pll_language | 1 year | Polylang sets this cookie to remember the language the user selects when returning to the website and get the language information when unavailable in another way. |
Cookie | Duration | Description |
---|---|---|
_ga | 1 year 1 month 4 days | Google Analytics sets this cookie to calculate visitor, session and campaign data and track site usage for the site's analytics report. The cookie stores information anonymously and assigns a randomly generated number to recognise unique visitors. |
_ga_* | 1 year 1 month 4 days | Google Analytics sets this cookie to store and count page views. |
_gat_gtag_UA_* | 1 minute | Google Analytics sets this cookie to store a unique user ID. |
_gid | 1 day | Google Analytics sets this cookie to store information on how visitors use a website while also creating an analytics report of the website's performance. Some of the collected data includes the number of visitors, their source, and the pages they visit anonymously. |
ai_session | 30 minutes | This is a unique anonymous session identifier cookie set by Microsoft Application Insights software to gather statistical usage and telemetry data for apps built on the Azure cloud platform. |
CONSENT | 2 years | YouTube sets this cookie via embedded YouTube videos and registers anonymous statistical data. |
vuid | 1 year 1 month 4 days | Vimeo installs this cookie to collect tracking information by setting a unique ID to embed videos on the website. |
Cookie | Duration | Description |
---|---|---|
ai_user | 1 year | Microsoft Azure sets this cookie as a unique user identifier cookie, enabling counting of the number of users accessing the application over time. |
VISITOR_INFO1_LIVE | 5 months 27 days | YouTube sets this cookie to measure bandwidth, determining whether the user gets the new or old player interface. |
YSC | session | Youtube sets this cookie to track the views of embedded videos on Youtube pages. |
yt-remote-connected-devices | never | YouTube sets this cookie to store the user's video preferences using embedded YouTube videos. |
yt-remote-device-id | never | YouTube sets this cookie to store the user's video preferences using embedded YouTube videos. |
yt.innertube::nextId | never | YouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen. |
yt.innertube::requests | never | YouTube sets this cookie to register a unique ID to store data on what videos from YouTube the user has seen. |
Cookie | Duration | Description |
---|---|---|
WFESessionId | session | No description available. |