您的位置:首页 > Web前端

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

2016-06-02 17:15 746 查看

[code]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.

[/code]
[/code]
图形界面操作:





案例分析:

从错误信息我们可以看出是删除某个系统表中记录时,由于外键约束关系,导致删除失败。最后导致存储过程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]如下所示:


[code][code]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



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


[code][code]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

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


[code][code]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

[/code]
[/code]
也就是说最终在此存储过程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系统表






[code]
[code]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

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





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





sysjob_view视图代码如下所示:


[code]
[code]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

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


[code]
[code]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

[/code]
[/code]
原来master_server的值是默认的。因为表dbo.sysoriginatingservers无记录。至此,可以看出,这应该是SQLServer2005的一个BUG来的。

解决方案:

手工删除系统表msdb.dbo.sysmaintplan_subplans中的记录,然后删除该作业。问题搞定。


[code][code]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

[/code]
[/code]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: