michelleufford / sql-scripts Goto Github PK
View Code? Open in Web Editor NEWRepo for Microsoft SQL Server scripts. These are targeted at both DBAs/admins and at database developers.
Home Page: http://sqlfool.com
License: Apache License 2.0
Repo for Microsoft SQL Server scripts. These are targeted at both DBAs/admins and at database developers.
Home Page: http://sqlfool.com
License: Apache License 2.0
I am seeing the exception:
Looping through our list of databases and checking for fragmentation...
...
working on DBNAME...
Violation of PRIMARY KEY constraint 'PK_indexDefragStatus_v40'. Cannot insert duplicate key in object 'dbo.dba_indexDefragStatus'. The duplicate key value is (9, 50099219, 1, 1). (Line Number: 442)
When executing the SELECT portion of the script on Line 442, I get two rows:
databaseID databaseName objectID indexID partitionNumber fragmentation page_count range_scan_count scanDate
9 [DBNAME] 50099219 1 1 61.1909650924025 1948 0 2018-07-26 12:19:31.040
9 [DBNAME] 50099219 1 1 61.1909650924025 1948 84 2018-07-26 12:19:31.040
These two rows have the same values for all the primary keys in the table (databaseID, indexID, objectID, partitionNumber).
Is this a bug in the script, or is there something wrong the data in the instance I'm running the script on?
Hi,
I got the error in the subject line a few weeks back. It might be applicable to others that have the same issue.
In my case it's because I scan a large number of databases and some indexes have been dropped during the scan (due to maintenance processes from a 3rd party application) and no longer available in sys.objects. When dba_indexDefragStatus needs to be updated with the objectName (for instance) from sys.objects it leaves it as null. Therefore when updating dba_indexDefragLog from dba_indexDefragStatus the insert fails because the objectName is null.
The solution is change the Insert statement at the /* Log our actions */ section.
Original section
/ * Log our actions */
INSERT INTO dbo.dba_indexDefragLog
(
databaseID
, databaseName
, objectID
, objectName
, indexID
, indexName
, partitionNumber
, fragmentation
, page_count
, dateTimeStart
, sqlStatement
)
SELECT
@databaseid
, @databaseName
, @objectid
, @objectName
, @indexid
, @indexName
, @partitionNumber
, @fragmentation
, @pageCount
, @datetimestart
, @sqlcommand;
Fix
Add WHERE @objectName IS NOT null
This code seems to have a bug. If you dump the Defrag log, you will see that sometimes the Index Name and the SQL to rebuild or reorganize that index do not match. It turns out, that the SQL command to do the "fix" is from the last index that was determined to need a fix.
Here is SQL to see the problem. It extracts the index name from the Reindex/Reorg SQL command stored to the index that was being evaluated and dumps the ones that don't match:
SELECT TOP (1000) [indexDefrag_id]
,[databaseName]
,[objectName]
,[indexName]
,substring(substring(sqlstatement,CHARINDEX('[',sqlstatement) ,1000),1,CHARINDEX(']',substring(sqlstatement,CHARINDEX('[',sqlstatement) ,1000))) alteredIndexName
,[sqlStatement]
FROM [ITS_Maintenance].[dbo].[dba_indexDefragLog]
where
substring(substring(sqlstatement,CHARINDEX('[',sqlstatement) ,1000),1,CHARINDEX(']',substring(sqlstatement,CHARINDEX('[',sqlstatement) ,1000)))
<> indexName
order by indexDefrag_id desc
The bug seems occure when an index is being evaluated and turns out not to need a fix right now.
Whenever it passes through this code:
ELSE
/* Print an error message if any indexes happen to not meet the criteria above */
IF @printCommands = 1 OR @debugMode = 1
RAISERROR('We are unable to defrag this index.', 0, 42) WITH NOWAIT;
If it passes through the code above, the @sqlcommand is never updated from the previous index in the loop, and contains the SQL for the last key that needed a fix. It then falls down into the EXECUTE code. That code has no logic to catch the situation and executes whatever is in @sqlcommand, which is not the current index being evaluated and had already been executed. It then logs that an index was fixed under the name of the current index with the SQL command for a different index.
In my database, several indexes are being rebuilt over and over again, a great waste of resources.
I put in a fix for my system. Inside the BEGIN-END block for the if @executeSQL = 1 I added a check to see if @sqlcommand <> ''. If emtpy then don't execute the SQL but do execute the status update. The same in the print only BEGIN-END block. After it leaves this nested IF and is ready to loop to the next index, I set @sqlcommand to empty.
This fix is not perfect because it puts a defrag date in the log for an index that was determined did not need it. But, you can tell in the log because the SQL command is empty.
My fix trying tonight (scan for RLO 09/24/2020 - not test yet, but will give you an idea of what I think is going on here):
/* Are we executing the SQL? IF so, do it */
IF @executeSQL = 1
BEGIN
-- RLO 09/24/2020 Added to prevent previous SQL command from being executed again if next index was determined not needing a fix
if @sqlCommand <> ''
BEGIN -- RLO 09/24/2020
SET @debugMessage = 'Executing: ' + @sqlCommand;
/* Print the commands we're executing if specified to do so */
IF @printCommands = 1 OR @debugMode = 1
RAISERROR(@debugMessage, 0, 42) WITH NOWAIT;
/* Grab the time for logging purposes */
SET @datetimestart = GETDATE();
/* Log our actions */
INSERT INTO dbo.dba_indexDefragLog
(
databaseID
, databaseName
, objectID
, objectName
, indexID
, indexName
, partitionNumber
, fragmentation
, page_count
, DATETIMEStart
, sqlStatement
)
SELECT
@databaseID
, @databaseName
, @objectID
, @objectName
, @indexID
, @indexName
, @partitionNumber
, @fragmentation
, @pageCount
, @datetimestart
, @sqlCommand;
SET @indexDefrag_id = SCOPE_IDENTITY();
/* Wrap our execution attempt in a TRY/CATCH and log any errors that occur */
BEGIN TRY
/* Execute our defrag! */
EXECUTE sp_executesql @sqlCommand;
SET @dateTimeEnd = GETDATE();
/* Update our log with our completion time */
UPDATE dbo.dba_indexDefragLog
SET dateTimeEnd = @dateTimeEnd
, durationSeconds = DATEDIFF(second, @datetimestart, @dateTimeEnd)
WHERE indexDefrag_id = @indexDefrag_id;
END TRY
BEGIN CATCH
/* Update our log with our error message */
UPDATE dbo.dba_indexDefragLog
SET dateTimeEnd = GETDATE()
, durationSeconds = -1
, errorMessage = ERROR_MESSAGE()
WHERE indexDefrag_id = @indexDefrag_id;
IF @debugMode = 1
RAISERROR(' An/error has occurred executing this command! Please review the dba_indexDefragLog table for details.'
, 0, 42) WITH NOWAIT;
END CATCH
END -- RLO 09/24/2020
/* Just a little breather for the server */
WAITFOR DELAY @defragDelay;
UPDATE dbo.dba_indexDefragStatus
SET defragDate = GETDATE()
, printStatus = 1
WHERE databaseID = @databaseID
AND objectID = @objectID
AND indexID = @indexID
AND partitionNumber = @partitionNumber;
END
ELSE
/* Looks like we're not executing, just printing the commands */
BEGIN
if @sqlCommand <> '' --RLO 09/24/2020
BEGIN --RLO 09/24/2020
IF @debugMode = 1 RAISERROR(' Printing SQL statements...', 0, 42) WITH NOWAIT;
IF @printCommands = 1 OR @debugMode = 1
PRINT ISNULL(@sqlCommand, 'error!');
END --RLO 09/24/2020
UPDATE dbo.dba_indexDefragStatus
SET printStatus = 1
WHERE databaseID = @databaseID
AND objectID = @objectID
AND indexID = @indexID
AND partitionNumber = @partitionNumber;
END
--RLO 09/24/2020 If index was determined not to be fixed, the code was exectuting the previous t_SQL again
set @sqlCommand = '';
END
Hey, thanks for publishing and the great work.
I was testing abit around creating my own admin database and I'm missing the Enterprise Edition: Core-based Licensing for online rebuild
should be:
IF (SELECT ServerProperty('EditionID')) IN (1804890536, 1872460670, 610778273, -2117995310)
sql-scripts/indexes/dba_indexDefrag_sp.sql
Line 537 in f4e22da
Hi,
I recently got the error "String or binary data would be truncated." while executing dba_indexDefrag_sp . This is because the log, status and exclusion tables (plus some variables) have the type NVARCHAR(128) for columns such as database name, index name etc. being the maximum size allowed for object names. However, if the object name is the maximum 128 characters long a failure will occur with an INSERT using QUOTENAME() because adding square brackets to the name takes the length to 130 characters.
I suggest that the DDL for the tables and variables that are set at NVARCHAR(128) are changed to NVARCHAR(130). Thanks.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.