Использование курсоров для чтения данных временных рядов из SQL Server с использованием С#?

У меня есть большая база данных (50 миллионов строк), содержащая данные временных рядов. В столбце [datetime] есть кластеризованный индекс, который гарантирует, что таблица всегда сортируется в хронологическом порядке.

Каков наиболее эффективный способ считывания строк таблицы в приложение C# построчно?


person Contango    schedule 24.06.2011    source источник
comment
В столбце [datetime] есть кластеризованный индекс, который обеспечивает постоянную сортировку таблицы — он гарантирует, что запрос строк в том же порядке является относительно дешевой операцией. Он не дает никаких гарантий ни о фактическом порядке хранения строк, ни о порядке извлечения строк, если не указано условие ORDER BY.   -  person Damien_The_Unbeliever    schedule 24.06.2011
comment
Кроме того, вы уверены, что использование 50 миллионов строк в приложении C#, по одной строке за раз, является лучшим способом обработки этих данных?   -  person Damien_The_Unbeliever    schedule 24.06.2011
comment
Кроме того, будет ли вам сложно попробовать это и посмотреть, как оно работает? Используйте простой запрос, например SELECT * FROM MyTable ORDER BY clustered_index_column   -  person John Saunders    schedule 24.06.2011
comment
@John Saunsers MyTable 300 миллионов строк и 50 ГБ. Единственный способ передать эти данные в мое приложение — медленно считывать их по строкам или фрагментам.   -  person Contango    schedule 24.06.2011
comment
@Gravitas: 50 миллионов или 300 миллионов?   -  person John Saunders    schedule 24.06.2011
comment
@John Saunders Когда я опубликовал, было 50 миллионов, а сейчас 330 миллионов. Загрузка происходит очень быстро, у меня она работает со скоростью 100 тыс. строк в секунду на обычном оборудовании, несмотря на то, что она имеет кластерный индекс в столбце [datetime].   -  person Contango    schedule 28.06.2011
comment
@Gravitas: см. мой ответ ниже. Первый ряд пришел сразу.   -  person John Saunders    schedule 28.06.2011


Ответы (3)


Вы должны попробовать это и узнать. Я только что сделал и не видел проблем с производительностью.

USE [master]
GO
/****** Object:  Database [HugeDatabase]    Script Date: 06/27/2011 13:27:50 ******/
CREATE DATABASE [HugeDatabase] ON  PRIMARY 
( NAME = N'HugeDatabase', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQL2K8R2\MSSQL\DATA\HugeDatabase.mdf' , SIZE = 1940736KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
 LOG ON 
( NAME = N'HugeDatabase_log', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL10_50.SQL2K8R2\MSSQL\DATA\HugeDatabase_log.LDF' , SIZE = 395392KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)
GO

USE [HugeDatabase]
GO
/****** Object:  Table [dbo].[HugeTable]    Script Date: 06/27/2011 13:27:53 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[HugeTable](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [PointInTime] [datetime] NULL,
PRIMARY KEY NONCLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE CLUSTERED INDEX [IX_HugeTable_PointInTime] ON [dbo].[HugeTable] 
(
    [PointInTime] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO

Заполнить:

DECLARE @t datetime
SET @t = '2011-01-01'

DECLARE @i int
SET @i=0

SET NOCOUNT ON

WHILE (@i < 50000000)
BEGIN
    INSERT INTO HugeTable(PointInTime) VALUES(@t)
    SET @t = DATEADD(ss, 1, @t)

    SET @i = @i + 1
END

Контрольная работа:

using System;
using System.Data.SqlClient;
using System.Diagnostics;

namespace ConsoleApplication1
{
    internal class Program
    {
        private static void Main()
        {
            TimeSpan firstRead = new TimeSpan();
            TimeSpan readerOpen = new TimeSpan();
            TimeSpan commandOpen = new TimeSpan();
            TimeSpan connectionOpen = new TimeSpan();
            TimeSpan secondRead = new TimeSpan();

            try
            {

                Stopwatch sw1 = new Stopwatch();
                sw1.Start();
                using (
                    var conn =
                        new SqlConnection(
                            @"Data Source=.\sql2k8r2;Initial Catalog=HugeDatabase;Integrated Security=True"))
                {
                    conn.Open(); connectionOpen = sw1.Elapsed;

                    using (var cmd = new SqlCommand(
                        "SELECT * FROM HugeTable ORDER BY PointInTime", conn))
                    {
                        commandOpen = sw1.Elapsed;

                        var reader = cmd.ExecuteReader(); readerOpen = sw1.Elapsed;

                        reader.Read(); firstRead = sw1.Elapsed;
                        reader.Read(); secondRead = sw1.Elapsed;
                    }
                }
                sw1.Stop();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            finally
            {

                Console.WriteLine(
                    "Connection: {0}, command: {1}, reader: {2}, read: {3}, second read: {4}",
                    connectionOpen,
                    commandOpen - connectionOpen,
                    readerOpen - commandOpen,
                    firstRead - readerOpen,
                    secondRead - firstRead);

                Console.Write("Enter to exit: ");
                Console.ReadLine();
            }
        }
    }
}
person Community    schedule 27.06.2011
comment
Вау, действительно хороший ответ - сейчас я запускаю пример. - person Contango; 28.06.2011
comment
Это работает очень хорошо. Однако, если вы не используете Top 1000000 для ограничения количества строк, возвращаемых оператором select, существует 30-секундный промежуток, если оператор using() завершается раньше (т. е. если пользователь нажимает кнопку отмены). - person Contango; 28.06.2011
comment
@Gravitas: да, на моей машине время ожидания Dispose of SqlDataReader истекло. Вот почему вокруг него нет блока using. - person John Saunders; 28.06.2011

Я бы использовал SqlDataReader, поскольку он передает свои Результаты. Вам все равно придется указывать порядок, но если вы используете кластеризованный индекс для ORDER BY, это должна быть (относительно) дешевая операция.

using (var db = new SqlConnection(connStr)) {
    using (var rs = new SqlCommand(someQuery, db).ExecuteReader()) {
        while (rs.Read()) {
            // do interesting things!
        }
    }
}
person Yuck    schedule 24.06.2011
comment
Это будет работать для небольших баз данных, но не для больших баз данных. Проблема в том, что если бы я сначала сделал выбор для всей таблицы, это заняло бы 30 минут, после чего я мог бы начать потоковую передачу результатов обратно в свое приложение. Есть ли более быстрый способ? - person Contango; 24.06.2011
comment
@Gravitas: это нанесет наименьший вред серверу. Альтернативой (во всяком случае, теоретически) было бы заполнение DataSet информацией, которую вам нужно обработать. Это может быть более быстрой операцией, но, учитывая размер вашей таблицы, я думаю, что на практике она задохнется и умрет. - person Yuck; 24.06.2011
comment
Возможно, используйте курсор для заполнения временной таблицы на сервере, а затем перенесите временную таблицу с помощью SqlDataReader? - person Contango; 24.06.2011
comment
@Gravitas: О, мерзко. Я забыл, что ваш пункт заказа требует, чтобы весь набор был оценен, прежде чем результаты могут быть возвращены. - person Yuck; 24.06.2011
comment
@Юк: ты уверен? Я бы подумал, что будет учитываться кластеризованный индекс и без каких-либо агрегатов в запросе строки могли начать возвращаться сразу. - person John Saunders; 24.06.2011
comment
@John: Да, строки скорее всего будут возвращены в порядке кластеризованного индекса. Конечно, всегда следует помнить, что SQL не гарантирует порядок, если вы не укажете его явно. Это означает, что механизм оптимизатора/базы данных может возвращать данные так, как он считает наиболее эффективным. Почти в каждом случае это будет означать использование порядка кластеризованного индекса, но может быть и какой-то другой порядок. - person Yuck; 24.06.2011
comment
@Yuck: я имел в виду не столько порядок, сколько предположение о том, что весь набор необходимо отсортировать, прежде чем можно будет вернуть первую строку. Я думаю, что из-за индекса строки могут быть возвращены быстрее. - person John Saunders; 24.06.2011
comment
Даже если строки начнут возвращаться немедленно, весь запрос будет продолжать выполняться в фоновом режиме, что было бы неоптимально, если вы хотите получить только первый 1% из 50 миллионов строк. Может понадобиться курсор или аналог курсора. - person Contango; 24.06.2011
comment
@Gravitas: если вам нужен только первый 1%, используйте в запросе предложение TOP. - person John Saunders; 28.06.2011
comment
Вы всегда можете выйти из цикла чтения на устройстве чтения данных, поэтому, как только вы прочитали 1%, просто прекратите цикл. правильно? - person Brady Moritz; 31.10.2011