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 进行同步可能会有所帮助。

编辑:如果您想了解交易、锁定和阻塞,我推荐这两个视频:http ://technet.microsoft.com/en-us/sqlserver/gg545007.aspx和http://technet.microsoft.com /en-us/sqlserver/gg508892.aspx

于 2012-09-29T16:16:27.323 回答
0

这是我根据 TToni 的回答制作的 SqlClient 表的答案:

    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;
        }

        ...

对于 oracle 客户端,似乎需要:

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

-m

于 2012-10-02T07:07:26.633 回答