2

私はその前にメソッドを作成しました:

  1. テーブルをロックしました
  2. そこから値を読み取る
  3. 更新された値を書き戻しました
  4. テーブルのロックを解除しました

コードは Oracle で機能しました。現在、SQL Server 2008 では機能しません。方法は以下のとおりで、ロック解除コマンドを実行するとSqlException、テキストが表示されます。

「NOLOC」は、認識されたテーブル ヒント オプションではありません。テーブル値関数または CHANGETABLE 関数のパラメーターとして意図されている場合は、データベース互換モードが 90 に設定されていることを確認してください。

コード:

public static int GetAndSetMaxIdTable(DbProviderFactory factory, DbConnection cnctn, DbTransaction txn, int tableId, string userName, int numberOfIds)
{
        bool isLocked = false;
        string sql = string.Empty;
        string maxIdTableName;

        if (tableId == 0)
            maxIdTableName = "IdMax";
        else
            maxIdTableName = "IdMaxTable";

        try
        {
            bool noPrevRow = false;
            int realMaxId;

            if (factory is OracleClientFactory)
                sql = string.Format("lock table {0} in exclusive mode", maxIdTableName);
            else if (factory is SqlClientFactory)
                sql = string.Format("select * from {0} with (TABLOCKX)", maxIdTableName);
            else
                throw new Exception(string.Format("Unsupported DbProviderFactory -type: {0}", factory.GetType().ToString()));

            using (DbCommand lockCmd = cnctn.CreateCommand())
            {
                lockCmd.CommandText = sql;
                lockCmd.Transaction = txn;
                lockCmd.ExecuteNonQuery();
                isLocked = true;
            }

            using (DbCommand getCmd = cnctn.CreateCommand())
            {
                getCmd.CommandText = CreateSelectCommand(factory, tableId, userName, getCmd, txn);

                object o = getCmd.ExecuteScalar();
                if (o == null)
                {
                    noPrevRow = true;
                    realMaxId = 0;
                }
                else
                {
                    realMaxId = Convert.ToInt32(o);
                }
            }

            using (DbCommand setCmd = cnctn.CreateCommand())
            {
                if (noPrevRow)
                    setCmd.CommandText = CreateInsertCommand(factory, tableId, userName, numberOfIds, realMaxId, setCmd, txn);
                else
                    setCmd.CommandText = CreateUpdateCommand(factory, tableId, userName, numberOfIds, realMaxId, setCmd, txn);

                setCmd.ExecuteNonQuery();
            }
            if (factory is OracleClientFactory)
                sql = string.Format("lock table {0} in share mode", maxIdTableName);
            else if (factory is SqlClientFactory)
                sql = string.Format("select * from {0} with (NOLOC)", maxIdTableName);             

            using (DbCommand lockCmd = cnctn.CreateCommand())
            {
                lockCmd.CommandText = sql;
                lockCmd.Transaction = txn;
                lockCmd.ExecuteNonQuery();
                isLocked = false;
            }

            return realMaxId;
        }
        catch (Exception e)
        {
          ...
        }
}

それで、ここで何がうまくいかないのですか?このエラーはどこから来たのですか? サーバーまたはクライアント? Cコードからステートメントをコピーしましたが、そこで動作するはずです。残念ながら、デバッグして機能するかどうかを確認することはできません。

編集:(読み取りまたは更新せずに)ロックおよびロック解除しようとすると、同じ例外が発生します。

ありがとう & BR -マティ

4

2 に答える 2

2

TABLOCKX ヒントは意図したとおりにテーブルをロックしますが、手動でロックを解除することはできません。ロックが維持される時間は、トランザクション レベルによって異なります。接続にアクティブなトランザクションがない場合、SELECT の実行中はロックが保持され、その後は破棄されます。

「テーブルをロックする -> テーブルを操作する -> ロックを解除する」というシーケンスを実現したい場合は、次の T-SQL スクリプトに相当する ADO.NET を実装する必要があります。

BEGIN TRAN
    SELECT TOP (1) 1 FROM myTable (TABLOCKX, KEEPLOCK)
    -- do something with the table
COMMIT -- This will release the lock, if there is no outer transaction present

DbCommand オブジェクトを介して "BEGIN TRAN"/"COMMIT" を実行するか、System.Data.SqlClient.SqlTransaction クラスを使用してトランザクションを開始し、コミットすることができます。

注意: このアプローチは、接続がまだトランザクションに登録されていない場合にのみ機能します。SQL Server はネストされたトランザクションをサポートしていないため、COMMIT は何も実行せず、ロックが保持されます。すでに実行中のトランザクションがある場合は、トランザクションが完了するまでロックを解放できません。この場合、sp_getapplock/sp_releaseapplock による同期が役立つ可能性があります。

編集: トランザクション、ロック、ブロックについて学びたい場合は、次の 2 つのビデオをお勧めます/en-us/sqlserver/gg508892.aspx

于 2012-09-29T16:16:27.323 に答える
0

TToniの回答に基づいて作成したコードを含むSqlClientの1つのテーブルの回答は次のとおりです。

    public static int GetAndSetMaxIdTable(DbProviderFactory factory, DbConnection cnctn,   DbTransaction txn, int numberOfIds)
    {
            bool noPrevRow = false;
            int realMaxId;


            using (DbCommand getCmd = cnctn.CreateCommand())
            {
                getCmd.CommandText = "SELECT MaxId FROM IdMax WITH (TABLOCKX)"
                getCmd.Transaction = txn;

                object o = getCmd.ExecuteScalar();
                if (o == null)
                {
                    noPrevRow = true;
                    realMaxId = 0;
                }
                else
                {
                    realMaxId = Convert.ToInt32(o);
                }
            }

            using (DbCommand setCmd = cnctn.CreateCommand())
            {
                if (noPrevRow)
                    setCmd.CommandText = CreateInsertCommand(factory, tableId, userName, numberOfIds, realMaxId, setCmd, txn);
                else
                    setCmd.CommandText = CreateUpdateCommand(factory, tableId, userName, numberOfIds, realMaxId, setCmd, txn);

                setCmd.ExecuteNonQuery();
            }

            return realMaxId;
    }

それは私がこのようなものです:

        ...

        try
        {
            using (txn = cnctn.BeginTransaction())
            {
                oldMaxId = GetAndSetMaxIdTable(factory, cnctn, txn, 5);
                for (i = 0; i < 5; i++)
                {
                    UseNewIdToInsertStuff(factory, cnctn, txn, oldMaxId + i + 1)
                }
                txn.Commit();
                return true;
            }
        }
        catch (Exception e)
        {
            // don't know if this is needed
            if (txn != null && cnctn.State == ConnectionState.Open)
                txn.Rollback();

            throw e;
        }

        ...

オラクルクライアントの場合、次のものが望ましいと思われます。

SELECT MaxId from IdMax WHERE ... FOR UPDATE OF MaxId

-m

于 2012-10-02T07:07:26.633 に答える