{"id":256,"date":"2016-07-06T09:52:51","date_gmt":"2016-07-06T08:52:51","guid":{"rendered":"https:\/\/sqldoubleg.live-website.com\/?p=256"},"modified":"2016-07-06T11:42:09","modified_gmt":"2016-07-06T10:42:09","slug":"sql-server-2016-query-store-user-data-or-metadata","status":"publish","type":"post","link":"https:\/\/www.sqldoubleg.com\/es\/2016\/07\/06\/sql-server-2016-query-store-user-data-or-metadata\/","title":{"rendered":"SQL Server 2016 Query Store, User Data or Metadata?"},"content":{"rendered":"<p>One of the newest features for SQL Server is the Query Store, let&#8217;s explore it to know a bit more about it&nbsp;It&#8217;s been a month since the SQL Server community has their new toy, SQL Server 2016, which to my surprise and considering not everyone has yet adopted SQL 2014, it&#8217;s absolutely packed with new features.<\/p>\n<p>One of this features is the Query Store which promises will help troubleshooting performance issues by spotting queries which can be misbehaving due to something called \u00abplan regression\u00bb.<\/p>\n<p>But even though this is a SQL Server feature and can be accessed using DMO&#8217;s, there are some characteristics that makes me think we should treat it as <strong>User Data instead of metadata<\/strong> and I will give 5 reasons why<\/p>\n<p>&nbsp;<br \/>\n<strong>1. You can optionally enable or disable<\/strong><\/p>\n<p>By default the query store is disabled but it can be enabled for the [model] database, although that&#8217;s something I wouldn&#8217;t recommend. But there is a way a brand new database have this feature enabled if we want to.<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nUSE model\r\nGO\r\n\r\nALTER DATABASE model SET QUERY_STORE = ON\r\nGO\r\n\r\nSELECT *\r\nFROM sys.database_query_store_options  \r\nGO\r\n<\/pre>\n<p><a href=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/07\/01_enable_Query_Store_model_database.png\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/07\/01_enable_Query_Store_model_database.png\" alt=\"01_enable_Query_Store_model_database\" width=\"888\" height=\"127\" class=\"aligncenter size-full wp-image-257\" srcset=\"https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/01_enable_Query_Store_model_database.png 888w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/01_enable_Query_Store_model_database-300x43.png 300w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/01_enable_Query_Store_model_database-150x21.png 150w\" sizes=\"(max-width: 888px) 100vw, 888px\" \/><\/a><\/p>\n<p>You can see there is nothing returned by the query although this setting will actually be part of new databases. To double check, let&#8217;s create a database without any options which would give us a database exactly as [model] is<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nUSE master\r\nGO\r\n\r\nCREATE DATABASE QS_on\r\nGO\r\n\r\nUSE QS_on\r\nGO\r\n\r\nSELECT *\r\nFROM sys.database_query_store_options  \r\nGO\r\n<\/pre>\n<p><a href=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/07\/02_new_database_Query_Store_state.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/07\/02_new_database_Query_Store_state.png\" alt=\"02_new_database_Query_Store_state\" width=\"892\" height=\"115\" class=\"aligncenter size-full wp-image-258\" \/><\/a><\/p>\n<p>There is our Query Store enabled, in case we want to do it afterwards, the way of enabling it in its simplest way as we have seen already would be <\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nALTER DATABASE [dbname] SET QUERY_STORE = ON\r\nGO\r\n<\/pre>\n<p>&nbsp;<br \/>\n<strong>2. You can define how big it can grow<\/strong><\/p>\n<p>There are some other comparable DMO&#8217;s which hold statistical information like <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms188755.aspx\" target=\"_blank\">[sys].[dm_db_index_usage_stats]<\/a>, <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/cc280701.aspx\" target=\"_blank\">[sys].[dm_exec_procedure_stats]<\/a> or <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms189741.aspx\" target=\"_blank\">[sys].[dm_exec_query_stats]<\/a> but there is no way a DBA can decide how much information they can hold. <\/p>\n<p>This is possible for the query store by using one of the options provided<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nALTER DATABASE [dbname] SET QUERY_STORE = ON (MAX_STORAGE_SIZE_MB = number)\r\nGO\r\n<\/pre>\n<p>It&#8217;s good to remind that once we have reached the limit, the Query Store silently will be set to READ_ONLY and new information will not be collected, so keep an eye on it!<\/p>\n<p>&nbsp;<br \/>\n<strong>3. You can delete the data in a simple way<\/strong><\/p>\n<p>Some others DMV&#8217;s allow us to remove their data using DBCC commands, but usually that requires elevated privileges most DBA&#8217;s do not want to grant lightly.<\/p>\n<p>For cleaning sys.dm_exec_procedure_stats and sys.dm_exec_query_plan we can use <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms174283.aspx\" target=\"_blank\">DBCC FREEPROCCACHE<\/a><\/p>\n<p>For cleaning sys.dm_os_latch_stats and sys.dm_os_wait_stats we can use <a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms189768.aspx\" target=\"_blank\">DBCC SQLPERF<\/a><\/p>\n<p>Unfortunately for clearing sys.dm_db_index_usage_stats we don&#8217;t have any specific statement, although we can detach and attach, SET READ_ONLY and READ_WRITE or for all of them just restart our server.<\/p>\n<p>For the Query Store we have this ALTER DATABASE option which seems more appropriate than DBCC commands<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nALTER DATABASE [dbname] SET QUERY_STORE CLEAR\r\nGO\r\n<\/pre>\n<p>&nbsp;<br \/>\n<strong>4. Query Store data is backed up and restored along the rest of user data<\/strong><\/p>\n<p>Other metadata like the DMV&#8217;s shown above is not backed up together with the user data, but this is not the case for the Query Store data.<\/p>\n<p>Let&#8217;s create some data and run some queries<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nUSE QS_on\r\nGO\r\n\r\nCREATE TABLE dbo.t(\r\nc1 INT IDENTITY PRIMARY KEY\r\n)\r\nGO\r\n\r\nINSERT INTO dbo.t DEFAULT VALUES \r\nGO 10\r\n\r\nSELECT * FROM dbo.t WHERE c1 = 1\r\nSELECT * FROM dbo.t\r\nGO 5\r\n\r\nSELECT q.query_id, qt.query_sql_text, q.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 TOP 1 count_executions FROM sys.query_store_runtime_stats WHERE plan_id = qp.plan_id ORDER BY runtime_stats_id DESC) AS qr\r\nGO\r\n\r\nSELECT OBJECT_NAME(object_id), * \r\n\tFROM sys.dm_db_index_usage_stats \r\n\tWHERE database_id = DB_ID()\r\nGO\r\n<\/pre>\n<p><a href=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/07\/03_new_database_Query_Store_metadata.png\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/07\/03_new_database_Query_Store_metadata.png\" alt=\"03_new_database_Query_Store_metadata\" width=\"977\" height=\"329\" class=\"aligncenter size-full wp-image-260\" srcset=\"https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/03_new_database_Query_Store_metadata.png 977w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/03_new_database_Query_Store_metadata-300x101.png 300w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/03_new_database_Query_Store_metadata-150x51.png 150w\" sizes=\"(max-width: 977px) 100vw, 977px\" \/><\/a><\/p>\n<p>We can see our 10 INSERT and 5 of each SELECT, scan and seeks. If now we take a backup and restore the database, only the metadata which belongs to the Query Store will be back when restoring the data, this might be extremely useful as we can do troubleshooting in a different server than production but looking at production execution plans and stats.<\/p>\n<p>If now we back up and restore our database, only the Query Store metadata will come back, let&#8217;s see it<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\n\r\nUSE master\r\nGO\r\n\r\nBACKUP DATABASE QS_on TO DISK = 'QS_on.bak' WITH INIT\r\nGO\r\n\r\nRESTORE DATABASE QS_on FROM DISK = 'QS_on.bak' WITH RECOVERY, REPLACE\r\nGO\r\n\r\nUSE QS_on\r\nGO\r\n\r\nSELECT q.query_id, qt.query_sql_text, q.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 TOP 1 count_executions FROM sys.query_store_runtime_stats WHERE plan_id = qp.plan_id ORDER BY runtime_stats_id DESC) AS qr\r\nGO\r\n\r\nSELECT OBJECT_NAME(object_id), * \r\n\tFROM sys.dm_db_index_usage_stats \r\n\tWHERE database_id = DB_ID()\r\nGO\r\n<\/pre>\n<p><a href=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/07\/04_Query_Store_metadata_after_restoredb.png\" target=\"_blank\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/07\/04_Query_Store_metadata_after_restoredb.png\" alt=\"04_Query_Store_metadata_after_restoredb\" width=\"896\" height=\"313\" class=\"aligncenter size-full wp-image-261\" srcset=\"https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/04_Query_Store_metadata_after_restoredb.png 896w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/04_Query_Store_metadata_after_restoredb-300x105.png 300w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/04_Query_Store_metadata_after_restoredb-150x52.png 150w\" sizes=\"(max-width: 896px) 100vw, 896px\" \/><\/a><\/p>\n<p>&nbsp;<br \/>\n<strong>5. Query Store become READ_ONLY when the database goes READ_ONLY<\/strong><\/p>\n<p>This point is important, because other metadata will keep working when the database goes READ_ONLY, but the query store won&#8217;t.<\/p>\n<p>You can find more information about this behaviour in a recent post by Kendra Little (<a href=\"http:\/\/www.littlekendra.com\/\" target=\"_blank\">b<\/a>|<a href=\"https:\/\/twitter.com\/kendra_little\" target=\"_blank\">t<\/a>) where she shows that <a href=\"http:\/\/www.littlekendra.com\/2016\/06\/21\/will-query-store-work-in-a-read-only-database\/\" target=\"_blank\">in order to get the new Query Store the database must be read-write<\/a>, which I encourage you to read it.<\/p>\n<p>To demonstrate it, let&#8217;s clear the Query Store and set the database to READ_ONLY before running a few queries<\/p>\n<pre class=\"brush: tsql; title: ; notranslate\" title=\"\">\r\nALTER DATABASE QS_on SET QUERY_STORE CLEAR \r\nGO\r\n\r\nALTER DATABASE QS_on SET READ_ONLY WITH ROLLBACK IMMEDIATE\r\nGO\r\n\r\nSELECT * FROM dbo.t WHERE c1 = 1\r\nSELECT * FROM dbo.t\r\nGO 5\r\n\r\n\r\nSELECT q.query_id, qt.query_sql_text, q.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 TOP 1 count_executions FROM sys.query_store_runtime_stats WHERE plan_id = qp.plan_id ORDER BY runtime_stats_id DESC) AS qr\r\nGO\r\n\r\nSELECT OBJECT_NAME(object_id), * \r\n\tFROM sys.dm_db_index_usage_stats \r\n\tWHERE database_id = DB_ID()\r\nGO\r\n\r\n<\/pre>\n<p><a href=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/07\/05_Query_Store_READ_ONLY.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/sqldoubleg.live-website.com\/wp-content\/uploads\/2016\/07\/05_Query_Store_READ_ONLY.png\" alt=\"05_Query_Store_READ_ONLY\" width=\"916\" height=\"314\" class=\"aligncenter size-full wp-image-262\" srcset=\"https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/05_Query_Store_READ_ONLY.png 916w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/05_Query_Store_READ_ONLY-300x103.png 300w, https:\/\/www.sqldoubleg.com\/wp-content\/uploads\/2016\/07\/05_Query_Store_READ_ONLY-150x51.png 150w\" sizes=\"(max-width: 916px) 100vw, 916px\" \/><\/a><\/p>\n<p>So you can see how the counters for [dm_db_index_usage_stats] keep counting while the Query Store freezes.<\/p>\n<p>&nbsp;<br \/>\n<strong>Conclusion<\/strong><\/p>\n<p>Although it is <a href=\"https:\/\/msdn.microsoft.com\/en-GB\/library\/dn817826.aspx\" target=\"_blank\">documented and classified as metadata<\/a>, the Query Store information behaves like user data in many aspects, which is something to take into account to provision enough resources to keep it working.<\/p>\n<p>I&#8217;m already working on a new post where I explain how to overcome the limitation and have the Query Store working for \u00abREAD_ONLY\u00bb databases, so stay tuned!<\/p>\n<p>Thanks for reading!<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>One of the newest features for SQL Server is the Query Store, let&#8217;s explore it to know a bit more about it&nbsp;It&#8217;s been a month since the SQL Server community has their&#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],"tags":[32,42,5,23],"_links":{"self":[{"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/posts\/256"}],"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=256"}],"version-history":[{"count":1,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/posts\/256\/revisions"}],"predecessor-version":[{"id":263,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/posts\/256\/revisions\/263"}],"wp:attachment":[{"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/media?parent=256"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/categories?post=256"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqldoubleg.com\/es\/wp-json\/wp\/v2\/tags?post=256"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}