OleDb откуда происходит чтение при OleDBCommand.Read()

183
22 октября 2019, 12:10

выполняется запрос

OleDbCommand cmd=new OleDbCommand("select ...");
OleDbDataReader reader=cmd.ExecuteReader();
while(reader.Read()){
   ...
}

при выполнении Read() инфа берется из ОЗУ или каждый раз читается из базы?

Answer 1

Метод Read может считывать данные как из промежуточного буфера в памяти, так и напрямую из файла. Конкретные детали определяются используемой СУБД. Так как OLE DB чаще всего используют для работы с MS Access, имеет смысл рассмотреть именно его.

Для отслеживания обращений к файлу можно использовать механизм Event Tracing for Windows: Kernel tracing provider предоставляет событие FileIo_ReadWrite. Для использования ETW в C# подключим библиотеку Microsoft.Diagnostics.Tracing.TraceEvent. Напишем такой код для считывания 10 строк из БД и логирования событий чтения из файла (так как ETW выдает события с задержкой около секунды, между отдельными этапами работы с БД добавлены паузы, чтобы можно было отличить, какие события к чему относятся):

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Threading;
using Microsoft.Diagnostics.Tracing.Parsers;
using Microsoft.Diagnostics.Tracing.Session;
using System.Data.OleDb;
namespace ConsoleApp1
{    
    class Program
    {
        struct Timestamp : IComparable
        {
            public Timestamp(long t, string s)
            {
                tvalue = t;
                descr = s;
            }
            public long tvalue { get; set; }
            public string descr { get; set; }
            public int CompareTo(object obj)
            {
                return (int)(this.tvalue - ((Timestamp)obj).tvalue );
            }
            public override string ToString()
            {
                return tvalue.ToString()+": "+descr;
            }
        }    
        static string filename = "C:\\Test\\Database1.mdb";        
        static TraceEventSession m_EtwSession;
        static Stopwatch sw;
        static List<Timestamp> timestamps = new List<Timestamp>(100000);
        static object sync = new object();
        static ManualResetEvent mre = new ManualResetEvent(false);
        static void AddTimestamp(long t, string s)
        {
            lock (sync)
            {
                timestamps.Add(new Timestamp( t,s) );
            }
        }
        static void AddTimestamp( string s)
        {
            lock (sync)
            {
                timestamps.Add(new Timestamp(sw.ElapsedMilliseconds, s));
            }
        }
        static void ThreadProc()
        {
            int pid = Process.GetCurrentProcess().Id;
            //Создаем сессию ETW и подписываемся на события чтения файлов
            using (m_EtwSession = new TraceEventSession(KernelTraceEventParser.KernelSessionName))
            {
                m_EtwSession.StopOnDispose = true;
                m_EtwSession.EnableKernelProvider(KernelTraceEventParser.Keywords.FileIOInit |
                    KernelTraceEventParser.Keywords.DiskFileIO);
                m_EtwSession.Source.Kernel.FileIORead += data =>
                {
                    //сохраняем все события чтения файла БД текущим процессом
                    if (data.ProcessID == pid && data.FileName.ToLower().Trim() == filename.ToLower())  
                    {
                        AddTimestamp((long)data.TimeStampRelativeMSec,
                            "Bytes read: " + data.IoSize.ToString());                        
                    }  
                };
                lock (sync)
                {
                    sw = new Stopwatch();
                    sw.Start();
                }
                //сигнализируем основному потоку о готовности
                mre.Set();
                //запускаем Event Tracing
                m_EtwSession.Source.Process();
            }
        }
        public static void Main(string[] argv)
        {
            //создаем поток для Event Tracing
            Thread th = new Thread(ThreadProc);
            th.IsBackground = true;
            th.Start();
            //ждем готовности второго потока
            mre.WaitOne();
            string constr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source="+ filename+"; ";   
            string sql = "SELECT * FROM Table1";
            //подключаемся к БД
            OleDbConnection conn = new OleDbConnection(constr);
            AddTimestamp( "Start");
            Console.Write("Connecting...\r");
            conn.Open();
            AddTimestamp( "Connection.Open");
            Thread.Sleep(1000);
            //выполняем запрос и читаем первые 10 строк
            using (conn)
            {
                OleDbCommand cmd = new OleDbCommand(sql, conn);
                Console.Write("Executing SQL...\r");
                OleDbDataReader reader = cmd.ExecuteReader();
                AddTimestamp( "OleDbCommand.ExecuteReader");
                Thread.Sleep(1000);
                int i = 0;
                int n = 10;
                using (reader)
                    while (reader.Read())
                    {
                        AddTimestamp( "OleDbDataReader.Read ");
                        string s = reader[0].ToString();
                        Console.Write("Reading data... "+
                            s.PadLeft(5)+" ("+
                            i.ToString().PadLeft(4)+"/"+n.ToString()+") rows\r");
                        i++;
                        if (i > n) break;
                        Thread.Sleep(1000);
                    }                
            }
            AddTimestamp( "End");                       
            //завершаем сессию ETW
            m_EtwSession.Dispose();
            //выводим результаты
            Console.Clear();
            lock (sync)
            {
                timestamps.Sort();
                foreach (var ts in timestamps)
                {
                    Console.WriteLine(ts.ToString());
                }
            }
            Console.WriteLine("Press any key to exit ...");
            Console.ReadKey();            
        }    
    }    
}

Результат выглядит так:

7: Start
34: Connection.Open
154: Bytes read: 65536
154: Bytes read: 4096
154: Bytes read: 4096
154: Bytes read: 4096
1037: OleDbCommand.ExecuteReader
1170: Bytes read: 4096
1170: Bytes read: 4096
1170: Bytes read: 4096
1170: Bytes read: 4096
2038: OleDbDataReader.Read
2172: Bytes read: 4096
3040: OleDbDataReader.Read
4041: OleDbDataReader.Read
5042: OleDbDataReader.Read
5153: Bytes read: 512
5175: Bytes read: 4096
5175: Bytes read: 4096
6042: OleDbDataReader.Read
7043: OleDbDataReader.Read
8044: OleDbDataReader.Read
9044: OleDbDataReader.Read
10045: OleDbDataReader.Read
10153: Bytes read: 512
11045: OleDbDataReader.Read
12081: End

По результатам видно, что Jet считывает данные блоками по 4096 байт (размер страницы в Access) и помещает в промежуточный буфер в памяти. Если требуемая строка находится в буфере, она берется из него, в противном случае осуществляется чтение следующего блока. События считывания по 512 байт являются, по видимому, периодическим опросом какой-то служебной информации (возможно, состояния блокировки записей), так как они наблюдаются постоянно, пока открыто соединение с БД (даже если ничего не считывать).

READ ALSO
Вопрос по циклам в C#

Вопрос по циклам в C#

Сколько раз в этом цикле будет выполняться строка j = j - 1; Почему цикл выполниться 50 раз? Обьясните, пожалуйста

154
Программа дождь на с#

Программа дождь на с#

Создать приложение «Дождь», которое позволяет отображать на экране падающие капли дождяКапля представляет собой набор графических элементов...

198
DotNetZip очень долго упаковывает в архив

DotNetZip очень долго упаковывает в архив

Вот мой код, который упаковывает файлы из папки в архив:

157
Как передать массив файлов и текст AJAX в контроллер

Как передать массив файлов и текст AJAX в контроллер

Я формирую FormData из файлов и передаю в контроллер, но мне так же нужно передавать туда текст из TextAreaВот мой код на JS :

149