Pages

If you have a fill factor with 70 , what will be the effect , good or bad.

For this I will have :
– table with fill factor 70 – Table70
– table with fill factor 100 = Table100

Each table will have a cluster index on an identity column.
Then Inserting records so that we have like 1000 pages:
4 records per page * 1000 pages = 4000 records needed to be inserted.
Check pages for both tables.
Do a rebuild with the right fill factor.
Check the level of fragmentation.

 

 

 

use db_workspace
go
DROP TABLE dbo.Table70;
CREATE TABLE dbo.Table70
(
idTable INT IDENTITY(1,1) NOT NULL
,colText CHAR(2000) NOT NULL DEFAULT ‘ 70 table some text 2000 char’
,CONSTRAINT PK_Table70_idTable PRIMARY KEY CLUSTERED(idTable)
);
DROP TABLE dbo.Table100;
CREATE TABLE dbo.Table100
(
idTable INT IDENTITY(1,1) NOT NULL
,colText CHAR(2000) NOT NULL DEFAULT ‘ 100 table some text 2000 char’
,CONSTRAINT PK_Table100_idTable PRIMARY KEY CLUSTERED(idTable)
);
INSERT INTO dbo.Table70(colText)
DEFAULT VALUES
GO 4000
INSERT INTO dbo.Table100(colText)
DEFAULT VALUES
GO 4000

Check the number of pages:

 

SELECT COUNT(DISTINCT CA.page_id) as distinct_count
FROM dbo.Table70
CROSS APPLY sys.fn_PhysLocCracker(%%physloc%%)AS CA
–1000 distinct pages
SELECT COUNT(DISTINCT CA.page_id) as distinct_count
FROM dbo.Table100
CROSS APPLY sys.fn_PhysLocCracker(%%physloc%%)AS CA
–1000 distinct pages

Do rebuild with the fill factor :

ALTER INDEX PK_Table70_idTable ON dbo.Table70
REBUILD WITH (FILLFACTOR = 70);
ALTER INDEX PK_Table100_idTable ON dbo.Table100
REBUILD WITH (FILLFACTOR = 100);

Check pages:

SELECT COUNT(DISTINCT CA.page_id) as distinct_count
FROM dbo.Table70
CROSS APPLY sys.fn_PhysLocCracker(%%physloc%%)AS CA
–1334 distinct pages
SELECT COUNT(DISTINCT CA.page_id) as distinct_count
FROM dbo.Table100
CROSS APPLY sys.fn_PhysLocCracker(%%physloc%%)AS CA
–1000 distinct pages

So , we have more pages, in Table70, because of the fill factor = 70. This will trigger more logical reads, more memory …

A plus , in a lower fill factor then 100, and depending of your situation/table/activity . is that it will not generate an expensive page split, in case of a update/insert.

In this case, new records are always added to the end of table , like an append. For update , also it’s not a problem because, you have CHAR data type, and it’s fully allocated.

So , in such a case,  Table100 it’s a better option.

 

In other cases, analyze it, check the cluster index, what kind of DML activity , monitor page splits and finally hire a good DBA.

 

 

S

 

 

Ad hoc

If you are sending to server requests under the form of a query( not call to a stored procedure) , this is call ad hoc queries, and you are doing a ad hoc workload.

It is important to keep this under control , so that your plan cache will not grow under control. So, you have to check to plan cache , daily, and maybe hourly, depending on your system.

For this, you need some objects:

This is keeping the list of counters:

IF OBJECT_ID(‘dbo._Counters’,’U’) IS NULL
BEGIN
CREATE TABLE dbo._Counters
(
cnt_ID INT NOT NULL IDENTITY(1,1)
,cnt_Name VARCHAR(50) NOT NULL
,cnt_Description VARCHAR(200) NOT NULL
,CONSTRAINT pk__Counters_cntID PRIMARY KEY CLUSTERED(cnt_ID)
);
END

Next two tables, are keeping the raw data.

IF OBJECT_ID(‘dbo.Monitoring_Header’,’U’) IS NULL
BEGIN
CREATE TABLE dbo.Monitoring_Header
(
mnh_ID INT NOT NULL IDENTITY(1,1)
,mnh_Date DATE NOT NULL
,mnh_InsertedDate DATETIME2(0) NOT NULL DEFAULT SYSDATETIME()
,CONSTRAINT pk_Monitoring_Header_mnhID PRIMARY KEY CLUSTERED(mnh_ID)
–,CONSTRAINT df_Monitoring_Header_InsertedDate DEFAULT GETDATE() FOR mnh_InsertedDate
);
END
DROP TABLE IF EXISTS dbo.Monitoring_Details;
IF OBJECT_ID(‘dbo.Monitoring_Details’,’U’) IS NULL
BEGIN
CREATE TABLE dbo.Monitoring_Details
(
mnd_ID INT NOT NULL IDENTITY(1,1)
,mnd_mnh_ID INT NOT NULL
,mnd_cnt_ID INT NOT NULL
,mnd_dbID SMALLINT NOT NULL
,mnd_Value DECIMAL(16,2) NOT NULL
,CONSTRAINT pk_Monitoring_Details_mndID_cntID_dbName PRIMARY KEY NONCLUSTERED(mnd_ID)
,CONSTRAINT fk_Monitoring_Details_Header_mnh_ID
FOREIGN KEY ( mnd_mnh_id)
REFERENCES dbo.Monitoring_Header
,CONSTRAINT fk_Monitoring_Details__Counters_cntID
FOREIGN KEY (mnd_cnt_id)
REFERENCES dbo._Counters
,CONSTRAINT UQ_C_Monitoring_Details_mnhID_cntID_dbID UNIQUE CLUSTERED
(mnd_mnh_id
,mnd_cnt_id
,mnd_dbID)
);
/*CREATE UNIQUE CLUSTERED INDEX idx_Monitoring_Details_mnhID_cntID_dbName ON dbo.Monitoring_Details
(mnd_mnh_id
,mnd_cnt_id
,mnd_dbName)
*/
END

 

Next 2 tables, are some aggregation, for better understand the trends.

One is aggregation by DAY.

IF OBJECT_ID(‘dbo.Monitoring_AGG_ByDay’,’U’) IS NULL
BEGIN
CREATE TABLE dbo.Monitoring_AGG_ByDay
(
mad_ID INT NOT NULL IDENTITY(1,1)
,mad_Date DATE NOT NULL
,mad_cnt_ID INT NOT NULL
,mad_dbID SMALLINT NOT NULL
,mad_MinValue DECIMAL(16,2) NOT NULL
,mad_MaxValue DECIMAL(16,2) NOT NULL
,mad_AvgValue DECIMAL(16,2) NOT NULL
,CONSTRAINT pk_Monitoring_AGG_ByDay_madID PRIMARY KEY NONCLUSTERED(mad_ID)
,CONSTRAINT fk_Monitoring_AGG_ByDay__Counters_cntID
FOREIGN KEY (mad_cnt_ID)
REFERENCES dbo._Counters
,CONSTRAINT UQ_C_Monitoring_AGG_ByDay_mnhID_cntID_dbID UNIQUE CLUSTERED
(mad_Date
,mad_cnt_ID
,mad_dbID)
);
END

And this by Month

IF OBJECT_ID(‘dbo.Monitoring_AGG_ByMonth’,’U’) IS NULL
BEGIN
CREATE TABLE dbo.Monitoring_AGG_ByMonth
(
mam_ID INT NOT NULL IDENTITY(1,1)
,mam_Date DATE NOT NULL
,mam_cnt_ID INT NOT NULL
,mam_dbID SMALLINT NOT NULL
,mam_MinValue DECIMAL(16,2) NOT NULL
,mam_MaxValue DECIMAL(16,2) NOT NULL
,mam_AvgValue DECIMAL(16,2) NOT NULL
,CONSTRAINT pk_Monitoring_AGG_ByMonth_mamID PRIMARY KEY NONCLUSTERED(mam_ID)
,CONSTRAINT fk_Monitoring_AGG_ByMonth__Counters_cntID
FOREIGN KEY (mam_cnt_ID)
REFERENCES dbo._Counters
,CONSTRAINT UQ_C_Monitoring_AGG_ByMonth_mnhID_cntID_dbID UNIQUE CLUSTERED
(mam_Date
,mam_cnt_ID
,mam_dbID)
);
END
INSERT INTO dbo._Counters(cntName,cntDescription)
VALUES(‘adHoc cached plan size’,’ad Hoc size in cached plan’);

For this, you need some stored procedure, that are good to be place inside some jobs.

One stored procedure  is populating the raw tables Header and Details:

CREATE OR ALTER PROCEDURE dbo.Monitoring_adHoc
AS
BEGIN
SET NOCOUNT ON;
DECLARE @d_date AS DATE = CAST(SYSDATETIME() AS DATE)
,@i_mnhID INT = 0
,@i_cntID INT = 1; — tbl dbo._Counters – adHoc size
BEGIN TRY

–BEGIN TRANSACTION
INSERT INTO dbo.Monitoring_Header(mnh_Date)
VALUE(@d_date)
SELECT @i_mnhID = SCOPE_IDENTITY();

INSERT INTO dbo.Monitoring_Details(mnd_mnh_ID,mnd_cnt_ID,mnd_dbID,mnd_Value)
SELECT @i_mnhID, @i_cntID, dbid, SUM(CAST(size_in_bytes AS bigint))/1024/1024
FROM sys.dm_exec_cached_plans AS cp
CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) AS st
WHERE
cp.cacheobjtype = N’Compiled Plan’
AND cp.objtype IN(N’Adhoc’, N’Prepared’)
AND cp.usecounts = 1
GROUP BY dbid
OPTION (RECOMPILE);
–COMMIT TRANSACTION;
END TRY
BEGIN CATCH

ROLLBACK TRAN;
/* you can log to logTable…*/
END CATCH
END

Stored procedure , for doing Aggregation by day and populate the right table:

/* grouping informations from Monitoring and inserting into the AGG_ByDay table for previous day*/
CREATE OR ALTER PROCEDURE dbo.Monitoring_AGG_ByDay
AS
BEGIN
INSERT INTO dbo.Monitoring_AGG_ByDay(mad_Date,mad_cnt_ID,mad_dbID,mad_MinValue,mad_MaxValue,mad_AvgValue)
SELECT mh.mnh_Date
,md.mnd_cnt_ID
,md.mnd_dbID
,MIN(mnd_Value),MAX(mnd_Value),AVG(mnd_Value)
FROM
dbo.Monitoring_Header AS mh
INNER JOIN dbo.Monitoring_Details AS md
ON mh.mnh_id = md.mnd_mnh_id
WHERE mh.mnh_Date >= CAST(DATEADD(dAY,-1,GETDATE()) AS DATE)
AND mh.mnh_Date < CAST(GETDATE()AS DATE)
GROUP BY mh.mnh_Date,md.mnd_cnt_ID,md.mnd_dbID
END

Stored procedure for agg by month

CREATE OR ALTER PROCEDURE dbo.Monitoring_AGG_ByMonth
AS
BEGIN
INSERT INTO dbo.Monitoring_AGG_ByDay(mad_Date,mad_cnt_ID,mad_dbID,mad_MinValue,mad_MaxValue,mad_AvgValue)
SELECT CAST(DATEADD(MONTH,DATEDIFF(MONTH,0,mh.mnh_Date)-1,0) AS DATE)
,md.mnd_cnt_ID
,md.mnd_dbID
,MIN(mnd_Value),MAX(mnd_Value),AVG(mnd_Value)
FROM
dbo.Monitoring_Header AS mh
INNER JOIN dbo.Monitoring_Details AS md
ON mh.mnh_id = md.mnd_mnh_id
WHERE mh.mnh_Date >= CAST(DATEADD(MONTH,DATEDIFF(MONTH,0,GETDATE())-1,0) AS DATE) –previous month
AND mh.mnh_Date < CAST(DATEADD(MONTH,DATEDIFF(MONTH,0,GETDATE()),0) AS DATE) — current month
GROUP BY mh.mnh_Date,md.mnd_cnt_ID,md.mnd_dbID
END

And also, for old records in the raw table , maybe need to delete/purge from Details and Header.

CREATE OR ALTER PROCEDURE dbo.Monitoring_Purge_old_records
AS
BEGIN
DECLARE @i_rc AS INT = 0
,@i_j AS INT = 0
BEGIN TRY
WHILE (1=1)
BEGIN
BEGIN TRANSACTION;
DELETE TOP(100) md
FROM
dbo.Monitoring_Details as md
INNER JOIN dbo.Monitoring_Header as mh
ON md.mnd_mnh_ID = mh.mnh_ID
WHERE
mh.mnh_Date < CAST(DATEADD(DAY,-92,GETDATE()) AS DATE);
SET @i_rc = @@ROWCOUNT;
IF @i_rc < 100 BREAK;

SET @i_j = @i_j + 1;

IF @i_j = 10 /* less log activity*/
BEGIN
SET @i_j = 0;
COMMIT TRANSACTION;
END
END /* from while*/
IF @@TRANCOUNT > 0
COMMIT TRANSACTION; /* expected @@tranCount to be 1 ; so commit and close*/
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION; /* in this step @@tranCount should be 0 , in other way , rollback it*/

DELETE mh
FROM dbo.Monitoring_Header AS mh
WHERE mh.mnh_Date < CAST(DATEADD(DAY,-92,GETDATE()) AS DATE);
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
END CATCH
END

Now , place this stored procedure , each one, in a right job , and start to monitor it.

 

S

New in sql server 2017

New , in sql server 2017, with CU10, we have new hints related to CE&Compatibility level:

 

USE HINT(QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_XXX)

where XXX can be :

  •     120 (for sql server 2014)
  •     130 (for sql server 2016)
  •    140 (for sql server 2017)

SELECT  TOP(10) *

FROM TableAny

OPTION(USE HINT(QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_120));

 

For more info, please, read here.

 

 

S