{"id":250,"date":"2016-07-12T10:54:49","date_gmt":"2016-07-12T09:54:49","guid":{"rendered":"https:\/\/sqldoubleg.live-website.com\/?p=250"},"modified":"2022-12-25T10:13:04","modified_gmt":"2022-12-25T10:13:04","slug":"sql-server-2016-using-the-query-store-for-read-only-databases","status":"publish","type":"post","link":"https:\/\/www.sqldoubleg.com\/es\/2016\/07\/12\/sql-server-2016-using-the-query-store-for-read-only-databases\/","title":{"rendered":"T-SQL Tuesday #80 &#8211; SQL Server 2016, using the Query Store for READ-ONLY databases"},"content":{"rendered":"<p>The new Query Store is one of the most exciting features coming with SQL Server 2016, but unfortunately comes with some limitations. Learn how we can overcome one of those&nbsp;<a href=\"https:\/\/chrisyatessql.wordpress.com\/2016\/07\/06\/t-sql-tuesday-080\/\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/07\/tsql2sday150x150.jpg\" alt=\"tsql2sday150x150\" width=\"154\" height=\"154\" style=\"float:left;margin-right:7px\" \/><\/a><\/p>\n<p>&nbsp;<br \/>\nThis post is part of the <a href=\"https:\/\/voiceofthedba.wordpress.com\/t-sql-tuesday-topic-list\/\" target=\"_blank\" rel=\"noopener\">T-SQL Tuesday<\/a>, which is a monthly blog party on the second Tuesday of each month. Everyone is welcome and have the chance to write about SQL Server. <a href=\"https:\/\/chrisyatessql.wordpress.com\/2016\/07\/06\/t-sql-tuesday-080\/\" target=\"_blank\" rel=\"noopener\"><br \/>\nThis month&#8217;s topic is to give a [SQL] gift<\/a>, so here is mine,<br \/>\nMaking the Query Store to work in a READ_ONLY database.<\/p>\n<p>&nbsp;<br \/>\n&nbsp;<br \/>\nLast week I explained why, from my point of view, we should treat <a href=\"https:\/\/sqldoubleg.live-website.com\/2016\/07\/06\/sql-server-2016-query-store-user-data-or-metadata\/\" target=\"_blank\" rel=\"noopener\">the new Query Store as user data for several reasons<\/a>.<\/p>\n<p>One of the reasons is how the Store behaves when the database goes READ_ONLY, which was earlier explained by Kendra Little (<a href=\"http:\/\/www.littlekendra.com\/\" target=\"_blank\" rel=\"noopener\">b<\/a>|<a href=\"https:\/\/twitter.com\/kendra_little\" target=\"_blank\" rel=\"noopener\">t<\/a>) <a href=\"http:\/\/www.littlekendra.com\/2016\/06\/21\/will-query-store-work-in-a-read-only-database\/\" target=\"_blank\" rel=\"noopener\">here<\/a><\/p>\n<p>This is the post I wanted to write first, but getting everyone on the same page first (with the previous one) seemed more appropriate, so I postponed it.<\/p>\n<p>We have seen that in order to get the new Query Store, the database must be READ_WRITE, which might be a bit disappointing because people normally use READ_ONLY database for reporting, so we still might be interested in tune most frequent queries or those that consume lots of resources, so this new tool can help a lot to identify them.<\/p>\n<p>Since that limitation comes together with the feature, the only we can do is to workaround it \ud83d\ude42<\/p>\n<p>&nbsp;<br \/>\n<strong>Bypassing Read-Only database Query Store limitations<\/strong><\/p>\n<p><strong style=\"color:red\">Disclaimer: This approach will not work if you have strong restrictions and your database must be read-only<\/strong><\/p>\n<p>Said that, we can start.<\/p>\n<p>Although the query store&#8217;s information looks like is part of the databases&#8217;s metadata and it&#8217;s accessible using DMV&#8217;s in the [sys] schema, <a href=\"https:\/\/sqldoubleg.live-website.com\/2016\/07\/06\/sql-server-2016-query-store-user-data-or-metadata\/\" target=\"_blank\" rel=\"noopener\">it behaves as it&#8217;s user data<\/a>, so when the database goes READ_ONLY so does the Query Store.<\/p>\n<p>First let&#8217;s see the normal behaviour when we create a new database<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nUSE master\r\nGO\r\nIF DB_ID('read_only_db') IS NOT NULL BEGIN\r\n\tALTER DATABASE read_only_db SET SINGLE_USER WITH ROLLBACK IMMEDIATE\r\n\tDROP DATABASE read_only_db\r\nEND\r\nGO\r\n\r\nCREATE DATABASE read_only_db\r\nGO\r\n\r\nALTER DATABASE read_only_db SET QUERY_STORE = ON\r\nGO\r\n<\/pre>\n<p>Once we have our database with the Query Store enabled, we can start running queries<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nUSE read_only_db\r\nGO\r\n\r\nCREATE TABLE dbo.t(\r\nc1 INT IDENTITY CONSTRAINT PK_t PRIMARY KEY\r\n)\r\nGO\r\n\r\nSELECT * FROM dbo.t WHERE c1 = 26\r\nGO 10\r\n\r\nSELECT q.query_id, qt.query_sql_text, qr.last_execution_time, qr.count_executions\r\n\tFROM sys.query_store_query AS q\r\n\t\tLEFT JOIN sys.query_store_query_text AS qt\r\n\t\t\tON qt.query_text_id = q.query_text_id\r\n\t\tLEFT JOIN sys.query_store_plan AS qp\r\n\t\t\tON qp.query_id = q.query_id\r\n\t\tCROSS APPLY (SELECT SUM(count_executions) AS count_executions, MAX(last_execution_time) AS last_execution_time FROM sys.query_store_runtime_stats WHERE plan_id = qp.plan_id) AS qr\r\n\tWHERE qt.query_sql_text = '(@1 tinyint)SELECT * FROM [dbo].[t] WHERE [c1]=@1'\r\nGO\r\n<\/pre>\n<p><a href=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/07\/01_first_query.png\" target=\"_blank\" rel=\"noopener\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/07\/01_first_query.png\" alt=\"01_first_query\" width=\"989\" height=\"132\" class=\"aligncenter size-full wp-image-264\" srcset=\"https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/01_first_query.png 989w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/01_first_query-300x40.png 300w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/01_first_query-150x20.png 150w\" sizes=\"(max-width: 989px) 100vw, 989px\" \/><\/a><\/p>\n<p>There are 10 executions for our query in the Query Store.<\/p>\n<p>Now we can see how the counter stops when the database goes READ_ONLY.<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nALTER DATABASE read_only_db SET READ_ONLY WITH ROLLBACK IMMEDIATE\r\nGO\r\n\r\nSELECT * FROM dbo.t WHERE c1 = 26\r\nGO 10\r\n\r\nSELECT q.query_id, qt.query_sql_text, qr.last_execution_time, qr.count_executions\r\n\tFROM sys.query_store_query AS q\r\n\t\tLEFT JOIN sys.query_store_query_text AS qt\r\n\t\t\tON qt.query_text_id = q.query_text_id\r\n\t\tLEFT JOIN sys.query_store_plan AS qp\r\n\t\t\tON qp.query_id = q.query_id\r\n\t\tCROSS APPLY (SELECT SUM(count_executions) AS count_executions, MAX(last_execution_time) AS last_execution_time FROM sys.query_store_runtime_stats WHERE plan_id = qp.plan_id) AS qr\r\n\tWHERE qt.query_sql_text = '(@1 tinyint)SELECT * FROM [dbo].[t] WHERE [c1]=@1'\r\n\r\nGO\r\n<\/pre>\n<p><a href=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/07\/02_read_only_query.png\" target=\"_blank\" rel=\"noopener\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/07\/02_read_only_query.png\" alt=\"02_read_only_query\" width=\"989\" height=\"132\" class=\"aligncenter size-full wp-image-265\" srcset=\"https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/02_read_only_query.png 989w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/02_read_only_query-300x40.png 300w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/02_read_only_query-150x20.png 150w\" sizes=\"(max-width: 989px) 100vw, 989px\" \/><\/a><\/p>\n<p>&nbsp;<br \/>\nDatabases&#8217; metadata resides in the PRIMARY filegroup and we can take advantage of that. By adding a new filegroup where to place user data, we can leave PRIMARY exclusively for metadata.<br \/>\nWhich is another little proof of why database design matters.<\/p>\n<p>We need to get the database back to READ_WRITE to be able to add new files or filegroups, so we&#8217;ll do that first. Then we will move our table to the new filegroup by recreating the clustered index there.<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nALTER DATABASE read_only_db SET READ_WRITE WITH ROLLBACK IMMEDIATE\r\nGO\r\n\r\nALTER DATABASE read_only_db ADD FILEGROUP [USER_DATA]\r\nGO\r\n\r\nALTER DATABASE read_only_db MODIFY FILEGROUP [USER_DATA] DEFAULT\r\nGO\r\n\r\nALTER DATABASE read_only_db ADD FILE (NAME='UserData', FILENAME='C:\\Program Files\\Microsoft SQL Server\\MSSQL13.MSSQL2016\\MSSQL\\DATA\\UserData.ndf') TO FILEGROUP [USER_DATA]\r\nGO\r\n\r\nCREATE UNIQUE CLUSTERED INDEX PK_t ON dbo.t (c1) WITH (DROP_EXISTING = ON) ON [USER_DATA];  \r\nGO  \r\n\r\nALTER DATABASE read_only_db MODIFY FILEGROUP [USER_DATA] READ_ONLY\r\nGO\r\n<\/pre>\n<p>After these changes, our database is still READ_WRITE, but our data is READ_ONLY because we have moved it to a READ_ONLY filegroup. Also note that I made the new filegroup as DEFAULT, so any attempt of creating a table (without specifying another READ_WRITE filegroup) will fail.<\/p>\n<p>Trying to insert data in our table, will produce an error.<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nINSERT INTO dbo.t DEFAULT VALUES\r\nGO\r\n<\/pre>\n<p><a href=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/07\/03_insert_error.png\" target=\"_blank\" rel=\"noopener\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/07\/03_insert_error.png\" alt=\"03_insert_error\" width=\"1080\" height=\"157\" class=\"aligncenter size-full wp-image-266\" srcset=\"https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/03_insert_error.png 1080w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/03_insert_error-300x44.png 300w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/03_insert_error-1024x149.png 1024w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/03_insert_error-150x22.png 150w\" sizes=\"(max-width: 1080px) 100vw, 1080px\" \/><\/a><\/p>\n<p>But the Query store still works with no problem<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nSELECT * FROM dbo.t WHERE c1 = 26\r\nGO 10\r\n\r\nSELECT q.query_id, qt.query_sql_text, qr.last_execution_time, qr.count_executions\r\n\tFROM sys.query_store_query AS q\r\n\t\tLEFT JOIN sys.query_store_query_text AS qt\r\n\t\t\tON qt.query_text_id = q.query_text_id\r\n\t\tLEFT JOIN sys.query_store_plan AS qp\r\n\t\t\tON qp.query_id = q.query_id\r\n\t\tCROSS APPLY (SELECT SUM(count_executions) AS count_executions, MAX(last_execution_time) AS last_execution_time FROM sys.query_store_runtime_stats WHERE plan_id = qp.plan_id) AS qr\r\n\tWHERE qt.query_sql_text = '(@1 tinyint)SELECT * FROM [dbo].[t] WHERE [c1]=@1'\r\n\r\nGO\r\n<\/pre>\n<p><a href=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/07\/04_read_only_db_query_store_read_write.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/07\/04_read_only_db_query_store_read_write.png\" alt=\"04_read_only_db_query_store_read_write\" width=\"1086\" height=\"140\" class=\"aligncenter size-full wp-image-267\" srcset=\"https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/04_read_only_db_query_store_read_write.png 1086w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/04_read_only_db_query_store_read_write-300x39.png 300w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/04_read_only_db_query_store_read_write-1024x132.png 1024w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/04_read_only_db_query_store_read_write-150x19.png 150w\" sizes=\"(max-width: 1086px) 100vw, 1086px\" \/><\/a><\/p>\n<p>You can see how again the query store keeps counting the executions of our query.<\/p>\n<p>&nbsp;<br \/>\n<strong>Conclusion<\/strong><\/p>\n<p>As I said before, you need the ability of leaving your database of READ_WRITE while your user data is READ_ONLY to accomplish it, but I am sure with this trick and a couple of triggers you can stop people messing with the data and at the same time enjoy this new feature, which I remind you it&#8217;s available for all editions, even Express (although there are some caveats  like <a href=\"https:\/\/www.brentozar.com\/archive\/2016\/07\/breaking-news-2016-query-store-cleanup-doesnt-work-standard-express-editions\/\" target=\"_blank\" rel=\"noopener\">this<\/a>)<\/p>\n<p>And just to finish, I&#8217;ll leave you some links. Thanks for reading<\/p>\n<ul>\n<li>A bit old, but still applicable most of it, <a href=\"https:\/\/blogs.msdn.microsoft.com\/buckwoody\/2009\/05\/21\/sql-server-best-practices-setting-a-default-filegroup\/\" target=\"_blank\" rel=\"noopener\">SQL Server Best Practices: Setting a Default Filegroup<\/a><\/li>\n<li><a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms175905.aspx\" target=\"_blank\" rel=\"noopener\">Move an Existing Index to a Different Filegroup<\/a><\/li>\n<li><a href=\"https:\/\/msdn.microsoft.com\/en-GB\/library\/bb522469.aspx\" target=\"_blank\" rel=\"noopener\">ALTER DATABASE File and Filegroup Options<\/a><\/li>\n<\/ul>\n<p>&nbsp;<br \/>\n&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The new Query Store is one of the most exciting features coming with SQL Server 2016, but unfortunately comes with some limitations. Learn how we can overcome one of those&nbsp; &nbsp; This&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,21,43],"tags":[42,23,60],"_links":{"self":[{"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/posts\/250"}],"collection":[{"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/comments?post=250"}],"version-history":[{"count":0,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/posts\/250\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/media?parent=250"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/categories?post=250"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/tags?post=250"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}