目前面临问题如下一台服务器上的GPS数据接收程序,7X24小时不间断运行。
目前GPS源约有6000台左右,至少每台30秒回传一次数.一天的数据量约4个G。
为了提高查询速度,决定分库分表来存取。每一天生成一个数据库(按日期命名),每个库里每一个GPS数据源为一个表。我的程序通过socket不停的接收从网络过来的GPS定位数据,按协议解包后,利用线程池去解析内容,生成SQL语句,然后存入一个队列中。程序有一个专门的线程负责写入数据库,但是忙不过来,大约每秒插入150条就到了极限了。起先我认为是线程忙不过来,又改成N个线程同时入,后来发现性能还下降了1倍,估计150条是SQL Server服务器的极限了,队列读写同步反而令线程耗费开支,造成性能下降。有没有高人出来指点一下.
目前测试环境如下:CPU     赛扬双核 1.8G
内存    2G
系统    Windows server 2003
SQL     SQL Server2005

解决方案 »

  1.   

    附带存入数据库的部份代码
            /// <summary>
            /// 将SQL语句压入任务队表
            /// </summary>
            /// <param name="strSQL">Insert语句</param>
            /// <param name="strDBname">目标数据库名</param>
            /// <returns></returns>
            public bool ExecuteSQL(string strSQL, string strDBname)
            {
                try
                {
                    //利用信号机,限制最大入队数,避免任务队列被撑爆
                    m_Semaphore.WaitOne();                //sqlTask   我也偿试过构个对象池来装,实现对像重用,不需要每次都new新的,发现性能下降了
                    //          原因在于出池入池的锁,消耗了开支    
                    SQL_TASK sqlTask = new SQL_TASK(MakeADOConnectString(strDBname), strSQL);                lock (m_sqlTask)
                    {                   
                        //压入队列
                        m_sqlTask.Enqueue(sqlTask);
                        _TaskCount = m_sqlTask.Count;
                        //重置一下信号,避免入库线程饿死
                        m_Event.Set();                    
                    }
                    sqlTask = null;
                    return true;
                }
                catch (Exception ex)
                {
                    dbgPrint(ex);
                    return false;
                }
            }        /// <summary>
            /// 入库线程
            /// </summary>
            private void DoInsertThread()
            {
                try
                {
                    SQL_TASK sqlTask = null;
                    string strDBName=string.Empty;
                    string strSQL = string.Empty;
                    bool blnHaveTask = false;                SqlCommand objSQLCommand = new SqlCommand();
                    while (true)
                    {
                        lock (m_sqlTask)
                        {                       
                            _TaskCount = m_sqlTask.Count;
                            if (_TaskCount > 0)
                            {
                                blnHaveTask = true;                         
                                //将任务出队
                                sqlTask = m_sqlTask.Dequeue();
                                strDBName = sqlTask.DBName;
                                strSQL = sqlTask.Command;
     
                                m_Semaphore.Release();   
                            }
                            else
                            {
                                m_Event.Reset();
                                blnHaveTask = false;
                            }
                        }                    
     
                        if (blnHaveTask)
                        {                         
                            try
                            {
                                using (SqlConnection cn = new SqlConnection(strDBName))
                                {
                                    cn.Open();
                                    if (cn.State == ConnectionState.Open)
                                    {
                                        //SqlCommand cmd = m_SqlCommandPool.Pop();
                                        try
                                        {
                                            objSQLCommand.Connection = cn;
                                            objSQLCommand.CommandText = strSQL;
                                            objSQLCommand.CommandType = CommandType.Text;
                                            objSQLCommand.ExecuteNonQuery();
                                        }
                                        catch (Exception ex)
                                        {
                                            CDebug.Print(ex.ToString() + "\r\n" + sqlTask.Command);
                                        }
                                        finally
                                        {
                                            //释放对连接的引用
                                            objSQLCommand.Connection = null;
                                           // m_SqlCommandPool.Push(cmd);
                                        }
                                    }
                                }
                            }
                            catch (ThreadAbortException threadError)
                            {
                                System.Diagnostics.Debug.WriteLine(threadError);
                            }
                            catch (Exception ex)
                            {
                                CDebug.Print(ex.ToString() + "\r\n" + strSQL);
                            }                        //释放引用
                            sqlTask = null;                        if (m_blnExit)
                            {
                                //退出的时候,将队列缓冲的数量报告一下界面,缓冲数多的时候,避免用户强制关闭进程,丢失数据
                                if (OnShowReleaseTaskBuffer != null)
                                {
                                    OnShowReleaseTaskBuffer(_TaskCount);
                                }
                            }                                            
                        }
                        else
                        {
                            //队列没有数据时,就停下来
                            m_Event.WaitOne();
                        }
                        blnHaveTask = false;
                    }
                }
                catch (ThreadAbortException threadError)
                {
                    System.Diagnostics.Debug.WriteLine(threadError);
                }
                catch (Exception ex)
                {
                    dbgPrint(ex);
                }
            }
      

  2.   

    目前测试环境如下:CPU 赛扬双核 1.8G
    内存 2G
    系统 Windows server 2003
    SQL SQL Server2005就你这个服务器?不仅分库分表,还得分服务器来做。
      

  3.   

    这不是营运中的服务器,是我测试用的机器,
    营运服务器最低都是1U的,四核至强。
    大家有没有好的方案,在硬件环境不变的条件下,能尽量提升数据库性能,压榨出SQL最大潜力。我感觉我目前的硬件环境 , 这个插入速度应该还不是SQL的极限,毕竟它还远没逼近到硬盘的IO极限
      

  4.   

    使用sqlbulkcopy类提高插入效率
    http://blogs.msdn.com/b/mikecha/archive/2009/08/07/two-fast-ways-to-bulk-insert-client-generated-data-to-sql-database.aspx另有条件disk做成raid10
      

  5.   

    之前看过sqlbulkcopy的介绍,它不适合这个场合。因为是每一个GPS数据定位数据用单独的一个表来记录,这样下来,约有7000多张表。另外每个GPS产生报警的数据的时候,还要存报警数据到报警表,还会有图片数据包到来,也是单独的表。每次从socket过来的数据包都是随机的,如果分批的话。每一个GPS数据源需要至少要维护3张DataTable在内存中,sqlbulkcopy在攒到一定量的数据时,批量刷到服务器才会有性能提升。假如我每个表攒至少1000条,才刷一次,这样7K * 3 * 1000 。内存的消耗量会相当可观。内存用量上升,导至换内存页机率上升,整个系统的性能都会下降。
    至于硬件条件,我感觉还远没有达到它的极限,CPU占用率平均都在30左右,硬盘IO量也很小,1秒不到0.1M的写入量。所以从程序员的角度,有没有更好的优化代码,把存入速度再提高一些。
    刚才试了4核至强的机器上,SQL2000的库。插入速度平均也就在165-170条/s 左右。跟赛扬1.8 的机器每秒145条的区别大不,所以跟硬件限制没多大关系。
      

  6.   

    7K * 3 * 1000 
    一条记录如果说是10个字节的话 一次21M,30s写入一次不会有什么内存消耗可观.
    sqlbulkcopy 广泛应用的批量插入方式,在下孤陋寡闻了没听说还有什么比这个更优秀的程式批量导入了.
    一条条insert事务的开销就得多大?如此当然会blocking了
      

  7.   

    目前找不到更好的优化办法了,这几天尝试了朋友们提供所有的方式,结果差异都不大。
    最终还是用回最先的方案。IO操作1-2个线程就够了,再多只会性能下降。在干净安装的机器上测试,每个数据包我激发3次。相当于放大三倍流量,平均插入速度在每秒350左右,
    缓冲数很难降下来了。测试机器环境:赛杨E1500 2.2G
    内存2G
    硬盘SATA日立250G
    WINDOWS 2003
    SQL2005程序放在正式营运的服务器上跑,每秒插入速度大约在110左右。除了本程序读写数据库外,还有IIS和别的几个程序需要不停的读写。所以比测试环境性能差许多。
    估计只能从硬件上着手了。RAID不知道能不能提高一些速度
      

  8.   

    不要每条记录就用一句insert
    要收到100(或1000)条后,生成这样的Insert语句
    insert tb (字段们)
    select 实际数据
    union all select 实际数据
    union all select 实际数据
    ...
    union all select 实际数据
    union all select 实际数据
    这样的插入效率会高很多
      

  9.   


    试过了,包括有朋友说将N条SQL语句用“;”号串在一起去执行,
    没效果的。因为这些插入的语句,分布在各个不同的表里面去。之前说过了,每台GPS单独一个表。还有朋友说插入之前先开启事务,插个N条之后,再提交。同样起不了作用,因为这N条不是同一个表的。
    反而会丢失数据。只要这中间有一条SQL异常,就回滚了。(因为在接收SOcket数据的时候,还有别的程序因响应客户操作,不断在数据库添减GPS,有一些表刚好还没建好,这时候插入就抛错了。)我也尝试过在内存维护一个GPS操作列表,把解析到的SQL,添加到相应的GPS对象下,攒个十条八条后,再串成一串拿去SQL执行,感觉理论上同一串语句插同一个表,应该速度会有所提高。但是实际情况却是性能下降得十分历害。可能原因在于我每处理好一条SQL,放到相应GPS对象下的操作,都得去循环遍历(1次要3重for)所有GPS对象,遍历过程还要加锁,所以速度下降了。
    sqlbulkcopy的话,不太现实。因为它是表对表的操作,而目前我遇到的情况是一个GPS一个表,而默认一个GPS的数据回传时间是30秒,如果攒个一千几百条才刷一次到数据库,那时间滞后太多,而且一条记录也不是10个字节,光300个字符长度的字段就有几个。
    现在服务器还在死撑,估计跑个一两天这个程序就得重起一次。。
    据很多人口头传说甲骨文的数据库比微软的数据库速度快N倍,不知道是不是真有这么神。我下了一个11g的装在win2003上。装完就傻眼,不知道要怎么操作。
    头大
      

  10.   

    N条SQL语句用“;”号串在一起去执行
    与我给的还是不一样的多个insert是可以一起执行,不用";"也一样的
    但是这样还是多次插入,只是提交次数减少了而已
    我的写法,是一次插入
      

  11.   

    每台GPS单独一个表?
    那的确比较难使用我的写法,因为一台gps要隔30秒才有下一个数据
    建议改为所有gps占一个表,但是按日期分区,或按日期分表(7000*24*60*2=20160000条/表)
    当然,是验证我的写法的确高效的前提下
      

  12.   


    理论应该可能提高吧?但是这样做一张表的数据量会太大,查询会非常慢。
    查询慢又会反过影响插入。之所以要设计成每天一个数据库,每个库里每个GPS一张表,就是为了减少表的数据量,提高查询速度。
      

  13.   


    1天增加4G的数据,的确也挺麻烦。1天1个库倒是也省事
    或者历史数据做成1天1个库,当天库还是 所有gps1天 1个表等待楼主测试 1千个insert 和 1个insert插入1千行 的效率差别比较结果。。
      

  14.   

    这个需求看业务逻辑如何强制定义 
    有的需求是必须每条立即写入DB并返回结果给前端。。若批次1千条,若因为什么原因失败将如何??看来楼主不是玩DB的,对这个系统的瓶颈磁盘写入都没交待
    若是业务强制要求每次一条的话,可分多个DB,将6000台GPS的数据分组写入对应的DB
    即一个组对应一个DB,就没必要再分什么DB<库>了,但分区需要
    按照每秒200条记录<不考虑未来规模持续扩大>,其实配置好磁盘与数据文件设计就足够了提供有偿支持
      

  15.   

    这个不用测了。前几年这个库设计就是所有的历史数据都在同一个库,按日期分表存一天一个表。在1000台以下的时候,速度还可以忍受。3000台以上,连我们程序员自己拿查询分析器查数据,都自己受不了,别说客户。昨晚7点程序编译好后,放到营运中的服务器上跑。
    并且我改了一些操作系统和SQLSERVER的设置。1。在“设备管理器”的硬盘属性上的“策略”勾上那两个关于硬盘缓冲的开关。
    2。在“我的电脑”属性里,将处理器计划和内存使用,都设置为优先后台服务和系统缓存。
    3。在SQL的“企业管理器”勾上“提升SQL线程优先级”和使用“WINNT 纤程”设置后重启服务器,程序从晚上7点多一直跑到晚上12点,平均插入插入速度达到130多,感觉比调整系统设置之前,性能上升明显,每秒高出20条左右。如果能再高出20条,应该可以应付目前的问题。还有微量的数据入不过来我可以丢掉一些,一天里面一台GPS三两小时丢一秒的数据,感觉不出来^_^!另外,通过这次大数据量处理,我发现连接数据库的串,如果是连本机,用“.”号代替换"127.0.0.1”速度有明显变化,在SQL管理看到连接方式为LPC,用IP连接的话,是TCP/IP。
    每秒130条的插入速度一直维持到昨晚12之前。过了12点后,速度直线下降到40-60条每秒了。不知道是什么原因!于是昨晚12点多的时候,我远程上服务器关了启起这个程序,还是没有什么改变,一直在40-60左右。我以为是可以别的程序在访问数据库造成的干扰,就没有理会。等到差不多两点的时候,我又上去看了一次,还是这个速度。我又重开了一次这个程序。至今天早上8点的时候,估计也是这个速度,因为它崩了。看打印日志是由于内存错误,应该是入不过来,数据大量堆积造成的。
    我的程序除了解析数据入库外,还有一个优先级较低的后台线程,它负责每天晚上过了23点后,为明天建立按日期命名的数据库,及在这个库里为每一个GPS建立一个表。这两个操作过程是写成存储过程的,调用的时候只需要传个日期参数入去就可以的了。库需要包含的表结构都在存储过程里定义。昨晚时间跨天了之后,性能急剧下降的原因,我挠破脑袋也想不出原因来。有一点点怀疑是由于存储过程建的新库可能不对劲,但是,这几个月来的建库建库操作都是由这两个存储过程去做的,包括昨天晚上插入速度非常快的那个库和表。
      

  16.   

    使用“WINNT 纤程”
    这个是有很多限制的,建议不要使用所有的历史数据都在同一个库?
    那那肯定不行啊,每天加4G,备份都恐怖啊
    历史数据 应该 1天1个库,省事!——备份简单,丢弃过期的数据也简单
    但是,当天库,为了插入效率高,应该 所有gps记录都存1个表
      

  17.   

    【连本机,用“.”号代替换"127.0.0.1”速度有明显变化,在SQL管理看到连接方式为LPC,用IP连接的话,是TCP/IP】
    没有管道方式,快是不是因为使用了内部共享内存的方式?【硬盘属性上的“策略”勾上那两个关于硬盘缓冲的开关】
    如果掉电或突然死机,会比较危险
    如果性能只够应付,是比较危险的,至少要宽裕1倍才能放心吧
    是出租车管理公司?在什么地方?
    半夜还要监控,的确比较辛苦了
      

  18.   

    using (SqlConnection cn = new SqlConnection(strDBName))
                                {
                                    cn.Open();
                                    if (cn.State == ConnectionState.Open)
                                    {
                                        //SqlCommand cmd = m_SqlCommandPool.Pop();
                                        try
                                        {
                                            objSQLCommand.Connection = cn;
                                            objSQLCommand.CommandText = strSQL;
                                            objSQLCommand.CommandType = CommandType.Text;
                                            objSQLCommand.ExecuteNonQuery();
                                        }
                                        catch (Exception ex)
                                        {
                                            CDebug.Print(ex.ToString() + "\r\n" + sqlTask.Command);
                                        }
                                        finally
                                        {
                                            //释放对连接的引用
                                            objSQLCommand.Connection = null;
                                           // m_SqlCommandPool.Push(cmd);
                                        }
                                    }
                                }
    你这样性能肯定会下降,using 每次执行完都会关闭连接.对于你的这种需求应将SqlConnection 对象改成单实例,因为差不多实时都在写入,因此不需要每次都Close.
    我也有一个和你类似的需求(与移动收发短信),虽然数据没有你的大,但改成单实例性差不多提高了10倍.当然也带一个问题就是只有程序退出才会释放这个连接.也就是这个连接是一直被占用.
      

  19.   

    LZ,你服务器上面配置?
    是RAID10还是RAID5,RAID10的插入性能是RAID5的4倍左右
    还有你是SQL2008还是SQL2005还是SQL2000?
    内存配置多少?
    SQL2008可以启用页压缩选项,然后再来测试。
    当然磁盘阵列柜性能更佳。
      

  20.   


    服务器4G内存
    至强处理器
    2个SATA 2的硬盘,没有组阵列。其中这些实时数据的库单独放在一个硬盘上。
    win2003标准版
    SQL2000企业版
    现在老板己经下单,再买一台DELL的服务器。8000块左右的。
    内存8G
    双SATA2的硬盘,准备组个RAID 0的阵列。系统还是用回Win2003,准备上64位。SQL准备用2005 64位。
    服务器上我们的程序,准备全部编译为64位的。
    目前服务器上的程序.net写的,有vs03,vs05,vs08
    不知道高版本的.net会不会性能也高一些?到时候服务器回来再试试。
      

  21.   

    每行一个insert,效率估计也改善不了多少
    每500-1000行一个insert,才能本质的区别
      

  22.   

    目前测试环境如下:CPU 赛扬双核 1.8G
    内存 2G
    系统 Windows server 2003
    SQL SQL Server2005这样的硬件环境,还一天4个G的流量,乖乖,你怎么办到的啊
      

  23.   

    你那CPU,你那内存,他们表示鸭梨很大呀