编辑 10/8/2015: 此答案中的宏已于 2015 年 10 月 8 日更新。通常更好的调试信息,添加了很好地终止 SAS 的选项,无论是以批处理还是交互模式运行。
编辑 2014 年 12 月 8 日: 此答案中的宏已于 2014 年 12 月 8 日更新。如果您有之前的版本,如果您的 libname 包含数百或数千个数据集,您会注意到锁定时间很慢。以下更新版本已修复此问题。
答:我们的数据集每隔几分钟就会更新一次,同时我们需要临时和计划的报告来访问这些数据集。为了克服锁定问题,我们创建了宏来在使用表之前锁定和解锁表。它已经运行了将近一年而没有报告任何错误(我们已经解决了我们遇到的所有错误)。
用法:
会话 A 中的程序:
%lock(iDs=sashelp.class);
** READ TABLE;
%unlock(iDs=sashelp.class);
会话 B 中的程序:
%lock(iDs=sashelp.class);
** UPDATE TABLE;
%unlock(iDs=sashelp.class);
它是如何工作的......假设会话 B 首先启动。它将在执行更新之前锁定数据集。会话 A 想要使用同一个表(而 B 仍在更新)它。在会话 A 尝试对其进行任何操作之前,它会检查它是否被锁定。如果被锁定,会话 A 会等待一段时间(默认 = 5 分钟)才决定放弃。如果会话 B 在 5 分钟内完成,它将释放锁定,会话 A 将锁定它并继续进行。会话 A 将在完成后将其解锁。您可以根据需要传入许多其他选项,以自定义在无法获得锁定时如何处理事情。
%lock
宏:
/******************************************************************************
** PROGRAM: MACRO.LOCK.SAS
**
** DESCRIPTION: LOCKS A SAS TABLE SO THAT YOU CAN DO WHAT IT IS YOU NEED TO DO.
**
** PARAMETERS: iDS : THE TABLE TO TRY AND LOCK.
** iTIMEOUT: THE MAXIMUM # OF SECONDS TO WAIT TRYING TO GET THE LOCK.
** iRETRY : HOW OFTEN TO RETRY GETTING THE LOCK (IN SECONDS)
** iVERBOSE: WHETHER TO PRINT OUT DEBUGGING INFO
** iEndQuietlyOnTimeout: EXIT SAS IF THE LOCK FAILS
** iIgnoreError: IGNORE ERRORS IF DATASET DOESNT EXIST AND CANT BE LOCKED
**
*******************************************************************************
** VERSION:
** 2.0 ON: 13-NOV-14 BY: RP
** CHANGED METHOD OF CHECKING WHETHER FILE EXISTS TO USE THE FILEEXIST
** FUNCTION. MUCH BETTERER.
** 2.1 ON: 14-SEP-15 BY: MS
** ADDED OPTIONAL FLAG TO IGNORE ERRORS WHILE ATTEMPTING A LOCK. MUCH BETTERERER.
** 2.2 ON: 25-SEP-15 BY: RP
** HANDLED FAILS BETTERERER. MADE WORDS IN LOG MORE BETTERERER TOO.
******************************************************************************/
%macro lock(iDs=, iTimeOut=600, iRetry=3, iVerbose=1, iEndSasQuietlyOnTimeout=0, iIgnoreError=0);
%global lock_lock_failed ;
%local starttime lib mbr physical_filename;
%let starttime = %sysfunc(datetime());
%let lock_lock_failed = 1;
/*
** MAKE SURE THE REQUIRED DS IS NOT A VIEW.
** TODO. CHANGE THIS TO ACCEPT 1 WORD DATA SET REFERENCES
*/
%let lib = %sysfunc(pathname(%sysfunc(scan(&iDs,1))));
%let mbr = %sysfunc(scan(&iDs,2));
%let physical_filename = &lib\&mbr..sas7bdat;
%if not %sysfunc(fileexist(&physical_filename)) %then %do;
%if not &iIgnoreError %then %do;
%put &err: (MACRO.LOCK.SAS) THE DATASET YOU TRIED TO LOCK DOES NOT EXIST (OR YOU TRIED TO LOCK A VIEW WHICH IS NOT POSSIBLE). ;
%put &err: (MACRO.LOCK.SAS) THE DATASET NAME WAS &iDs . EXITING SAS.;
%stop_sas;
%end;
%else %do;
%put NOTE: (MACRO.LOCK.SAS) THE DATASET YOU TRIED TO LOCK DOES NOT EXIST (OR YOU TRIED TO LOCK A VIEW WHICH IS NOT POSSIBLE). ;
%put NOTE: (MACRO.LOCK.SAS) CONTINUUING DESPITE FAILURE TO LOCK AS iIgnoreError=&iIgnoreError ;
%end;
%end;
%else %do;
%do %until(&lock_lock_failed eq 0 or %sysevalf(%sysfunc(datetime()) gt (&starttime + &iTimeOut)));
%if &iVerbose %then %do;
%put trying open ...;
%end;
data _null_;
dsid = 0;
do until (dsid gt 0 or datetime() gt (&starttime + &iTimeOut));
dsid = open("&iDs");
if (dsid eq 0) then do;
rc = sleep(&iRetry);
end;
end;
if (dsid gt 0) then do;
rc = close(dsid);
end;
run;
%if &iVerbose %then %do;
%put trying lock ...;
%end;
lock &iDs;
%if &syslckrc eq 0 %then %do;
%let lock_lock_failed = 0;
%end;
%else %do;
/*
** THIS WILL ONLY HAPPEN WHEN THE DATASET IS BEING VIEWED IN AN INTERACTIVE SESSION.
** THE OPEN FUNCTION WILL SAY ITS ABLE TO OPEN THE DATASET BUT THEN THE LOCK FUNCTION
** WILL FAIL. WHEN THIS HAPPENS SLEEP HERE AS WELL SO THAT WE DONT GET THOUSANDS OF LOCK
** ATTEMPTS IN THE LOG FILE.
*/
%let rc = %sysfunc(sleep(%eval(&iRetry * 5)));
%end;
%if &iVerbose %then %do;
%put syslckrc=&syslckrc;
%end;
%end;
%if &lock_lock_failed %then %do;
%if &iEndSasQuietlyOnTimeout %then %do;
%stop_sas;
%end;
%put &err: (MACRO.LOCK.SAS) COULD NOT LOCK DATASET BEFORE TIMEOUT (&iTimeOut) OCCURRED.;
%end;
%end;
%mend;
解锁宏:
/******************************************************************************
** PROGRAM: MACRO.UNLOCK.SAS
**
** DESCRIPTION: UNLOCKS A SAS TABLE LOCKED WITH MACROS.LOCK.SAS.
**
** PARAMETERS: iDS : THE TABLE TO TRY AND UNLOCK.
**
*******************************************************************************
** VERSION:
** 1.0 ON: 07-OCT-11 BY: RP
** CREATED.
******************************************************************************/
%macro unlock(iDs=);
/*
** ONLY UNLOCK IF THE LOCK WAS SUCCESSFUL
*/
%if %symexist(lock_lock_failed) %then %do;
%if &lock_lock_failed eq 0 %then %do;
lock &iDs clear;
%end;
%else %do;
%put ATTEMPT TO UNLOCK WAS IGNORED AS ATTEMPT TO LOCK FAILED.;
%end;
%end;
%else %do;
%put ATTEMPT TO UNLOCK WAS IGNORED AS NO ATTEMPT TO LOCK WAS MADE.;
%end;
%mend;
这些程序还需要存在一些其他实用程序宏。
IsDir 宏:
/******************************************************************************
** PROGRAM: CMN_MAC.ISDIR.SAS
**
** DESCRIPTION: DETERMINES IF THE SPECIFIED PATH EXISTS OR NOT.
** RETURNS: 0 IF THE PATH DOES NOT EXIST OR COULD NOT BE OPENED.
** 1 IF THE PATH EXISTS AND CAN BE OPENED.
**
** PARAMETERS: iPath: THE FULL PATH TO EXAMINE. NOTE THAT / AND \ ARE TREATED
** THE SAME SO &SASDIR/COMMON/MACROS IS THE SAME AS
** &SASDIR\COMMON\MACROS.
**
*******************************************************************************
** VERSION:
** 1.0 ON: 13-JUL-07 BY: RP
** CREATED.
** 1.1 ON: 29-APR-10 BY: RP
** ADDED CLEANUP CODE SO FILES WOULD NOT REMAIN LOCKED.
** 1.2 ON: 15-APR-14 BY: JG
** ADDED MORE DEBUGGING INFO.
** 1.3 ON: 12-DEC-14 BY: RP
** CLEANED UP DEBUGGING INFO TO A SINGLE LINE
******************************************************************************/
%macro isDir(iPath=,iQuiet=1);
%local result dname did rc;
%let result = 0;
%let check_file_assign = %sysfunc(filename(dname,&iPath));
%put ASSIGNED FILEREF (0=yes, 1=no)? &check_file_assign &iPath;
%if not &check_file_assign %then %do;
%let did = %sysfunc(dopen(&dname));
%if &did %then %do;
%let result = 1;
%end;
%else %if not &iQuiet %then %do;
%put &err: (ISDIR MACRO).;
%put %sysfunc(sysmsg());
%end;
%let rc = %sysfunc(dclose(&did));
%end;
%else %if not &iQuiet %then %do;
%put &err: (ISDIR MACRO).;
%put %sysfunc(sysmsg());
%end;
&result
%mend;
/*%put %isDir(iPath=&sasdir\commonn\macros);*/
/*%put %isDir(iPath=&sasdir/kxjfdkebnefe);*/
/*%put %isDir(iPath=&sasdir/kxjfdkebnefe, iQuiet=0);*/
/*%put %isDir(iPath=c:\temp);*/
文件列表宏:
/******************************************************************************
** PROGRAM: MACRO.FILE_LIST.SAS
**
** DESCRIPTION: RETURNS THE LIST OF FILES IN A DIRECTORY SEPERATED BY THE
** SPECIFIED DELIMITER. RETURNS AN EMPTY STRING IF THE THE
** DIRECTORY CAN'T BE READ OR DOES NOT EXIST.
**
** PARAMETERS: iPath: THE FULL PATH TO EXAMINE. NOTE THAT / AND \ ARE TREATED
** THE SAME SO &SASDIR/COMMON/MACROS IS THE SAME AS
** &SASDIR\COMMON\MACROS. WORKS WITH BOTH UNIX AND WINDOWS.
**
*******************************************************************************
** VERSION:
** 1.0 ON: 17-JUL-07 BY: RP
** CREATED.
** 1.1 ON: 29-APR-10 BY: RP
** ADDED CLEANUP CODE SO FILES WOULD NOT REMAIN LOCKED.
** FIXED ERROR OCCURRING WHEN IPATH DID NOT EXIST.
** 1.2 ON: 18-AUG-10 BY: RP
** CATERED FOR MACRO CHARS IN FILENAMES
** 1.3 ON: 14-APR-14 BY: RP&JG
** ADDED MORE DEBUGGING INFO TO CHECK PATH
** 1.4 ON: 13-NOV-14 BY: RP
** CHANGED PROGRAM FLOW TO MAKE IT BOTH EASIER TO READ AND TO
** REDUCE UNNECESSARY CHECKS / IMPROVE PERFORMANCE.
******************************************************************************/
/*
** TODO. THERES ABOUT 100 WAYS THIS COULD BE IMPROVED. DO IT SOMETIME.
** SIMPLY IF STATEMENTS FOR FILTERS.
*/
%macro file_list(iPath=, iFilter=, iFiles_only=0, iDelimiter=|);
%local result did dname cnt num_members filename rc check_dir_exist check_file_assign;
%let result=;
%let check_dir_exist = %isDir(iPath=&iPath);
%let check_file_assign = %sysfunc(filename(dname,&iPath));
%put The desired path: &iPath;
%if &check_dir_exist and not &check_file_assign %then %do;
%let did = %sysfunc(dopen(&dname));
%let num_members = %sysfunc(dnum(&did));
%do cnt=1 %to &num_members;
%let filename = %qsysfunc(dread(&did,&cnt));
%if "&filename" ne "" %then %do;
%if "&iFilter" ne "" %then %do;
%if %index(%lowcase(&filename),%lowcase(&iFilter)) eq 0 %then %do;
%goto next;
%end;
%end;
%if &iFiles_only %then %do;
%if %isDir(iPath=%nrbquote(&iPath/&filename)) %then %do;
%goto next;
%end;
%end;
%let result = &result%str(&iDelimiter)&filename;
%next:
%end;
%else %do;
%put ERROR: (CMN_MAC.FILE_LIST) FILE CANNOT BE READ.;
%put %sysfunc(sysmsg());
%end;
%end;
%let rc = %sysfunc(dclose(&did));
%end;
%else %do;
%put ERROR: (CMN_MAC.FILE_LIST) PATH DOES NOT EXIST OR CANNOT BE OPENED.;
%put %sysfunc(sysmsg());
%put DIRECTORY EXISTS (1-yes, 0-no)? &check_dir_exist;
%put ASSIGN FILEREF SUCCESSFUL (0-yes, 1-no)? &check_file_assign;
%end;
/*
** RETURN THE RESULT. TRIM THE LEADING DELIMITER OFF THE FRONT OF THE RESULTS.
*/
%if "&result" ne "" %then %do;
%qsubstr(%nrbquote(&result),2)
%end;
%mend;
/*%put %file_list(iPath=e:\blah\);*/
/*%put %file_list(iPath=e:\SASDev);*/
/*%put %file_list(iPath=e:\SASDev\,iFiles_only=1);*/
/*%put %file_list(iPath=e:\sasdev\,iFiles_only=1,iFilter=auto);*/
stop_sas 宏:
/******************************************************************************
** PROGRAM: MACRO.STOP_SAS.SAS
**
** DESCRIPTION: SIMPLE MACRO TO UNCONDITIONALLY END THE CURRENTLY RUNNING SAS CODE
**
** PARAMETERS: NONEE
**
*******************************************************************************
** VERSION:
** 1.0 ON: 25-SEP-15 BY: RP
** CREATED
** 1.1 ON: 05-OCT-15 BY: RP
** FIXED TYPO TO AVOID WARNING MESSAGE
******************************************************************************/
%macro stop_sas;
%if "&sysenv" eq "FORE" %then %do;
%abort cancel;
%end;
%else %do;
endsas;
%end;
%mend;