I would like to set up a SQL Server 2008 Alert to notify me when any procedure executes for 1 second or longer (just an example).
Any ideas?
EDIT:
Okay, it seems this is not possible. But, just to nudge you in another direction, I know that there are statistics tables in the master database which hold compilation counts, number of calls and other various statistics. Could I maybe periodically query them and then report on it, somehow?
no there are no notifications for this.
you'll have to set up a trace and poll it every now and then.
I'd like to add to Mladen Prajdic's correct answer and to improve upon kevchadders' answer from SQL Server Central. The solution I propose below uses DBMail instead of SQLMail (which is used by SQLServerCentral's solution by way of the xp_sendmail call). Essentially, SQLMail uses MAPI and is harder to setup and DBMail uses SMTP and is easier to setup. Here's more information about the difference between the two.
I could not get SQL Server Central's solution to work in SQL 2005 and the solution below has only been tested on my installation of SQL 2005 - YMMV based on version.
First, you'll need a new UDF, which will translate the job id into the process id for a JOIN:
CREATE FUNCTION dbo.udf_SysJobs_GetProcessid(#job_id uniqueidentifier)
RETURNS VARCHAR(8)
AS
BEGIN
RETURN (substring(left(#job_id,8),7,2) +
substring(left(#job_id,8),5,2) +
substring(left(#job_id,8),3,2) +
substring(left(#job_id,8),1,2))
END
And then the sproc:
CREATE PROC sp_check_job_running
#job_name char(50),
#minutes_allowed int,
#person_to_notify varchar(50)
AS
DECLARE #minutes_running int,
#message_text varchar(255)
SELECT #minutes_running = isnull(DATEDIFF(mi, p.last_batch, getdate()), 0)
FROM master..sysprocesses p
JOIN msdb..sysjobs j ON dbo.udf_sysjobs_getprocessid(j.job_id) = substring(p.program_name,32,8)
WHERE j.name = #job_name
IF #minutes_running > #minutes_allowed
BEGIN
SELECT #message_text = ('Job ' + UPPER(SUBSTRING(#job_name,1,LEN(#job_name))) + ' has been running for ' + SUBSTRING(CAST(#minutes_running AS char(5)),1,LEN(CAST(#minutes_running AS char(5)))) + ' minutes, which is over the allowed run time of ' + SUBSTRING(CAST(#minutes_allowed AS char(5)),1,LEN(CAST(#minutes_allowed AS char(5)))) + ' minutes.')
EXEC msdb.dbo.sp_send_dbmail
#recipients = #person_to_notify,
#body = #message_text,
#subject = 'Long-Running Job to Check'
END
This sproc can be easily scheduled as a SQL Server Agent job or any other method necessary. It takes no action if the job is not running or it is running within specified parameters. It sends the email if the job has run longer than specified.
e.g.
EXEC sp_check_job_running 'JobNameGoesHere', 5, 'admin#mycompany.com'
HT: http://www.sqlserverspecialists.co.uk/blog/_archives/2008/11/26/3996346.html
If found this on sqlservercentral (it probably for an older version of SQL Server, but still might be relevant to 2008)
Alert Procedure for Long-Running Job
http://www.sqlservercentral.com/scripts/Lock+and+Connection+Management/30144/
Text below if you cant access it.
For jobs that run periodically and should take only a short time to run, a DBA may want to know when the job has been running for an excessive time. In this case, just checking to see IF the job is running won't do; the ability to make sure that it hasn't been running for a long period is needed. Matching the job id to a process id in sysprocesses requires some re-arranging of the job id to do the match. This script creates a stored procedure that will accept a job name, maximum run time allowed, and email address to notify. It will then use the job name to re-string the job number and check sysprocesses (based on the process id being part of the program name) to determine the amount of time the job has been runing, then alert if that time is over the "time allowed" parameter.
CREATE proc sp_check_job_running
#job_name char(50),
#minutes_allowed int,
#person_to_notify varchar(50)
AS
DECLARE #var1 char(1),
#process_id char(8),
#job_id_char char(8),
#minutes_running int,
#message_text varchar(255)
select #job_id_char = substring(CAST(job_id AS char(50)),1,8)
from msdb..sysjobs
where name = #job_name
select #process_id = substring(#job_id_char,7,2) +
substring(#job_id_char,5,2) +
substring(#job_id_char,3,2) +
substring(#job_id_char,1,2)
select #minutes_running = DATEDIFF(minute,last_batch, getdate())
from master..sysprocesses
where program_name LIKE ('%0x' + #process_id +'%')
if #minutes_running > #minutes_allowed
BEGIN
select #message_text = ('Job '
+ UPPER(SUBSTRING(#job_name,1,LEN(#job_name)))
+ ' has been running for '
+ SUBSTRING(CAST(#minutes_running AS char(5)),1,LEN(CAST(#minutes_running AS char(5))))
+ ' minutes, which is over the allowed run time of '
+ SUBSTRING(CAST(#minutes_allowed AS char(5)),1,LEN(CAST(#minutes_allowed AS char(5))))
+ ' minutes.')
EXEC master..xp_sendmail
#recipients = #person_to_notify,
#message = #message_text,
#subject = 'Long-Running Job to Check'
END
-- Typical job step syntax for job to do the checking
execute sp_check_job_running
'JobThatSHouldBeDoneIn5Minutes',
5,
'DBAdmin#mycompany.com'
You could use a monitoring software like Nagios to periodically count the number of slow queries in your database.
I personally use the following query with the excellent PRTG. I set it to send me an email each time I have more than 5 queries with an execution time greater than 3 seconds each, or when the slowest query takes longer than 10 seconds:
SELECT total_elapsed_time / execution_count / 1000000 avg_elapsed_time, execution_count, creation_time, last_execution_time,
SUBSTRING(st.text, (qs.statement_start_offset/2) + 1, ((CASE statement_end_offset WHEN -1 THEN DATALENGTH(st.text) ELSE qs.statement_end_offset END - qs.statement_start_offset)/2) + 1) AS statement_text
FROM sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st
WHERE total_elapsed_time / execution_count / 1000000 >= 3 -- filter queries that takes > 3 seconds
AND CHARINDEX('dm_exec_query_stats', st.text) = 0 -- In case this query is slow, we don't want it in the result set!
ORDER BY total_elapsed_time / execution_count DESC;
Disclaimer: this query is based on https://stackoverflow.com/a/820252/638040
Now if you want to monitor your Stored Procedures, then just use sys.dm_exec_procedure_stats instead of sys.dm_exec_sql_text (also remove the field creation_time from the selection and tweak the SUBSTRING on st.text)
You can log slow running queries use SQL Profiler, but that won't help you with an alert, specifically. (I suppose you could log to a File or to a database table, and have something else "listen" and raise an alert).
In case you are not familiar with SQL Profiler here are the steps:
You should run SQL Profiler on something other than the server itself.
Start with the SQLProfilerTSQL_Duration template
Set the Filter : Duration : "Greater than" to 1000 (milliseonds)
In the filters you can restrict the Database, Login, or similar if necessary.
In the Events you can restrict to just SProcs (default also includes SQL statements)
If you are not looking for a free solution, you could take a look at SQL Sentry. It does what you ask for and more.
Related
On SQL Server, I am testing to create a Plan guide to force a query from Java to use a specific optimal execution-plan always. Some how the SQL query not using this Plan Guide
I followed, https://technet.microsoft.com/en-us/library/ms190772%28v=sql.105%29.aspx?f=255&MSPPError=-2147217396
SQL comes from Java,
declare #p1 int
set #p1=1986
exec sp_prepexec #p1 output,N'#P0 nvarchar(4000)',N'SELECT Top 1 Amount,col2 from dbo.table1
where ExId=#P0
order by id
',N'0a8e8e31-2a05-0000-8ece-0003fd69e692'
select #p1
Plan Guide I created,
DECLARE #xml_showplan nvarchar(max);
SET #xml_showplan = (select cast(query_plan as nvarchar(max)) from sys.dm_exec_cached_plans cp
cross apply sys.dm_exec_sql_text (cp.plan_handle) st
cross apply sys.dm_exec_query_plan (cp.plan_handle) qp
where st.text like '%Top 1 Amount%'
and objtype='Prepared')
--select (#xml_showplan)
EXEC sp_create_plan_guide
#name = N'ForceuserLoanAmountRequests',
#stmt = N'SELECT Top 1 Amount,col2 from dbo.table1 where ExId=#P0 order by id',
#type = N'SQL',
#module_or_batch = NULL,
#params = N'#P0 nvarchar(4000)',
#hints = #xml_showplan;
Appreciate if you can help me to get thru the plan guide used by SQL query from java.
Created a Plan Guide with the SQL collected from SQL Profiler,
EXEC sp_create_plan_guide
#name = N'ForceuserLoanAmountRequests',
#stmt = N'SELECT Top 1 Amount,col2 from table1
where ExId=#P0
order by ID
',
#type = N'SQL',
#module_or_batch = NULL,
#params = N'#P0 nvarchar(4000)',
#hints = #xml_showplan;
GO
Please read the article sp_create_plan_guide (Transact-SQL) pay attention to Remarks:
Plan Guide Matching Requirements
For plan guides that specify #type = 'SQL' or #type = 'TEMPLATE' to
successfully match a query, the values for batch_text and
#parameter_name data_type [,...n ] must be provided in exactly the
same format as their counterparts submitted by the application. This
means you must provide the batch text exactly as the SQL Server
compiler receives it. To capture the actual batch and parameter text,
you can use SQL Server Profiler. For more information, see Using SQL
Server Profiler to Create and Test Plan Guides.
This means that the statement should exactly match to what is written in your plan guide, but in your code plan guide is created for a statement that differs from those sending by java: it has only one row while java code has 4 rows with CR and additional spaces.
If you evaluate any hash from java statement and your statement provided in sp_create_plan_guide you'll see they are different.
Another thing I suspect that may be wrong is how you get a "good plan". Since you just assign #xml_showplan the result of a query that can produce many rows, I'm not sure you get the plan you want. How do you ensure that you capture the "good£ plan and not the same plan that java code produced?
I have a job set up on a handful of servers (all in the same domain). The job sends an email via sp_send_dbmail, and the subject of the email should look like the following format:
=servername (ip address)= Weekly DB Backup to Azure Report
So as a potential example (obviously replace the 0s with actual IP address of the server SQL is running on):
=SQLMACHINE (000.000.000.00)= Weekly Backup to Azure Report
DBmail is configured, and I created the backup job. The T-SQL job step that sends the email has the following script:
SET NOCOUNT ON
DECLARE #ipAddress NVARCHAR(100)
SELECT #ipAddress = local_net_address
FROM sys.dm_exec_connections
WHERE Session_id = ##SPID;
DECLARE #subjectText NVARCHAR(255) = N'=' +
CAST(LEFT(##SERVERNAME, CHARINDEX('\', ##SERVERNAME)-1) AS NVARCHAR) + N'.' +
CAST(DEFAULT_DOMAIN() AS NVARCHAR) + N' ('+ #ipAddress +
N')= Weekly DB Backup to Azure Report'
DECLARE #tableHTML NVARCHAR(MAX) = N'this part works fine'
exec msdb.dbo.sp_send_dbmail #profile_name = 'Production Mail',
#recipients = 'xxx#xxx.com',
#subject = #subjectText,
#body = #tableHTML,
#body_format = 'HTML'
Each of the 5 servers has the same exact job definition - I have 1 source-controlled definition of the job that I use to create the job on each server.
Each week when the jobs run, most of them kick off an email with the expected subject line. Every couple weeks though, an email comes through with the subject SQL Server Message, which is what happens when no subject has been specified. Each time this happens, it could be on any one of the five servers. I'm not sure what's happening, since it should have a subject each time it executes.
EDIT:
This is happening because the #ipAddress variable is null. No idea why
SELECT #ipAddress = local_net_address
FROM sys.dm_exec_connections
WHERE Session_id = ##SPID;
would return null though...
local_net_address will be always NULL if a connection is not using the TCP transport provider. It's likely you use 'Shared memory' in net_transport.
You can force to use TCP when you create a connection, so local_net_address will be populated. E.g. when you open SSMS, you can specify the server name as "tcp:ServerName\InstanceName"
Below also can be used to retrieve the server properties (using TCP transport):
SELECT
CONNECTIONPROPERTY('net_transport') AS net_transport,
CONNECTIONPROPERTY('protocol_type') AS protocol_type,
CONNECTIONPROPERTY('auth_scheme') AS auth_scheme,
CONNECTIONPROPERTY('local_net_address') AS local_net_address,
CONNECTIONPROPERTY('local_tcp_port') AS local_tcp_port,
CONNECTIONPROPERTY('client_net_address') AS client_net_address
Your #ipAddress value could be null
Replace this code:
+ #ipAddress +
with this code
+ ISNULL(#ipAddress,'0.0.0.0') +
to prove that is the issue.
While working on a database documentation topic, I encountered a situation and got stuck. Thanks in advance for potential help. Here are the facts:
I am trying to obtain only the body of certain stored procedures in my database.
Anything else, such as SP parameters or options - I don't need.
Googled around and all I've found is ways to obtain the entire SP text - most of them already known.
I've put together a solution as you can see below but it's not covering all the cases and it's not pretty.
Having defined this test SP:
CREATE PROCEDURE dbo.returnDay
#addTheseDays SMALLINT = 0
AS
-- This is just a test SP that retrieves
-- the current date if #addTheseDays isn't defined,
-- otherwise the current day + #addTheseDays
SELECT GETDATE() + #addTheseDays;
GO
What didn't help:
-- This doesn't help since it retrieves all SP text (including parameters and options part)
EXEC sp_helptext 'dbo.returnDay';
-- The ROUTINE_DEFINITION column also holds the entire SP text.
SELECT *
FROM INFORMATION_SCHEMA.ROUTINES;
Workaround I've done and works, with exceptions:
DECLARE #spText VARCHAR(MAX)
SELECT #spText = object_definition(object_id('dbo.returnDay'))
SELECT SUBSTRING(#spText, CHARINDEX('AS', #spText, 0) + 2 , LEN(#spText)) AS spBody
This "ugly" string manipulation workaround works but only when the SP does not have "WITH EXECUTE AS CALLER" option or the parameters don't have "AS" as part of their name. In these cases then I get extra, unneeded info regarding the SP (again, only need SP body - only what is between the AS and batch terminator).
Also tried to use the first BEGIN and last END in the SP body (and get what's between) but since these are not mandatory in SQL Server and some SPs don't have them then I can't rely on them.
Any ideas and/or suggestions on how can I get only the SP body (code and comments) in a better way?
As a quick and dirty one-off you can look for AS as a single word, taking into account the single EXECUTE AS exception.
This will fail for any comments containing -AS- of course.
SELECT SUBSTRING(
#spText,
PATINDEX('%[ ' + CHAR(13) + CHAR(10) + CHAR(9) + ']AS[ ' + CHAR(13) + CHAR(10) + CHAR(9) + ']%', REPLACE(#spText, 'WITH EXECUTE AS CALLER', 'WITH EXECUTE ?? CALLER')) + 3,
LEN(#spText))
You will need parse the T-SQL sproc to obtain just the body. Have a look at RegEx to parse stored procedure and object names from DDL in a .sql file C#. There are several CLR assemblies that give you regex access within SQL, or perform in your application language if not purely SQL for your application.
We've a stored procedure that happens to build up some dynamic SQL and execute via a parametrised call to sp_executesql.
Under normal conditions, this works wonderfully, and has made a large benefit in execution times for the procedure (~8 seconds to ~1 second), however, under some unknown conditions, something strange happens, and performance goes completely the other way (~31 seconds), but only when executed via RPC (i.e. a call from a .Net app with the SqlCommand.CommandType of CommandType.StoredProcedure; or as a remote query from a linked server) - if executed as a SQL Batch using SQL Server Management Studio, we do not see the degradation in performance.
Altering the white-space in the generated SQL and recompiling the stored procedure, seems to resolve the issue at least in the short term, but we'd like to understand the cause, or ways to force the execution plans to be rebuilt for the generated SQL; but at the moment, I'm not sure how to proceed with either?
To illustrate, the Stored Procedure, looks a little like:
CREATE PROCEDURE [dbo].[usp_MyObject_Search]
#IsActive AS BIT = NULL,
#IsTemplate AS BIT = NULL
AS
DECLARE #WhereClause NVARCHAR(MAX) = ''
IF #IsActive IS NOT NULL
BEGIN
SET #WhereClause += ' AND (svc.IsActive = #xIsActive) '
END
IF #IsTemplate IS NOT NULL
BEGIN
SET #WhereClause += ' AND (svc.IsTemplate = #xIsTemplate) '
END
DECLARE #Sql NVARCHAR(MAX) = '
SELECT svc.[MyObjectId],
svc.[Name],
svc.[IsActive],
svc.[IsTemplate]
FROM dbo.MyObject svc WITH (NOLOCK)
WHERE 1=1 ' + #WhereClause + '
ORDER BY svc.[Name] Asc'
EXEC sp_executesql #Sql, N'#xIsActive BIT, #xIsTemplate BIT',
#xIsActive = #IsActive, #xIsTemplate = #IsTemplate
With this approach, the query plan will be cached for the permutations of NULL/not-NULL, and we're getting the benefit of cached query plans. What I don't understand is why it would use a different query plan when executed remotely vs. locally after "something happens"; I also don't understand what the "something" might be?
I realise I could move away from parametrisation, but then we'd lose the benefit of caching what are normally good execution plans.
I would suspect parameter sniffing. If you are on SQL Server 2008 you could try including OPTIMIZE FOR UNKNOWN to minimise the chance that when it generates a plan it does so for atypical parameter values.
RE: What I don't understand is why it would use a different query plan when executed remotely vs. locally after "something happens"
When you execute in SSMS it won't use the same bad plan because of different SET options (e.g. SET ARITHABORT ON) so it will compile a new plan that works well for the parameter values you are currently testing.
You can see these plans with
SELECT usecounts, cacheobjtype, objtype, text, query_plan, value as set_options
FROM sys.dm_exec_cached_plans
CROSS APPLY sys.dm_exec_sql_text(plan_handle)
CROSS APPLY sys.dm_exec_query_plan(plan_handle)
cross APPLY sys.dm_exec_plan_attributes(plan_handle) AS epa
where text like '%FROM dbo.MyObject svc WITH (NOLOCK)%'
and attribute='set_options'
Edit
The following bit is just in response to badbod99's answer
create proc #foo #mode bit, #date datetime
as
declare #Sql nvarchar(max)
if(#mode=1)
set #Sql = 'select top 0 * from sys.objects where create_date < #date /*44FC79BD-2AF5-4774-9674-04D6C3D4B228*/'
else
set #Sql = 'select top 0 * from sys.objects where modify_date < #date /*44FC79BD-2AF5-4774-9674-04D6C3D4B228*/'
EXEC sp_executesql #Sql, N'#date datetime',
#date = #date
go
declare #d datetime
set #d = getdate()
exec #foo 0,#d
exec #foo 1, #d
SELECT usecounts, cacheobjtype, objtype, text, query_plan, value as set_options
FROM sys.dm_exec_cached_plans
CROSS APPLY sys.dm_exec_sql_text(plan_handle)
CROSS APPLY sys.dm_exec_query_plan(plan_handle)
cross APPLY sys.dm_exec_plan_attributes(plan_handle) AS epa
where text like '%44FC79BD-2AF5-4774-9674-04D6C3D4B228%'
and attribute='set_options'
Returns
Recompilation
Any time the execution of the SP would be significantly different due to conditional statements the execution plan which was cached from the last request may not be optimal for this one.
It's all about when SQL compiles the execution plan for the SP. They key section regarding sp compilation on Microsoft docs is this:
... this optimization occurs automatically the first time a stored procedure is run after SQL Server is restarted. It also occurs if an underlying table that is used by the stored procedure changes. But if a new index is added from which the stored procedure might benefit, optimization does not occur until the next time that the stored procedure is run after SQL Server is restarted. In this situation, it can be useful to force the stored procedure to recompile the next time that it executes
SQL does recompile execution plans at times, from Microsoft docs
SQL Server automatically recompiles stored procedures and triggers when it is advantageous to do this.
... but it will not do this with each call (unless using WITH RECOMPILE), so if each execution could be resulting in different SQL, you may be stuck with the same old plan for at least one call.
RECOMPILE query hint
The RECOMPILE query hint, takes into account your parameter values when checking what needs to be recompiled at the statement level.
WITH RECOMPILE option
WITH RECOMPILE (see section F) will cause the execution plan to be compiled with each call, so you will never have a sub-optimal plan, but you will have the compilation overhead.
Restructure into multiple SPs
Looking at your specific case, the execution plan for the proc never changes and the 2 sql statements should have prepared execution plans.
I would suggest that restructuring the code to split the SPs rather than have this conditional SQL generation would simplify things and ensure you always have the optimal execution plan without any SQL magic sauce.
I'd like if its possible to work out from inside sql server how long sql server has been running.
Would like to use this in conjunction with one of the DMV's for unused indexes, but the counters are re-set every time sql server loads, so I'd like to know how useful they're going to be.
SELECT
login_time
FROM
sys.dm_exec_sessions
WHERE
session_id = 1
will give you a datetime for when the server was started.
To get it programmatically, you can run this script. It checks the creation time of your tempdb, since tempdb gets reinitialized every time Sql Server is started.
SELECT create_date
FROM sys.databases
WHERE name = 'tempdb'
To make it more intuitive, you can run the script below, which will tell you how many days and hours Sql Server has been running. Minutes and seconds information will be truncated. If you need that, modify the script to get it yourself.
SELECT 'Sql Server Service has been running for about '
+ CAST((DATEDIFF(hh, create_date, GETDATE()))/24 AS varchar(3)) + ' days and '
+ CAST((DATEDIFF(hh, create_date, GETDATE())) % 24 AS varchar(2)) + ' hours'
FROM sys.databases
WHERE name = 'tempdb'
Source: How long SQL Server has been running
SELECT crdate FROM sysdatabases WHERE [name] = 'tempdb'
The above will work on SQL Server 2000, 2005 and 2008.
The logic is that the result from the above SQL returns the created date of the tempdb database, which SQL Server recreates every time it is restarted. Hence, the created date of tempdb is the startup time of the server.
I know this is super old, but Microsoft has added a new DMV with this information since this question was asked.
SELECT dosi.sqlserver_start_time
FROM sys.dm_os_sys_info AS dosi
Grabbed here
USE Master
GO
SET NOCOUNT ON
DECLARE #crdate DATETIME, #hr VARCHAR(50), #min VARCHAR(5)
SELECT #crdate=crdate FROM sysdatabases WHERE NAME='tempdb'
SELECT #hr=(DATEDIFF ( mi, #crdate,GETDATE()))/60
IF ((DATEDIFF ( mi, #crdate,GETDATE()))/60)=0
SELECT #min=(DATEDIFF ( mi, #crdate,GETDATE()))
ELSE
SELECT #min=(DATEDIFF ( mi, #crdate,GETDATE()))-((DATEDIFF( mi, #crdate,GETDATE()))/60)*60
PRINT 'SQL Server "' + CONVERT(VARCHAR(20),SERVERPROPERTY('SERVERNAME'))+'" is Online for the past '+#hr+' hours & '+#min+' minutes'
IF NOT EXISTS (SELECT 1 FROM master.dbo.sysprocesses WHERE program_name = N'SQLAgent - Generic Refresher')
BEGIN
PRINT 'SQL Server is running but SQL Server Agent <<NOT>> running'
END
ELSE BEGIN
PRINT 'SQL Server and SQL Server Agent both are running'
END
This simple query works for versions before SQL Server 2005 as well as recent ones:
SELECT
crdate AS startup,
DATEDIFF(s, crdate, GETDATE()) / 3600. / 24 AS uptime_days
FROM master..sysdatabases
WHERE name = 'tempdb'