更快或更高效地进行大量单个行更新

时间:2014-11-06 15:33:45

标签: java sql-server multithreading spring transactions

我正在编写一个java应用程序,它将一个数据库的信息(db2)复制到另一个数据库(sql server)。操作顺序非常简单:

  1. 检查某个时间段内是否有任何更新
  2. 从指定时间范围内的第一个数据库中获取所有内容
  3. 将数据库信息映射到POJO
  4. 将POJO的子集划分为线程(在属性文件中预先定义的#)
  5. 线程循环遍历每个POJO
  6. 更新第二个数据库
  7. 我的一切工作都很顺利,但在一天的某些时候,需要进行的更新量会大幅增加(可以达到数十万)。

    下面你可以看到我的代码的通用版本。它遵循应用程序的基本算法。对象是通用的,实际应用程序有5种不同类型的指定对象,每种对象都有自己的更新程序线程类。但是下面的通用函数正是它们的样子。在updateDatabase()方法中,它们都会添加到threads并且所有内容都会同时运行。

    private void updateDatabase()
    {
        List<Thread> threads = new ArrayList<>();
        addObjectThreads( threads );     
        startThreads( threads );
        joinAllThreads( threads );
    }
    
    private void addObjectThreads( List<Thread> threads )
    {
        List<Object> objects = getTransformService().getObjects();
        logger.info( "Found " + objects.size() + " Objects" );
        createThreads( threads, objects, ObjectUpdaterThread.class );
    }
    
    private void createThreads( List<Thread> threads, List<?> objects, Class threadClass )
    {
        final int BASE_OBJECT_LOAD = 1;
        int objectLoad = objects.size() / Database.getMaxThreads() > 0 ? objects.size() / Database.getMaxThreads() + BASE_OBJECT_LOAD : BASE_OBJECT_LOAD;
    
        for (int i = 0; i < (objects.size() / objectLoad); ++i)
        {
            int startIndex = i * objectLoad;
            int endIndex = (i + 1) * objectLoad;
            try
            {
                List<?> objectSubList = objects.subList( startIndex, endIndex > objects.size() ? objects.size() : endIndex );
                threads.add( new Thread( (Thread) threadClass.getConstructor( List.class ).newInstance( objectSubList ) ) );
            }
            catch (Exception exception)
            {
                logger.error( exception.getMessage() );
            }
        }
    }
    
    
    public class ObjectUpdaterThread extends BaseUpdaterThread
    {
        private List<Object> objects;
        final private Logger logger = Logger.getLogger( ObjectUpdaterThread.class );
    
        public ObjectUpdaterThread( List<Object> objects)
        {
            this.objects = objects;
        }
    
        public void run()
        {
            for (Object object : objects)
            {
                logger.info( "Now Updating Object: " + object.getId() );
                getTransformService().updateObject( object );
            }
        }
    }
    

    所有这些都转到了弹出服务,看起来像下面的代码。它的泛型,但每种类型的对象都具有完全相同的逻辑类型。上面代码中的getObjects()只是传递给DAO的一行,所以不需要真正发布它。

    @Service
    @Scope(value = "prototype")
    public class TransformServiceImpl implements TransformService
    {
        final private Logger logger = Logger.getLogger( TransformServiceImpl.class );
    
        @Autowired
        private TransformDao transformDao;
    
        @Override
        public void updateObject( Object object )
        {
            String sql;
            if ( object.exists() )
            {
                sql = Object.Mapper.UPDATE;
            }
            else
            {
                sql = Object.Mapper.INSERT;
            }
    
            boolean isCompleted = false;
            while ( !isCompleted )
            {
                try
                {
                    transformDao.updateObject( object, sql );
                    isCompleted = true;
                }
                catch (Exception exception)
                {
                    logger.error( exception.getMessage() );
                    threadSleep();
                    logger.info( "Now retrying update for Object: " + object.getId() );
                }
            }
            logger.info( "Updated Object: " + object.getId() );
        }
    }
    

    最后这些全部转到看起来像这样的DAO:

    @Repository
    @Scope(value = "prototype")
    public class TransformDaoImpl implements TransformDao
    {
        //@Resource is like @Autowired but with the added option of being able to specify the name
        //Good for autowiring two different instances of the same class [NamedParameterJdbcTemplate]
        //Another alternative = @Autowired @Qualifier(BEAN_NAME)
        @Resource(name = "db2")
        private NamedParameterJdbcTemplate db2;
    
        @Resource(name = "sqlServer")
        private NamedParameterJdbcTemplate sqlServer;
    
        final private Logger logger = Logger.getLogger( TransformerImpl.class );
    
        @Override
        public void updateObject( Objet object, String sql )
        {
            MapSqlParameterSource source = new MapSqlParameterSource();
            source.addValue( "column1_value", object.getColumn1Value() );
            //put all source values from the POJO in just like above
    
            sqlServer.update( sql, source );
        }
    }
    

    我的插入语句如下所示:

    "INSERT INTO dbo.OBJECT_TABLE " +
    "(COLUMN1, COLUMN2...) " +
    "VALUES(:column1_value, :column2_value... "
    

    我的更新语句如下所示:

    "UPDATE dbo.OBJECT_TABLE SET " +
    "COLUMN1 = :column1_value, COLUMN2 = :column2_value, " +
    "WHERE PRIMARY_KEY_COLUMN = :primary_key_value"
    

    我知道很多代码和东西,但我只想布置我所拥有的一切,希望我可以获得帮助,使其更快或更高效。更新如此多的行需要几个小时的时间,如果它只需要几个小时或几个小时而不是几个小时,那就太好了。谢谢你的帮助。我欢迎所有关于弹簧,线程和数据库的学习经历。

2 个答案:

答案 0 :(得分:0)

  
      
  1. 检查某个时间段内是否有任何更新
  2.   
  3. 从指定时间范围内的第一个数据库中获取所有内容
  4.   

源表中的LAST_UPDATED_DATE列(或者您正在使用的任何内容)上是否有索引?而不是将负担放在您的应用程序上,如果它在您的控制范围内,为什么不在源数据库中编写一些在“更新日志”表中创建条目的触发器?这样,您的应用程序需要做的就是使用并执行这些条目。

您如何管理交易?如果你为每个操作创建一个新的交易,它将会非常缓慢。

关于线程代码,您是否考虑过使用更标准的内容而不是编写自己的内容?你拥有的是一个非常典型的producer/consumer,Java对{(3}}和ThreadPoolExecutor这类事物有很好的支持,可以在执行不同任务的线程之间移动数据。

使用现成品的好处是:1)经过充分测试2)有许多调整选项和尺寸调整策略,您可以调整以提高性能。

此外,您是否考虑将每种类型的处理逻辑封装到单独的策略类中,而不是为每种需要处理的对象类型使用5种不同的线程类型?这样,您可以使用单个工作线程池(这将更容易调整大小和调整)。

答案 1 :(得分:0)

如果您要向服务器发送大量SQL,则应考虑使用Statement.addBatchStatement.executeBatch方法对其进行批处理。这些批次的大小是有限的(我总是限制为64K的SQL),但它们大大降低了到数据库的往返次数。

当我在迭代并创建SQL时,我会跟踪已经批量的数量,当SQL越过64K边界时,我会触发executeBatch并开始一个新的。

您可能想要尝试64K号码,这可能是我当时使用的Oracle限制。

我无法与Spring交谈,但批处理是JDBC Statement的一部分。我确信这很简单。