代码之家  ›  专栏  ›  技术社区  ›  BartoszKP JuanBoca

已经有一个打开的DataReader。。。即使不是

  •  6
  • BartoszKP JuanBoca  · 技术社区  · 10 年前

    注: 当问题没有正确处理读卡器/连接,或者错误是由于处理不当的延迟加载时,我已经回答了数百万个问题。我认为这个问题是另一个问题,可能与MySQL的.NET连接器有关。

    我通过其.NET连接器(6.8.3)广泛使用MySQL服务器(5.6)数据库。出于性能原因,所有表都使用MyISAM引擎创建。我只有 一个过程 具有 一个线程 ( 更新: 事实上,这不是真的,请参见下文)顺序访问数据库,因此不需要事务和并发。

    今天,经过几个小时的处理后,出现了以下代码:

    public IEnumerable<VectorTransition> FindWithSourceVector(double[] sourceVector)
    {
        var sqlConnection = this.connectionPool.Take();
    
        this.selectWithSourceVectorCommand.Connection = sqlConnection;
    
        this.selectWithSourceVectorCommand.Parameters["@epsilon"].Value
            = this.epsilonEstimator.Epsilon.Min() / 10;
    
        for (int d = 0; d < this.dimensionality; ++d)
        {
            this.selectWithSourceVectorCommand.Parameters["@source_" + d.ToString()]
            .Value = sourceVector[d];
        }
    
        // *** the following line (201) throws the exception presented below
        using (var reader = this.selectWithSourceVectorCommand.ExecuteReader())
        {
            while (reader.Read())
            {
                yield return ReaderToVectorTransition(reader);
            }
        }
    
        this.connectionPool.Putback(sqlConnection);
    }
    

    引发了以下异常:

    MySqlException:已存在与此连接关联的打开的DataReader,必须首先关闭该连接。

    以下是堆栈跟踪的相关部分:

    位于MySql.Data.MySqlClient.ExceptionInterceptor.Row(异常异常) 位于MySql.Data.MySqlClient.MySqlConnection.Row(异常ex) 位于MySql.Data.MySqlClient.MySqlCommand.CheckState() 位于MySql.Data.MySqlClient.MySqlCommand.ExecuteReader(CommandBehavior行为) 位于MySql.Data.MySqlClient.MySqlCommand.ExecuteReader() 在C:\Users\bartoszp中实现.VectorTransitionsMySqlTable.d_27.MoveNext()。。。\implementation\VectorTransitionsMySqlTable.cs:line 201

    位于System.Linq.Enumerable.d_3a 1.MoveNext() at System.Linq.Buffer 1..ctor(IEnumerable 1 source) at System.Linq.Enumerable.ToArray[TSource](IEnumerable 1个来源) 在实现时。VectorTransitionService.Add(VectorTransition VectorTransition)在C:\Users\bartoszp中。。。\implementation\VectorTransitionService.cs:line 38

    在程序中。转到[T](环境`2 p,Space parentSpace,EpsilonEstimator epssilonEstuator,ThresholdEstimator thresholdEstimater,TransitionTransformer TransitionTransformer,AmbiguityCalculator ac,VectorTransitionTableFactory vttf,AxesTableFactory atf,NeighbourhoodsTableFactory ntf,AmbigiguitySamplesTableFactory astf,AmbinguitySampleMatchesTableFactory asmtf,MySqlConnectionPool connectionPool,Boolean rejectDu在C:\Users\bartoszp中,重复项、布尔addNew)。。。\程序.cs:line 323

    这个 connectionPool.Take 返回满足以下谓词的第一个连接:

    private bool IsAvailable(MySqlConnection connection)
    {
        var result = false;
    
        try
        {
            if (connection != null
                && connection.State == System.Data.ConnectionState.Open)
            {
                result = connection.Ping();
            }
        }
        catch (Exception e)
        {
            Console.WriteLine("Ping exception: " + e.Message);
        }
    
        return result && connection.State == System.Data.ConnectionState.Open;
    }
    

    (这与我之前的问题有关,当时我解决了一个不同但相似的问题: MySQL fatal error during information_schema query (software caused connection abort) )

    这个 FindWithSourceVector 方法由以下代码段调用:

    var existing
        = this.vectorTransitionsTable
            .FindWithSourceVector(vectorTransition.SourceVector)
            .Take(2)
            .ToArray();
    

    (我最多需要找到两个重复的向量)-这是 VectorTransitionService.cs:line 38 堆栈跟踪的一部分。

    现在最有趣的部分是:当调试器在异常发生后停止执行时,我已经研究了 sqlConnection 要查找的对象,即 它没有关联的读取器 (下图)!

    enter image description here

    为什么会发生这种情况(显然是“随机”的——在过去的~20小时里,几乎每分钟都会调用这种方法)?我能避免这种情况吗 Ping 抛出异常并祈祷它会有所帮助)?


    有关连接池实施的其他信息:

    Get 用于只调用简单查询而不使用读取器的方法,因此可以以可重入方式使用返回的连接。本例中未直接使用(因为涉及读者):

    public MySqlConnection Get()
    {
        var result = this.connections.FirstOrDefault(IsAvailable);
    
        if (result == null)
        {
            Reconnect();
    
            result = this.connections.FirstOrDefault(IsAvailable);
        }
    
        return result;
    }
    

    这个 Reconnect 方法只是遍历整个数组并重新创建和打开连接。

    Take 使用 收到 但也会从可用连接列表中删除返回的连接,因此如果某些方法在使用读取器期间调用其他也需要连接的方法,则不会共享该连接。这里也不是这样,因为 FindSourceVector 方法很简单(不调用使用DB的其他方法)。然而 用于惯例-如果有读者,请使用 :

    public MySqlConnection Take()
    {
        var result = this.Get();
    
        var index = Array.IndexOf(this.connections, result);
    
        this.connections[index] = null;
    
        return result;
    }
    

    Putback 只需将连接放置到第一个空点,或者在连接池已满时忘记它:

    public void Putback(MySqlConnection mySqlConnection)
    {
        int index = Array.IndexOf(this.connections, null);
    
        if (index >= 0)
        {
            this.connections[index] = mySqlConnection;
        }
        else if (mySqlConnection != null)
        {
            mySqlConnection.Close();
            mySqlConnection.Dispose();
        }
    }
    
    2 回复  |  直到 7 年前
        1
  •  3
  •   Jon Skeet    10 年前

    我怀疑这就是问题所在,在方法结束时:

    this.connectionPool.Putback(sqlConnection);
    

    你只是 迭代器中的两个元素-因此您永远不会完成 while 循环,除非实际上只有一个从读取器返回的值。现在您正在使用LINQ,它将自动调用 Dispose() 在迭代器上 using 语句仍将处理读取器,但您不会将连接放回池中。如果你在 finally 布洛克,我想你会没事的:

    var sqlConnection = this.connectionPool.Take();
    try
    {
        // Other stuff here...
    
        using (var reader = this.selectWithSourceVectorCommand.ExecuteReader())
        {
            while (reader.Read())
            {
                yield return ReaderToVectorTransition(reader);
            }
        }
    }
    finally
    {
        this.connectionPool.Putback(sqlConnection);
    }
    

    或者理想情况下,如果您的连接池是您自己的实现 Take 返回实现的东西 IDisposable 并在完成后将连接返回到池。

    这里有一个简短但完整的程序来演示正在发生的事情,而不涉及任何实际的数据库:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    class DummyReader : IDisposable
    {
        private readonly int limit;
        private int count = -1;
        public int Count { get { return count; } }
    
        public DummyReader(int limit)
        {
            this.limit = limit;
        }
    
        public bool Read()
        {
            count++;
            return count < limit;
        }
    
        public void Dispose()
        {
            Console.WriteLine("DummyReader.Dispose()");
        }
    }
    
    class Test
    {    
        static IEnumerable<int> FindValues(int valuesInReader)
        {
            Console.WriteLine("Take from the pool");
    
            using (var reader = new DummyReader(valuesInReader))
            {
                while (reader.Read())
                {
                    yield return reader.Count;
                }
            }
            Console.WriteLine("Put back in the pool");
        }
    
        static void Main()
        {
            var data = FindValues(2).Take(2).ToArray();
            Console.WriteLine(string.Join(",", data));
        }
    }
    

    正如所写的那样,读者只需找到两个值就可以对情况进行建模,结果如下:

    Take from the pool
    DummyReader.Dispose()
    0,1
    

    请注意,读取器已被释放,但我们从未从池中返回任何内容。如果你改变了 Main 模拟读者只有一个值的情况,如下所示:

    var data = FindValues(1).Take(2).ToArray();
    

    然后我们一路通过 虽然 循环,因此输出更改:

    Take from the pool
    DummyReader.Dispose()
    Put back in the pool
    0
    

    我建议你复制我的程序并进行实验。确保你了解所发生的一切…然后你可以将其应用到自己的代码中。你可能想看看我的文章 iterator block implementation details

        2
  •  0
  •   Community Egal    7 年前

    TyCobb Jon Skeet 正确地猜测,问题是池实现和多线程。我忘了我确实开始了一些小动作 Task s在 Reconnect 方法第一个连接是同步创建和打开的,但其他所有连接都是异步打开的。

    因为我一次只需要一个连接,所以其他人可以在不同的线程中重新连接。然而,因为我并不总是把连接放回去(如 Jon's answer )重新连接非常频繁,由于系统负载过重,这些重新连接线程速度不够快,最终导致了竞争状况。修复方法是以更简单直接的方式重新连接:

    private void Reconnect()
    {
        for (int i = 0; i < connections.Length; ++i)
        {
            if (!IsAvailable(this.connections[i]))
            {
                this.ReconnectAt(i);
            }
        }
    }
    
    private void ReconnectAt(int index)
    {
        try
        {
            this.connections[index] = new MySqlConnection(this.connectionString);
            this.connections[index].Open();
        }
        catch (MySqlException mse)
        {
            Console.WriteLine("Reconnect error: " + mse.Message);
            this.connections[index] = null;
        }
    }