Showing posts with label Scripts. Show all posts
Showing posts with label Scripts. Show all posts

Thursday, May 11, 2017

Find the number of rows for a value, without querying the table

Dear all,

Being a DBA, developers approach me with questions like can you run this query for me in production?

Select *
FROM
[dbo].[FactInternetSales]

WHERE UnitPrice = 32.6

Lets, say the table contains few billion rows and UnitPrice is not an indexed column, then query would take ages to provide the result. Not just that, it causes a massive I/O on a busy production database.  

For the above situation, I would usually ask the developer, "Would you need a accurate value or is it ok if it is approximate?". If approximate numbers are fairly sufficient then, one can answer these type of questions without actually querying the table. How?

Simple - STATISTICS is the word :)

To find the statistic that will be useful for the query, please use the following script and provide the table name and column name. The script provides the statistic name we should be checking.

Declare @table_name varchar(100)
Declare @Column_name varchar(100)

SET @table_name = 'FactInternetSales'
SET @Column_name = 'UnitPrice'

SELECT OBJECT_NAME(s.object_id) AS object_name, 
    COL_NAME(sc.object_id, sc.column_id) AS column_name, 
    s.name AS statistics_name 
FROM sys.stats AS
JOIN sys.stats_columns AS sc 
    ON s.stats_id = sc.stats_id 
AND s.object_id = sc.object_id 
WHERE  
OBJECT_NAME(s.object_id) like @table_name
and    
COL_NAME(sc.object_id, sc.column_id) like @Column_name
ORDER BY s.name;

After finding the statistic name, just go to the table, expand "Statistics" and double click on the stat that you are interested in. The click on the details section, find the value interested in on "Range_HI_Key" Column and observe the "EQ_Rows" section to get the approximate number of rows. 



Most of you would know that statistics can be used to find query estimates but few (I guess) would use it operationally for these kind of requests. One can also use them to estimate for queries of this type too

Select count(*), 
ProductKey 
from [FactInternetSales]
Group by ProductKey

Select count(*) from
[FactInternetSales]
WHERE UnitPrice > 40  -- ( Possible but can get little tricky at times, more of it on upcoming posts :) )

Few quick pointers:

The above method relies on the following prerequisites

* Either Auto create Stats should be turned on or the column should be the leading column of any index for the statistic to be present. Auto Create Stats are on by default

* For the stat to be reasonably accurate, one needs to have Auto Update Statistics on ( which is also "on" by default )

* Automatically created statistics are named like '_WA%'. For a column to have a auto created stat, the column should have been queried at least once since database creation. Indexed column would not need auto created statistics

* To figure out when was the statistic last updated, use the following query 

SELECT 
sp.stats_id, name, filter_definition, last_updated, rows,
rows_sampled, steps, unfiltered_rows, modification_counter  
FROM sys.stats AS stat  
CROSS APPLY sys.dm_db_stats_properties(stat.object_id, stat.stats_id) AS sp 
WHERE stat.object_id = object_id('people_Data')
and name like '_WA_Sys_0000000D_1273C1CD';

If the value you are looking for doesn't appear as a "Range_HI_Key" on the histogram then refer to the next biggest value on the "Range_Hi_Key" and observe "Avg_Range_Rows" for the estimates. Please refer to this post http://strictlysql.blogspot.sg/2016/01/sql-server-statistics-histograms.html for interpreting histograms.

There is always more to statistics. Will cover them in upcoming posts.


Thursday, December 8, 2016

Query Store - Part 4 - Find the plans and queries forced by Query Store

As most of you know, one of the most fundamental uses of query store is to force query plans. Unlike query hints, Query store doesn't demand code a change nor it is complex to implement like plan guides. One can force a plan without make a query / code change and it is pretty simple to achieve the same via user friendly GUI. To figure out how to force a plan, please refer here

But, one of the important things to do, after forcing plans using query store is to track the plans that are being forced. One can do that using the following query

SELECT
qt.query_sql_text,q.query_id,
CAST(query_plan AS XML) AS 'Execution Plan',
rs.first_execution_time, rs.last_execution_time,
rs.count_executions,
rs.avg_duration,
rs.avg_rowcount,
rs.last_logical_io_reads,
rs.avg_cpu_time,
rs.avg_query_max_used_memory, qp.force_failure_count
FROM sys.query_store_plan qp
INNER JOIN sys.query_store_query q
ON qp.query_id = q.query_id
INNER JOIN sys.query_store_query_text qt
ON q.query_text_id = qt.query_text_id
INNER JOIN sys.query_store_runtime_stats rs
ON qp.plan_id = rs.plan_id
WHERE qp.is_forced_plan = 1
Order by rs.last_execution_time

The reasons why one needs to track forced plans are interesting. That will be covered in the next post :)

Wednesday, October 12, 2016

Check Database Server Workload - Batches/ Sec

Today's post is about a simple but very useful query.

One of the basic things one needs to check is the amount of workload on the database server. Amount of workload or finding how busy the server is, can be done by "Batch Requests/sec" performance counter ( I guess most of my well informed readers should know that :) )  "Batch Requests/sec" actually tells us number of batches ( batch of query requests ) that the SQL Server is processing per second

One can check the same by the query below.

SELECT object_name,counter_name,cntr_Value
FROM sys.dm_os_performance_Counters

WHERE counter_name like 'Batch Requests/sec%'

However, the above query actually returns a huge number which makes you wonder if the server is actually processing so many queries a second. The huge number is because of the fact that "sys.dm_os_performance_Counters" actually returns a cumilative value of 'Batch Requests/sec' since last restart. So, the correct way of finding average "batches/sec" or average workload on the server would be

Average Workload Query:

SELECT object_name,counter_name,
cntr_Value / datediff(ss, ( SELECT create_Date FROM
                            sys.databases
                            WHERE name like 'tempdb'),getdate())
                            as Avg_Batches_Per_Sec
FROM sys.dm_os_performance_Counters 
WHERE counter_name like 'Batch Requests/sec%'   

The above query gives us the average workload since last restart. If you are interested just checking the current workload, instead of the average since restart, then

Current Workload Query:

DECLARE @BatchRequests Bigint
SELECT @BatchRequests = cntr_Value
FROM sys.dm_os_performance_Counters
WHERE counter_name like 'Batch Requests/sec%'
WaitFor Delay '00:00:01'
SELECT object_name,counter_name,cntr_Value - @BatchRequests as [Batches/Sec]
FROM sys.dm_os_performance_Counters
WHERE counter_name like 'Batch Requests/sec%'

Though there are no good or bad numbers for Batches/sec counter, a busy server would be expected to have about few thousand batches per second with extremely busy ones reaching 5 digit workloads.

Sunday, August 7, 2016

Current Running Queries

Following script is perhaps the most executed script in my 10 year Database Administration career. The script has been my first line of defense in any performance monitoring problem and has helped me narrow down most of the performance issues.

Many moons ago, I had posted a similar script over here. This is much more enhanced and powerful script with more detailed info. Works from SQL Server 2008 to SQL Server 2016.
SELECT getdate() as dt,
ss.session_id,
db_name(sysprocesses.dbid) as dbname,
er.status as req_status,
ss.login_name,
cs.client_net_address,
ss.program_name,
sysprocesses.open_tran,
er.blocking_session_id,
ss.host_name,
ss.client_interface_name,
[eqp].[query_plan] as qplan,
SUBSTRING(est.text,(er.statement_start_offset/2)+1,
CASE WHEN er.statement_end_offset=-1 OR er.statement_end_offset=0
THEN (DATALENGTH(est.Text)-er.statement_start_offset/2)+1
ELSE (er.statement_end_offset-er.statement_start_offset)/2+1
END) as req_query_text,
er.granted_query_memory,
er.logical_reads as req_logical_reads,
er.cpu_time as req_cpu_time,
er.reads as req_physical_reads,
er.row_count as req_row_count,
er.scheduler_id,
er.total_elapsed_time as req_elapsed_time,
er.start_time as req_start_time,
er.percent_complete,
er.wait_resource as wait_resource,
er.wait_type as req_waittype,
er.wait_time as req_wait_time,
wait.wait_duration_ms as blocking_time_ms,
lock.resource_associated_entity_id,
lock.request_status as lock_request_status,
lock.request_mode as lock_mode,
er.writes as req_writes,
sysprocesses.lastwaittype,
fn_sql.text as session_query,
ss.status as session_status,
ss.cpu_time as session_cpu_time,
ss.reads as session_reads,
ss.writes as session_writes,
ss.logical_reads as session_logical_reads,
ss.memory_usage as session_memory_usage,
ss.last_request_start_time,
ss.last_request_end_time,
ss.total_scheduled_time as session_scheduled_time,
ss.total_elapsed_time as session_elpased_time,
ss.row_count as session_rowcount
FROM sys.dm_exec_sessions ss
INNER JOIN sys.dm_exec_connections cs
ON ss.session_id = cs.session_id
OUTER APPLY
fn_get_sql(cs.most_recent_sql_handle) as fn_sql
INNER JOIN sys.sysprocesses
ON sys.sysprocesses.spid = cs.session_id
LEFT OUTER JOIN sys.dm_exec_requests [er]
ON er.session_id = ss.session_id
OUTER APPLY sys.dm_exec_sql_text ([er].[sql_handle]) [est]
OUTER APPLY sys.dm_exec_query_plan ([er].[plan_handle]) [eqp]
LEFT OUTER JOIN
sys.dm_os_waiting_tasks wait
ON er.session_id = wait.session_id
and wait.wait_type like 'LCK%' and
er.blocking_session_id = wait.blocking_session_id
LEFT OUTER JOIN sys.dm_tran_locks lock
ON lock.lock_owner_address = wait.resource_address
and lock.request_session_id = er.blocking_session_id
WHERE ss.status != 'sleeping'

Yes, a long query but perhaps the most useful one of all my scripts.

What does the script do?

Gives the details of about the active sessions and queries

Script gives us 2 types of information. 

1) Session related details - Information about the active sessions

2) Request related details - Information about the query that is active in each session

Request related details are lot more of interest.

Key columns include

1) req_query_text - Query being run
2) req_status - Status of the query ( "Running" / "Suspended"/ "Runnable" )
3) Query Plan - Execution plan of the query
4) Cpu_time - Time spent by the query on CPU
5) req_elapsed_time - How long the has been running - in milliseconds
6) req_waittype - Wait type query was waiting for
7) req_wait_time - Waiting time in ms
8) blocking_session_id - Session Id blocking it
9) Connection details - IP Address, Application, hostname etc
10) Other Resource utilization details - reads, writes, query memory, row count etc.

 How to make good use of it is for another day in another post :)