3

运行 SAS 程序时,我一直收到“A lock is not available”错误。如果我在一个程序中多次对同一数据集执行操作,通常会发生这种情况。在研究了这个错误之后,我的理解是这意味着 2 个程序正在尝试访问同一个数据集。换句话说,这类似于尝试打开已被他人或您自己使用的文档。这是给我此错误的代码示例:

data TSTONE.map;
    infile <PATH> delimiter = ',' truncover firstobs=2 dsd TERMSTR=cr LRECL=32760;

    format assessment_edition $45.;
    format ...

    input
    assessment_edition :$45.
    ...
    ;
run;

data tstone.map;
    set tstone.map;
    drop DistrictName ...;
run;

我在一些有很长的要导入或删除的字段列表的地方输入了“...”。所以,首先我导入了一个 CSV 文件,然后执行一个数据步骤来覆盖文件,删除一些我不需要的字段。我应该注意,有时当我运行这样的程序时,我不会收到任何锁定错误。没有其他用户正在访问这些数据集,它们在我的机器上。此外,如果我按顺序突出显示并运行 2 个数据步骤,我不会遇到任何问题。

4

4 回答 4

7

编辑 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;
于 2013-07-26T00:45:33.397 回答
3

首先,锁定错误可能只是时间问题。服务器可能没有从之前的导入中解锁文件。解决方法是不重用数据集名称;如果您需要做一些临时工作,则将文件导入临时 ( work.) 数据集,然后将该数据集设置为tstone.数据集。我发现服务器上更可能出现锁定错误(特别是使用 NAS),因为那里可能存在更多延迟;在本地磁盘上,文件锁定的延迟通常很短,以至于不会发生(但并非不可能)。

其次,DROP(如 KEEP 和 RENAME)可以在数据集选项中完成,几乎在任何 OUT= 或 SET 或任何类似存在的地方。例如,你可以做

proc import file=<whatever> out=tstone.map(drop=... list of drops...);
run;

没有理由强迫 SAS 重新处理整个数据集只是为了删除一些变量。

于 2013-07-25T15:53:17.447 回答
3

当我在 %do 循环中运行 proc append 时,我遇到了同样的问题,如下所示:

%do i=1996 %to 2013;
proc append base=inscat.pc_all data = pc&i. force;
run; quit;
%end;

这是在我的本地机器上,所以没有其他人试图访问数据集。发生的事情是循环运行得如此之快,以至于基本文件在开始编写循环的下一部分之前还没有关闭。经过大量的拉扯并尝试了许多复杂的解决方案,事实证明,您可以延长 SAS 在宣布锁定失败之前等待的时间。创建库时执行此操作:

libname inscat 'C:\Users\...\insurercat\data' filelockwait=5;

这只是将等待时间延长到 5 秒,然后 SAS 确定这是一个锁定错误(我相信从默认值 0 开始)。这个简单的选项解决了我遇到的所有锁定问题。

于 2015-08-27T16:43:45.337 回答
1

这是我们在Data Controller for SAS®和SASjs框架中的其他应用程序中使用的宏:

https://core.sasjs.io/mp__lockanytable_8sas.html

于 2022-02-26T21:03:36.487 回答