Nous avons du code SQL qui a été en production depuis un certain temps, dans une base de données SQL Server 2016; Mais il augmente une erreur dans une base de données SQL Server 2019 pour la première heure environ après le redémarrage du serveur SQL (allant de 5 à 10 minutes à une heure ou plus, probablement en fonction du niveau d'activité dans SQL Server). L'erreur est "Nom d'objet non valide" pour un CTE (expression de table commune).
Nous avons un environnement de production avec plusieurs bases de données dans SQL Server 2016. Nous avons maintenant mis en place un nouvel environnement de développement/test avec SQL Server 2019 (sur une machine Windows Server 2016, avec 24 Go de RAM et 4 cœurs CPU) afin que nous puissions tester avec SQL Server 2019. Les bases de données de ce serveur de test sont rétablies des copies des bases de données de production à partir de sauvegardes de production. Toutes les bases de données de l'environnement de test ont le niveau de compatibilité défini sur 150 (SQL Server 2019). .
Tôt chaque matin, nous avons commencé à voir des problèmes avec quelques fonctions qui utilisent des CTES, où la fonction souleverait des erreurs comme ceci:
SqlException (0x80131904): Invalid object name 'CTEuniqueName'.]
Msg 208, Level 16, State 1, Procedure ufn_FunctionName, Line 28 [Batch Start Line 0]
Invalid object name 'CTEuniqueName'.
Les erreurs ont cessé d'arriver après une courte période de temps et ne se sont pas reproduites avant le lendemain matin.
L'erreur se déroulait dans une paire de procédures stockées qui s'appellent l'une après l'autre, qui ont tous deux appelé la même fonction (SQL définie par l'utilisateur). Avec certains tests, j'ai appris que je pouvais parfois causer la même erreur en appelant simplement la fonction, puis en exécutant simplement un bloc de code de la fonction.
J'ai également découvert que je pouvais toujours causer l'erreur en redémarrant l'instance SQL Server et en appelant la fonction ou le bloc de code. C'est probablement pourquoi il ne faisait probablement que défaut tôt le matin - il n'y avait eu aucune activité sur l'instance SQL Server pendant plusieurs heures avant cela, de sorte qu'il était allé en mode veille ou fermait ses processus.
Après avoir appelé à plusieurs reprises la fonction ou le code, il réussirait à un moment donné, cela réussirait sans augmenter l'erreur et, après cela, il semblait continuer à fonctionner (jusqu'à ce que je redémarre l'instance SQL Server).
Si je modifie la base de données contenant cette fonction sur le mode de compatibilité "SQL Server 2016", la fonction réussit toujours, même immédiatement après le redémarrage de l'instance SQL Server. Donc, cela semble être un problème spécifique à SQL Server 2019.
Le code dans la fonction ressemble à ce qui suit (avec des noms anonymisés), et la fonction contient 2 blocs distincts "si" similaires à ceux suivants; Cependant, je peux causer l'erreur avec juste le bloc de code suivant. Les 2 vues de ce code sont des vues sur d'autres bases de données dans la même instance SQL Server.
DECLARE @MyID INT = 150589;
DECLARE @MyType VARCHAR(25) = 'Test';
DECLARE @ExpirationDate DATE;
BEGIN
IF @MyType = 'Test'
BEGIN
DECLARE @Test1 INT;
WITH CTEuniqueName(PersonID, DateComplete)
AS (SELECT T1.PersonID, MAX(T2.DateComplete) AS DateComplete
FROM dbo.TABLE1 AS T1 WITH(NOLOCK)
LEFT JOIN dbo.VIEW2 AS T2 WITH(NOLOCK) ON T2.ID = T1.ID
WHERE T1.ID = @MyID
AND T2.SecondID IN(SELECT SecondID
FROM dbo.TABLE3 WITH(NOLOCK)
WHERE Name = 'TEST')
GROUP BY T1.PersonID)
SELECT @ExpirationDate = CASE
WHEN V3.TimeFrame > 0 THEN DATEADD(DAY, TimeFrame, CONVERT(DATE, CTE1.DateComplete))
ELSE NULL
END
FROM CTEuniqueName AS CTE1
INNER JOIN dbo.VIEW2 AS V2 WITH(NOLOCK) ON V2.ID = CTE1.ID
AND V2.DateComplete = CTE1.DateComplete
INNER JOIN dbo.VIEW3 AS V3 WITH(NOLOCK) ON V3.QuizID = V2.QuizID
WHERE TS.SecondID IN(SELECT SecondID
FROM dbo.TABLE3 WITH(NOLOCK)
WHERE Name = 'TEST');
END;
END;
De plus, dans la période de démarrage après le démarrage que l'erreur est survenue, si j'exécute les 2 procédures stockées (appelant la fonction ci-dessus, et les deux procédures renvoient un ensemble de résultats) dans une fenêtre SSMS, les procédures renvoient avec succès leurs ensembles de résultats respectifs. Dans la fenêtre SSMS, puis SSMS bascule sur le panneau Messages et affiche l'erreur "Nom d'objet non valide".
Est-ce que quelqu'un sait pourquoi cela ne peut défaire de manière intermittente que dans SQL Server 2019 (et apparemment seulement après le démarrage du serveur SQL ou une période de temps identique)? Ou, quelqu'un peut-il suggérer comment résoudre les problèmes comme celui-ci? J'ai essayé de vérifier le journal d'erreur SQL; Et j'ai également essayé d'exécuter le profileur avec des appels de fonction inclus - aucun de ceux-ci n'a fourni d'indices.
Informations Complémentaires:
DBCC FREESYSTEMCACHE(N'ALL');
Cause la même erreur à se produire temporairement à nouveau (de la même manière que le redémarrage du serveur)Initialement, il semble que c'était un bug lié à la nouvelle fonctionnalité Scalar UDF Inlinge Ajouté dans SQL Server 2019, puisque vous avez mentionné que l'inlinage de désactivation résolue le problème. Lors de l'inspection ultérieure, la fonction ne peut pas être inlinée en raison de la présence d'un CTE dans la définition de la fonction.
Voici ma tentative (échouée) de reproduire le problème:
USE [master];
GO
DROP DATABASE IF EXISTS [256861OtherDatabase];
GO
CREATE DATABASE [256861OtherDatabase];
GO
USE [256861OtherDatabase];
GO
CREATE TABLE dbo.TableForView2
(
ID int IDENTITY(1,1) NOT NULL,
DateComplete datetime NOT NULL,
SecondID int NOT NULL,
QuizID int NOT NULL,
CONSTRAINT PK_TableForView2 PRIMARY KEY (Id)
);
GO
CREATE TABLE dbo.TableForView3
(
ID int IDENTITY(1,1) NOT NULL,
Timeframe int NOT NULL,
QuizID int NOT NULL,
CONSTRAINT PK_TableForView3 PRIMARY KEY (Id)
);
GO
USE [master];
GO
DROP DATABASE IF EXISTS [256861];
GO
CREATE DATABASE [256861];
GO
USE [256861];
GO
CREATE TABLE dbo.TABLE1
(
ID int IDENTITY(1,1) NOT NULL,
PersonID int NOT NULL,
CONSTRAINT PK_TABLE1 PRIMARY KEY (Id)
);
GO
CREATE VIEW dbo.View2
AS
SELECT
ID,
DateComplete,
SecondID,
QuizID
FROM [256861OtherDatabase].dbo.TableForView2 d
GO
CREATE TABLE dbo.TABLE3
(
ID int IDENTITY(1,1) NOT NULL,
SecondID int NOT NULL,
[Name] varchar(50) NOT NULL,
CONSTRAINT PK_TABLE3 PRIMARY KEY (Id)
);
GO
CREATE VIEW dbo.View3
AS
SELECT
ID,
Timeframe,
QuizID
FROM [256861OtherDatabase].dbo.TableForView3 d
GO
CREATE FUNCTION dbo.TestFunction
(
@MyID INT = 150589,
@MyType VARCHAR(25) = 'Test'
)
RETURNS date
AS
BEGIN;
DECLARE @ExpirationDate DATE;
IF @MyType = 'Test'
BEGIN
DECLARE @Test1 INT;
WITH CTEuniqueName(ID, DateComplete)
AS (SELECT T1.PersonID, MAX(T2.DateComplete) AS DateComplete
FROM dbo.TABLE1 AS T1 WITH(NOLOCK)
LEFT JOIN dbo.VIEW2 AS T2 WITH(NOLOCK) ON T2.ID = T1.ID
WHERE T1.ID = @MyID
AND T2.SecondID IN(SELECT SecondID
FROM dbo.TABLE3 WITH(NOLOCK)
WHERE Name = 'TEST')
GROUP BY T1.PersonID)
SELECT @ExpirationDate = CASE
WHEN V3.TimeFrame > 0 THEN DATEADD(DAY, TimeFrame, CONVERT(DATE, CTE1.DateComplete))
ELSE NULL
END
FROM CTEuniqueName AS CTE1
INNER JOIN dbo.VIEW2 AS V2 WITH(NOLOCK) ON V2.ID = CTE1.ID
AND V2.DateComplete = CTE1.DateComplete
INNER JOIN dbo.VIEW3 AS V3 WITH(NOLOCK) ON V3.QuizID = V2.QuizID
WHERE V2.SecondID IN(SELECT SecondID
FROM dbo.TABLE3 WITH(NOLOCK)
WHERE Name = 'TEST');
END;
RETURN @ExpirationDate;
END;
GO
J'ai remarqué que la fonction est marquée comme is_inlineable = 0
dans sys.sql_modules
, j'ai donc couru le code de création de fonction à nouveau avec la session d'événements étendus suivante:
CREATE EVENT SESSION [inlining] ON SERVER
ADD EVENT sqlserver.tsql_scalar_udf_not_inlineable
ADD TARGET package0.event_file(SET filename=N'inlining')
WITH (STARTUP_STATE=OFF)
GO
Il a produit 1 événement avec un blocked_reason
de "CTE" qui s'inscrivent avec les Configuration inverse Scalar UDFS (qui ont été mises à jour pour inclure des CTES après la publication initiale de cette question).
Cela semble toujours comme un comportement de buggy. La seule suggestion que j'ai, vraiment, est de réécrire la fonction pour éviter le CTE. Cela pourrait contourner le problème et permettre également la fonction d'être inlinée.
a couru dans cela aujourd'hui alors qu'un développeur testait un autre problème. Sur son SQL 2019 de temps en temps, il a rencontré un "nom d'objet non valide [nom de UDF]".
J'ai trouvé ce fil - et lisez un peu sur le nouveau traitement de la requête intelligente et l'allusion UDF.
Je peux maintenant reproduire le scénario et le transmettra à la SEP, ne connaissez pas la procédure à suivre, mais devine que je peux comprendre cette partie.
Le problème semble survenir (au moins dans notre cas) lors de l'utilisation d'un UDF créé sur un ancien serveur SQL et une sauvegarde est restauré sur le serveur 2019.
Ensuite, le "is_inlineable" est 1 sur l'UDF, même s'il a CTE dans le corps. Après avoir modifié l'UDF sur SQL 2019, le problème disparaît.
Cela pourrait potentiellement affecter toutes nos clients la mise à niveau vers SQL 2019, donc je le vois aussi critique.
Vous trouverez ci-dessous un script à suivre pour reproduire. Testé le 15.0.2070.41
Meilleures salutations
/ Anders
-- First steps are on non-SQL2019 server, used 14.0.2027.2 for this test
-- 1. Create Local database "InlineUDF" in SQL Server below 2019
-- 2. Execute create part of script
USE InlineUDF
CREATE TABLE Test ([Id] INT, [ParentID] INT NULL, [Name] NVARCHAR(10))
INSERT INTO Test (Id, ParentID, Name)
SELECT 1, NULL, 'Root'
UNION SELECT 2, 1, 'Child'
UNION SELECT 3, 1, 'Child1'
UNION SELECT 4, 1, 'Child2'
UNION SELECT 5, 1, 'Child3'
UNION SELECT 6, 3, 'Child4'
UNION SELECT 7, 6, 'Child5'
UNION SELECT 8, 5, 'Child6'
UNION SELECT 9, 2, 'Child7'
GO
CREATE FUNCTION [dbo].[ChildrenFilter]
(
@parentId int
)
RETURNS int
AS
BEGIN
DECLARE @returnValue int
DECLARE @result int
BEGIN
-- Recursive statement selecting InspectionJob inclusive all childs
WITH ChildrenCountRecursive(id) AS (
SELECT Test.[Id]
FROM Test
WHERE Test.[Id] = @parentId
UNION ALL
SELECT Test.[Id]
FROM Test
INNER JOIN ChildrenCountRecursive AS tr ON Test.[ParentID] = tr.[Id]
)
-- End of recursive statement
-- Check inspection job and all childs for vesseltype and metadata.
SELECT @result = COUNT(ChildrenCountRecursive.Id)
FROM ChildrenCountRecursive
END
IF (@result >= 2)
SELECT @returnValue = @parentId
ELSE
SELECT @returnValue = null
RETURN @returnValue
END
GO
-- 3. Backup database, and restore on SQL 2019 server
-- 4. Examine Inlineable on UDF
USE InlineUDF
SELECT is_inlineable, NAME
FROM
sys.sql_modules m
INNER JOIN
sys.objects o on o.object_id = m.object_id
WHERE
o.name = 'ChildrenFilter'
-- UDF is inlineable.
-- 5. Clear proc cache - and run query.
DBCC FREESYSTEMCACHE(N'ALL');
select * from Test
WHERE Id IN (SELECT [dbo].[ChildrenFilter](Id))
-- 6. Update compatibility level to 150
ALTER DATABASE [InlineUDF] SET COMPATIBILITY_LEVEL = 150
--7. Clear cache and try again - first time it fails - second time it's good.
DBCC FREESYSTEMCACHE(N'ALL');
select * from Test
WHERE Id IN (SELECT [dbo].[ChildrenFilter](Id))
-- 8. Alter UDF
GO
ALTER FUNCTION [dbo].[ChildrenFilter]
(
@parentId int
)
RETURNS int
AS
BEGIN
DECLARE @returnValue int
DECLARE @result int
BEGIN
-- Recursive statement selecting InspectionJob inclusive all childs
WITH ChildrenCountRecursive(id) AS (
SELECT Test.[Id]
FROM Test
WHERE Test.[Id] = @parentId
UNION ALL
SELECT Test.[Id]
FROM Test
INNER JOIN ChildrenCountRecursive AS tr ON Test.[ParentID] = tr.[Id]
)
-- End of recursive statement
-- Check inspection job and all childs for vesseltype and metadata.
SELECT @result = COUNT(ChildrenCountRecursive.Id)
FROM ChildrenCountRecursive
END
IF (@result >= 2)
SELECT @returnValue = @parentId
ELSE
SELECT @returnValue = null
RETURN @returnValue
END
GO
-- 9. Examine Inlineable on UDF
SELECT is_inlineable, NAME
FROM
sys.sql_modules m
INNER JOIN
sys.objects o on o.object_id = m.object_id
WHERE
o.name = 'ChildrenFilter'
-- UDF is inlineable.
-- 10. Clear proc cache - and run query.
DBCC FREESYSTEMCACHE(N'ALL');
GO
select * from Test
WHERE Id IN (SELECT [dbo].[ChildrenFilter](Id))
-- Now it works...