您的位置:首页 > Web前端

SQL SERVER 2005删除维护作业报错:The DELETE statement conflicted with the REFERENCE constraint "FK_subplan_job_id"

2014-07-17 21:56 761 查看
案例环境:数据库版本:MicrosoftSQLServer2005(MicrosoftSQLServer2005-9.00.5000.00(X64))案例介绍:对一个数据库实例做清理工作时,发现有一个很久之前禁用的数据库维护作业,于是遂删除该作业,但是删除该作业时,遇到如下错误:脚本删除操作:

USE[msdb]
GO
EXECmsdb.dbo.sp_delete_job@job_id=N'876ab683-6d81-47c4-bba2-0dfa58156110',
@delete_unused_schedule=1
GO
消息547,级别16,状态0,过程sp_delete_job,第178行
TheDELETEstatementconflictedwiththeREFERENCEconstraint"FK_subplan_job_id".Theconflictoccurredindatabase"msdb",
table"dbo.sysmaintplan_subplans",column'job_id'.Thestatementhasbeenterminated.

图形界面操作:





案例分析:

从错误信息我们可以看出是删除某个系统表中记录时,由于外键约束关系,导致删除失败。最后导致存储过程msdb.dbo.sp_delete_job执行失败。我想彻底弄清楚删除失败的具体原因,于是可以从提示信息的系统表dbo.sysmaintplan_subplans开始,如下所示,



可以看到系统表dbo.sysmaintplan_subplans中的job_id字段引用了msdb.dbo.sysjobs中的job_id字段,那么可以肯定是在删除msdb.dbo.sysjobs表中对应记录时,没有先删除dbo.sysmaintplan_subplans中的记录。这样推测也跟报错信息吻合。
那么接下来我们研究一下msdb数据库的存储过程[dbo].[sp_delete_job]如下所示:

USE[msdb]
GO
SETANSI_NULLSON
GO
SETQUOTED_IDENTIFIERON
GO
ALTERPROCEDURE[dbo].[sp_delete_job]
@job_idUNIQUEIDENTIFIER=NULL,--IfprovidedshouldNOTalsoprovidejob_name
@job_namesysname=NULL,--IfprovidedshouldNOTalsoprovidejob_id
@originating_serversysname=NULL,--Reserved(usedbySQLAgent)
@delete_historyBIT=1,--Reserved(usedbySQLAgent)
@delete_unused_scheduleBIT=1--Forbackwardcompatibilityschedulesaredeletedbydefaultiftheyarenot
--beingusedbyanotherjob.WiththeintroductionofreusableschedulesinV9
--callersshouldsetthisto0sotheschedulewillbepreservedforreuse.
AS
BEGIN
DECLARE@current_msx_serversysname
DECLARE@bMSX_jobBIT
DECLARE@retvalINT
DECLARE@local_machine_namesysname
DECLARE@category_idINT
DECLARE@job_owner_sidVARBINARY(85)
SETNOCOUNTON
--Removeanyleading/trailingspacesfromparameters
SELECT@originating_server=UPPER(LTRIM(RTRIM(@originating_server)))
--Turn[nullable]emptystringparametersintoNULLs
IF(@originating_server=N'')SELECT@originating_server=NULL
--Changeservernametoalwaysreflectrealservernameorservername\instancename
IF(@originating_serverISNOTNULLAND@originating_server='(LOCAL)')
SELECT@originating_server=UPPER(CONVERT(sysname,SERVERPROPERTY('ServerName')))
IF((@job_idISNOTNULL)OR(@job_nameISNOTNULL))
BEGIN
EXECUTE@retval=sp_verify_job_identifiers'@job_name',
'@job_id',
@job_nameOUTPUT,
@job_idOUTPUT,
@owner_sid=@job_owner_sidOUTPUT
IF(@retval<>0)
RETURN(1)--Failure
END
--Weneedeitherajobnameoraservername,notboth
IF((@job_nameISNULL)AND(@originating_serverISNULL))OR
((@job_nameISNOTNULL)AND(@originating_serverISNOTNULL))
BEGIN
RAISERROR(14279,-1,-1)
RETURN(1)--Failure
END
--Getcategorytoseeifitisamisc.replicationagent.@category_idwillbe
--NULLifthereisno@job_id.
select@category_id=category_idfrommsdb.dbo.sysjobswherejob_id=@job_id
--Ifjobnamewasgiven,determineifthejobisfromanMSX
IF(@job_idISNOTNULL)
BEGIN
SELECT@bMSX_job=CASEUPPER(originating_server)
WHENUPPER(CONVERT(sysname,SERVERPROPERTY('ServerName')))THEN0
ELSE1
END
FROMmsdb.dbo.sysjobs_view
WHERE(job_id=@job_id)
END
--Ifservernamewasgiven,warnuserifdifferentfromcurrentMSX
IF(@originating_serverISNOTNULL)
BEGIN
EXECUTE@retval=master.dbo.xp_getnetname@local_machine_nameOUTPUT
IF(@retval<>0)
RETURN(1)--Failure
IF((@originating_server=UPPER(CONVERT(sysname,SERVERPROPERTY('ServerName'))))OR(@originating_server=UPPER(@local_machine_name)))
SELECT@originating_server=UPPER(CONVERT(sysname,SERVERPROPERTY('ServerName')))
EXECUTEmaster.dbo.xp_instance_regreadN'HKEY_LOCAL_MACHINE',
N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
N'MSXServerName',
@current_msx_serverOUTPUT,
N'no_output'
SELECT@current_msx_server=UPPER(@current_msx_server)
--Ifservernamewasgivenbutit'snotthecurrentMSX,printawarning
SELECT@current_msx_server=LTRIM(RTRIM(@current_msx_server))
IF((@current_msx_serverISNOTNULL)AND(@current_msx_server<>N'')AND(@originating_server<>@current_msx_server))
RAISERROR(14224,0,1,@current_msx_server)
END
--Checkauthority(onlySQLServerAgentcandeleteanon-localjob)
IF(((@originating_serverISNOTNULL)AND(@originating_server<>UPPER(CONVERT(sysname,SERVERPROPERTY('ServerName')))))OR(@bMSX_job=1))AND
(PROGRAM_NAME()NOTLIKEN'SQLAgent%')
BEGIN
RAISERROR(14274,-1,-1)
RETURN(1)--Failure
END
--Checkpermissionsbeyondwhat'scheckedbythesysjobs_view
--SQLAgentReaderandSQLAgentOperatorrolesthatcanseealljobs
--cannotdeletejobstheydonotown
IF(@job_idISNOTNULL)
BEGIN
IF(@job_owner_sid<>SUSER_SID()--doesnotownthejob
AND(ISNULL(IS_SRVROLEMEMBER(N'sysadmin'),0)<>1))--isnotsysadmin
BEGIN
RAISERROR(14525,-1,-1);
RETURN(1)--Failure
END
END
--Dothedelete(foraspecificjob)
IF(@job_idISNOTNULL)
BEGIN
--Note:Thistemptableisreferencedbymsdb.dbo.sp_delete_job_references
CREATETABLE#temp_jobs_to_delete(job_idUNIQUEIDENTIFIERNOTNULL,job_is_cachedINTNOTNULL)
DECLARE@temp_schedules_to_deleteTABLE(schedule_idINTNOTNULL)
INSERTINTO#temp_jobs_to_delete
SELECTjob_id,(SELECTCOUNT(*)
FROMmsdb.dbo.sysjobservers
WHERE(job_id=@job_id)
AND(server_id=0))
FROMmsdb.dbo.sysjobs_view
WHERE(job_id=@job_id)
--Checkifwehaveanyworktodo
IF(NOTEXISTS(SELECT*
FROM#temp_jobs_to_delete))
BEGIN
DROPTABLE#temp_jobs_to_delete
RETURN(0)--Success
END
--Postthedeletetoanytargetservers(needtodothisBEFORE
--deletingthejobitself,butAFTERclearingallallpending
--downloadinstructions).NotethatifthejobisNOTa
--multi-serverjobthensp_post_msx_operationwillcatchthisand
--willdonothing.Sinceitwilldonothingthatiswhyweneed
--toNOTdeleteanypendingdeleterequests,becausethatdelete
--requestmighthavebeenforthelasttargetserverandthus
--thisjobisn'tamulti-serverjobanymoresopostingtheglobal
--deletewoulddonothing.
DELETEFROMmsdb.dbo.sysdownloadlist
WHERE(object_id=@job_id)
and(operation_code!=3)--Delete
EXECUTEmsdb.dbo.sp_post_msx_operation'DELETE','JOB',@job_id
--Mustdothisbeforedeletingthejobitselfsincesp_sqlagent_notifydoesalookuponsysjobs_view
--Note:Don'tnotifyagentinthiscall.Itisdoneafterthetransactioniscommitted
--justincasethisjobisintheprocessofdeletingitself
EXECUTEmsdb.dbo.sp_delete_job_references@notify_sqlagent=0
--Deletealltracesofthejob
BEGINTRANSACTION
--Gettheschedulestodeletebeforedeletingrecordsfromsysjobschedules
IF(@delete_unused_schedule=1)
BEGIN
--Getthelistofschedulestodelete
INSERTINTO@temp_schedules_to_delete
SELECTDISTINCTschedule_id
FROMmsdb.dbo.sysschedules
WHERE(schedule_idIN
(SELECTschedule_id
FROMmsdb.dbo.sysjobschedules
WHERE(job_id=@job_id)))
END
DELETEFROMmsdb.dbo.sysjobschedules
WHEREjob_idIN(SELECTjob_idFROM#temp_jobs_to_delete)
DELETEFROMmsdb.dbo.sysjobservers
WHEREjob_idIN(SELECTjob_idFROM#temp_jobs_to_delete)
DELETEFROMmsdb.dbo.sysjobsteps
WHEREjob_idIN(SELECTjob_idFROM#temp_jobs_to_delete)
DELETEFROMmsdb.dbo.sysjobs
WHEREjob_idIN(SELECTjob_idFROM#temp_jobs_to_delete)
--Deletetheschedule(s)ifrequestedtoanditisn'tbeingusedbyotherjobs
IF(@delete_unused_schedule=1)
BEGIN
--NowOKtodeletetheschedule
DELETEFROMmsdb.dbo.sysschedules
WHEREschedule_idIN
(SELECTschedule_id
FROM@temp_schedules_to_deleteassdel
WHERENOTEXISTS(SELECT*
FROMmsdb.dbo.sysjobschedulesASjs
WHERE(js.schedule_id=sdel.schedule_id)))
END
--Deletethejobhistoryifrequested
IF(@delete_history=1)
BEGIN
DELETEFROMmsdb.dbo.sysjobhistory
WHEREjob_idIN(SELECTjob_idFROM#temp_jobs_to_delete)
END
--Alldone
COMMITTRANSACTION
--Nownotifyagenttodeletethejob.
IF(EXISTS(SELECT*FROM#temp_jobs_to_deleteWHEREjob_is_cached>0))
BEGIN
DECLARE@nt_user_nameNVARCHAR(100)
SELECT@nt_user_name=ISNULL(NT_CLIENT(),ISNULL(SUSER_SNAME(),FORMATMESSAGE(14205)))
--Callthexpdirectly.sp_sqlagent_notifycheckssysjobs_viewandtherecordhasalreadybeendeleted
EXECmaster.dbo.xp_sqlagent_notifyN'J',@job_id,0,0,N'D',@nt_user_name,1,@@trancount,NULL,NULL
END
END
ELSE
--Dothedelete(foralljobsoriginatingfromthespecificserver)
IF(@originating_serverISNOTNULL)
BEGIN
EXECUTEmsdb.dbo.sp_delete_all_msx_jobs@msx_server=@originating_server
--NOTE:Inthiscasethereisnoneedtopropagatethedeleteviasp_post_msx_operation
--sincethistypeofdeleteisonlyeverperformedonaTSX.
END
IF(OBJECT_ID(N'tempdb.dbo.#temp_jobs_to_delete','U')ISNOTNULL)
DROPTABLE#temp_jobs_to_delete
RETURN(0)--0meanssuccess
END

从上面SQL脚本中可以看到在删除msdb.dbo.sysjobsteps之前,该存储过程执行了msdb.dbo.sp_delete_job_references

USE[msdb]
GO
SETANSI_NULLSON
GO
SETQUOTED_IDENTIFIEROFF
GO
ALTERPROCEDURE[dbo].[sp_delete_job_references]
@notify_sqlagentBIT=1
AS
BEGIN
DECLARE@deleted_job_idUNIQUEIDENTIFIER
DECLARE@task_id_as_charVARCHAR(10)
DECLARE@job_is_cachedINT
DECLARE@alert_namesysname
DECLARE@maintplan_plan_idUNIQUEIDENTIFIER
DECLARE@maintplan_subplan_idUNIQUEIDENTIFIER
--KeepSQLServerAgent'scachein-syncandcleanupany'webtask'cross-referencestothedeletedjob(s)
--NOTE:Thecallermusthavecreatedatablecalled#temp_jobs_to_deleteoftheformat
--(job_idUNIQUEIDENTIFIERNOTNULL,job_is_cachedINTNOTNULL).
DECLAREsqlagent_notifyCURSORLOCAL
FOR
SELECTjob_id,job_is_cached
FROM#temp_jobs_to_delete
OPENsqlagent_notify
FETCHNEXTFROMsqlagent_notifyINTO@deleted_job_id,@job_is_cached
WHILE(@@fetch_status=0)
BEGIN
--NOTE:WeonlynotifySQLServerAgentifweknowthejobhasbeencached
IF(@job_is_cached=1AND@notify_sqlagent=1)
EXECUTEmsdb.dbo.sp_sqlagent_notify@op_type=N'J',
@job_id=@deleted_job_id,
@action_type=N'D'
IF(EXISTS(SELECT*
FROMmaster.dbo.sysobjects
WHERE(name=N'sp_cleanupwebtask')
AND(type='P')))
BEGIN
SELECT@task_id_as_char=CONVERT(VARCHAR(10),task_id)
FROMmsdb.dbo.systaskids
WHERE(job_id=@deleted_job_id)
IF(@task_id_as_charISNOTNULL)
EXECUTE('master.dbo.sp_cleanupwebtask@taskid='+@task_id_as_char)
END
--MaintenanceplancleanupforSQL2005.
--Ifthisjobcamefromanotherserveranditrunsasubplanofa
--maintenanceplan,thendeletethesubplanrecord.Ifthatwas
--thelastsubplanstillreferencingthatplan,deletetheplan.
--Thisremovesadistributedmaintenanceplanfromatargetserver
--onceallofjobsfromthemasterserverthatusedthatmaintenance
--planaredeleted.
SELECT@maintplan_plan_id=plans.plan_id,@maintplan_subplan_id=plans.subplan_id
FROMsysmaintplan_subplansplans,sysjobs_viewsjv
WHEREplans.job_id=@deleted_job_id
ANDplans.job_id=sjv.job_id
ANDsjv.master_server=1--Thismeansthejobcamefromthemaster
IF(@maintplan_subplan_idisnotNULL)
BEGIN
EXECUTEsp_maintplan_delete_subplan@subplan_id=@maintplan_subplan_id,@delete_jobs=0
IF(NOTEXISTS(SELECT*
FROMsysmaintplan_subplans
whereplan_id=@maintplan_plan_id))
BEGIN
DECLARE@plan_namesysname
SELECT@plan_name=name
FROMsysmaintplan_plans
WHEREid=@maintplan_plan_id
EXECUTEsp_dts_deletepackage@name=@plan_name,@folderid='08aa12d5-8f98-4dab-a4fc-980b150a5dc8'--thisistheguidfor'MaintenancePlans'
END
END
FETCHNEXTFROMsqlagent_notifyINTO@deleted_job_id,@job_is_cached
END
DEALLOCATEsqlagent_notify
--Removesystaskidreferences(mustdothisAFTERsp_cleanupwebtaskstuff)
DELETEFROMmsdb.dbo.systaskids
WHEREjob_idIN(SELECTjob_idFROM#temp_jobs_to_delete)
--Removesysdbmaintplan_jobsreferences(legacymaintenanceplanspriortoSQL2005)
DELETEFROMmsdb.dbo.sysdbmaintplan_jobs
WHEREjob_idIN(SELECTjob_idFROM#temp_jobs_to_delete)
--Finally,cleanupanydanglingreferencesinsysalertstothedeletedjob(s)
DECLAREsysalerts_cleanupCURSORLOCAL
FOR
SELECTname
FROMmsdb.dbo.sysalerts
WHERE(job_idIN(SELECTjob_idFROM#temp_jobs_to_delete))
OPENsysalerts_cleanup
FETCHNEXTFROMsysalerts_cleanupINTO@alert_name
WHILE(@@fetch_status=0)
BEGIN
EXECUTEmsdb.dbo.sp_update_alert@name=@alert_name,
@job_id=0x00
FETCHNEXTFROMsysalerts_cleanupINTO@alert_name
END
DEALLOCATEsysalerts_cleanup
END

而msdb.dbo.sp_delete_job_references这个存储过程又接着调用了存储过程sp_maintplan_delete_subplan,

USE[msdb]
GO
SETANSI_NULLSON
GO
SETQUOTED_IDENTIFIEROFF
GO
ALTERPROCEDURE[dbo].[sp_maintplan_delete_subplan]
@subplan_idUNIQUEIDENTIFIER,
@delete_jobsBIT=1
AS
BEGIN
DECLARE@retvalINT
DECLARE@jobUNIQUEIDENTIFIER
DECLARE@jobMsxUNIQUEIDENTIFIER
SETNOCOUNTON
SET@retval=0
--Raiseanerrorifthe@subplan_iddoesn'texist
IF(NOTEXISTS(SELECT*FROMsysmaintplan_subplansWHEREsubplan_id=@subplan_id))
BEGIN
DECLARE@subplan_id_as_charVARCHAR(36)
SELECT@subplan_id_as_char=CONVERT(VARCHAR(36),@subplan_id)
RAISERROR(14262,-1,-1,'@subplan_id',@subplan_id_as_char)
RETURN(1)
END
BEGINTRAN
--IsthereanAgentJob/Scheduleassociatedwiththissubplan?
SELECT@job=job_id,@jobMsx=msx_job_id
FROMmsdb.dbo.sysmaintplan_subplans
WHEREsubplan_id=@subplan_id
EXEC@retval=msdb.dbo.sp_maintplan_delete_log@subplan_id=@subplan_id
IF(@retval<>0)
BEGIN
ROLLBACKTRAN
RETURN@retval
END
--Deletethesubplanstableentryfirstsinceithasaforeign
--keyconstraintonitsjob_idexistinginsysjobs.
DELETEmsdb.dbo.sysmaintplan_subplans
WHERE(subplan_id=@subplan_id)
IF(@delete_jobs=1)
BEGIN
--deletethelocaljobassociatedwiththissubplan
IF(@jobISNOTNULL)
BEGIN
EXEC@retval=msdb.dbo.sp_delete_job@job_id=@job,@delete_unused_schedule=1
IF(@retval<>0)
BEGIN
ROLLBACKTRAN
RETURN@retval
END
END
--deletethemulti-serverjobassociatedwiththissubplan.
IF(@jobMsxISNOTNULL)
BEGIN
EXEC@retval=msdb.dbo.sp_delete_job@job_id=@jobMsx,@delete_unused_schedule=1
IF(@retval<>0)
BEGIN
ROLLBACKTRAN
RETURN@retval
END
END
END
COMMITTRAN
RETURN(0)
END

也就是说最终在此存储过程sp_maintplan_delete_subplan中删除msdb.dbo.sysmaintplan_subplans表中的记录。过程梳理清楚了,那么逆向推导看看具体原因
如下所示,删除msdb.dbo.sysmaintplan_subplans中对应记录语句如下



此时要看参数@subplan_id的取值,它从msdb.dbo.sp_delete_job_references中传入,如下所示
ALTERPROCEDURE[dbo].[sp_maintplan_delete_subplan]
@subplan_idUNIQUEIDENTIFIER,
@delete_jobsBIT=1
AS
…………………………………………………………………
在[dbo].[sp_delete_job_references]中,它的值来自于@maintplan_subplan_id变量,最终来自于sysmaintplan_subplans系统表




SELECT@maintplan_plan_id=plans.plan_id,
@maintplan_subplan_id=plans.subplan_id
FROMsysmaintplan_subplansplans,sysjobs_viewsjv
WHEREplans.job_id=@deleted_job_id
ANDplans.job_id=sjv.job_id
ANDsjv.master_server=1--Thismeansthejobcamefromthemaster

我通过DAC登录数据库(sysmaintplan_subplans是内部对象,此对象在DAC下才可以访问),查询如下所示,你会发现无记录,也就是说@maintplan_subplan_id为NULL值,导致后面执行删除msdb.dbo.sysmaintplan_subplans表中记录时,没有真正的删除记录。



最后发现导致查询无记录的原因在于查询条件sjv.master_server=1




sysjob_view视图代码如下所示:

CREATEVIEWsysjobs_view
AS
SELECTjobs.job_id,
svr.originating_server,
jobs.name,
jobs.enabled,
jobs.description,
jobs.start_step_id,
jobs.category_id,
jobs.owner_sid,
jobs.notify_level_eventlog,
jobs.notify_level_email,
jobs.notify_level_netsend,
jobs.notify_level_page,
jobs.notify_email_operator_id,
jobs.notify_netsend_operator_id,
jobs.notify_page_operator_id,
jobs.delete_level,
jobs.date_created,
jobs.date_modified,
jobs.version_number,
jobs.originating_server_id,
svr.master_server
FROMmsdb.dbo.sysjobsasjobs
JOINmsdb.dbo.sysoriginatingservers_viewassvr
ONjobs.originating_server_id=svr.originating_server_id
--LEFTJOINmsdb.dbo.sysjobserversjsONjobs.job_id=js.job_id
WHERE(owner_sid=SUSER_SID())
OR(ISNULL(IS_SRVROLEMEMBER(N'sysadmin'),0)=1)
OR(ISNULL(IS_MEMBER(N'SQLAgentReaderRole'),0)=1)
OR((ISNULL(IS_MEMBER(N'TargetServersRole'),0)=1)AND
(EXISTS(SELECT*FROMmsdb.dbo.sysjobserversjs
WHEREjs.server_id<>0ANDjs.job_id=jobs.job_id)))--filteroutlocaljobs

继续往下扒,视图dbo.sysoriginatingservers_view代码如下所示,

CREATEVIEWdbo.sysoriginatingservers_view(originating_server_id,originating_server,master_server)
AS
SELECT
0ASoriginating_server_id,
UPPER(CONVERT(sysname,SERVERPROPERTY('ServerName')))ASoriginating_server,
0ASmaster_server
UNION
SELECT
originating_server_id,
originating_server,
master_server
FROM
dbo.sysoriginatingservers

原来master_server的值是默认的。因为表dbo.sysoriginatingservers无记录。至此,可以看出,这应该是SQLServer2005的一个BUG来的。
解决方案:
手工删除系统表msdb.dbo.sysmaintplan_subplans中的记录,然后删除该作业。问题搞定。

USE[msdb]
GO
DELETEFROMmsdb.dbo.sysmaintplan_subplansWHERESUBPLAN_ID='B9A639EB-955D-4AE6-B69E-860145C133E7';
USE[msdb]
GO
EXECmsdb.dbo.sp_delete_job@job_id=N'ce8cb4ad-c91f-45bc-9e21-b50947063fba',@delete_unused_schedule=1
GO
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐