Nice programing

Entity Framework를 사용하여 읽기시 테이블을 어떻게 잠글 수 있습니까?

nicepro 2020. 12. 12. 12:29
반응형

Entity Framework를 사용하여 읽기시 테이블을 어떻게 잠글 수 있습니까?


Entity Framework (4.1)를 사용하여 액세스하는 SQL Server (2012)가 있습니다. 데이터베이스에는 독립 프로세스가 새 URL을 제공하는 URL이라는 테이블이 있습니다. URL 테이블의 항목은 "New", "In Process"또는 "Processed"상태 일 수 있습니다.

다른 컴퓨터 에서 URL 테이블에 액세스하고 "New"상태의 URL 항목을 확인한 다음 첫 번째 항목을 "In Process"로 표시해야합니다.

var newUrl = dbEntity.URLs.FirstOrDefault(url => url.StatusID == (int) URLStatus.New);
if(newUrl != null)
{
    newUrl.StatusID = (int) URLStatus.InProcess;
    dbEntity.SaveChanges();
}
//Process the URL

쿼리와 업데이트가 원자 적이 지 않기 때문에 두 대의 컴퓨터가 데이터베이스에서 동일한 URL 항목을 읽고 업데이트하도록 할 수 있습니다.

이러한 충돌을 피하기 위해 선택 후 업데이트 시퀀스를 원자 단위로 만드는 방법이 있습니까?


나는 테이블에 수동으로 lock 문을 발행함으로써 이것을 실제로 달성 할 수 있었다. 이것은 완전한 테이블 잠금을 수행하므로 조심하십시오! 제 경우에는 여러 프로세스가 한 번에 연결되는 것을 원하지 않는 대기열을 만드는 데 유용했습니다.

using (Entities entities = new Entities())
using (TransactionScope scope = new TransactionScope())
{
    //Lock the table during this transaction
    entities.Database.ExecuteSqlCommand("SELECT TOP 1 KeyColumn FROM MyTable WITH (TABLOCKX, HOLDLOCK)");

    //Do your work with the locked table here...

    //Complete the scope here to commit, otherwise it will rollback
    //The table lock will be released after we exit the TransactionScope block
    scope.Complete();
}

업데이트 -Entity Framework 6, 특히 async/ await코드를 사용하는 경우 트랜잭션을 다르게 처리해야합니다. 이것은 일부 변환 후 우리에게 충돌했습니다.

using (Entities entities = new Entities())
using (DbContextTransaction scope = entities.Database.BeginTransaction())
{
    //Lock the table during this transaction
    entities.Database.ExecuteSqlCommand("SELECT TOP 1 KeyColumn FROM MyTable WITH (TABLOCKX, HOLDLOCK)");

    //Do your work with the locked table here...

    //Complete the scope here to commit, otherwise it will rollback
    //The table lock will be released after we exit the TransactionScope block
    scope.Commit();
}

Andre의 대답에 주석을 추가 할 수는 없지만 "IsolationLevel.RepeatableRead는 읽은 모든 행에 잠금을 적용하여 테이블 A를 읽은 경우 스레드 2가 테이블 A에서 읽을 수 없도록합니다. 스레드 1과 스레드 1이 트랜잭션을 완료하지 못했습니다. "

반복 가능한 읽기 전용은 트랜잭션이 끝날 때까지 모든 잠금을 유지한다는 것을 의미합니다. 트랜잭션에서이 격리 수준을 사용하고 행 (예 : 최대 값)을 읽을 때 "공유"잠금이 발행되고 트랜잭션이 완료 될 때까지 유지됩니다. 이 공유 잠금은 다른 스레드가 행을 업데이트하는 것을 방지하지만 (업데이트는 해당 행에 배타적 잠금을 적용하려고 시도하고 기존 공유 잠금에 의해 차단됨) 다른 스레드가 값을 읽을 수 있도록 허용합니다 (두 번째 스레드 행에 다른 공유 잠금을 넣습니다. 허용됩니다 (이것이 공유 잠금이라고하는 이유입니다). 따라서 위의 문을 올바로 작성하려면 "IsolationLevel.RepeatableRead는 스레드 2가 업데이트 할 수없는 방식으로 읽은 모든 행에 잠금을 적용합니다. 스레드 1이 테이블 A를 읽었고 스레드 1이 트랜잭션을 완료하지 않은 경우 테이블 A입니다. "

원래 질문의 경우 반복 가능한 읽기 격리 수준을 사용하고 두 프로세스가 동일한 값을 읽고 업데이트하지 못하도록 잠금배타 잠금 으로 에스컬레이션해야 합니다. 모든 솔루션에는 EF를 사용자 지정 SQL에 매핑하는 것이 포함됩니다 (잠금 유형을 에스컬레이션하는 것은 EF에 내장되지 않음). jocull 대답을 사용하거나 출력 절과 함께 업데이트를 사용하여 행을 잠글 수 있습니다 (업데이트 문은 항상 배타적 잠금을 가져오고 2008 이상에서는 결과 집합을 반환 할 수 있음).


@jocull이 제공 한 대답은 훌륭합니다. 이 조정을 제공합니다.

대신 :

"SELECT TOP 1 KeyColumn FROM MyTable WITH (TABLOCKX, HOLDLOCK)"

이 작업을 수행:

"SELECT TOP 0 NULL FROM MyTable WITH (TABLOCKX)"

This is more generic. You can make a helper method that simply takes the table name as a parameter. No need to know of the data (aka any column names), and there is no need to actually retrieve a record down the pipe (aka TOP 1)


You could try passing a UPDLOCK hint to the database and just lock specific rows.. So that what it selects to update it also aquires an exclusive lock on so it can save its changes (rather than just acquiring a readlock in the beginning, that it later tries to upgrade later when saving). The Holdlock suggested by jocull above is also a good idea too.

private static TestEntity GetFirstEntity(Context context) {
return context.TestEntities
          .SqlQuery("SELECT TOP 1 Id, Value FROM TestEntities WITH (UPDLOCK)")
          .Single();
}

I strongly advise considering optimistic concurrency: https://www.entityframeworktutorial.net/EntityFramework5/handle-concurrency-in-entity-framework.aspx

참고URL : https://stackoverflow.com/questions/13404061/how-can-i-lock-a-table-on-read-using-entity-framework

반응형