并行特性对分区表执行计划的影响

        前一段时间在大分区表数据切换方案上线的时候遇到一个问题:在新方案应用之后,新方案的执行计划与旧方案执行计划相差甚远,导致业务应用延迟。同样的SQL语句,同样的表结构,为什么方案应用前后的执行计划会相差那么多呢,难道仅仅是因为新方案使用了视图来屏蔽底层表的变化吗?知识储备告诉我不是这样的:Oracle在执行SQL语句的时候会将视图名称转化为视图定义,因此执行计划是不会受到影响的。肯定是两张表在某些细节属性上存在不同。

        通过查询DBA_TABLES数据字典表来比较两张表的属性:

 
   
>  owner, table_name, degree, cache, last_analyzed, compression
  2     dba_tables
  3   where table_name  'MV_AUC%';

OWNER      TABLE_NAME                     DEGREE               CACHE

LAST_ANALYZED  

ODS        MV_AUC                                   16              N
2009-6-20 12:42:42

ODS        MV_AUCTION20090804                        1             N

        从上面的查询结果可以看出,以前的表并行度为16,而现在的表并行度只有1,因此可以推出以前的查询会通过16个进程并行查询表的16个分区,充分利用了分区的并行特性,查询效率很高,而新表不具备并行特性,查询时间是之前的16倍以上。定位到了问题的根源,解决起来也就容易多了。将新表的并行度改为16。 Alter table MV_AUCTION20090804 parallel 16; 至此,执行计划与之前一模一样,问题迎刃而解。

        大家可能注意到了,两张表还存在一些区别,即以前的表曾经被分析过。通过实验验证,对于这种大型分区表(使用了并行的)而言,分析与否对执行计划并没太大影响,况且分析已经相当陈旧,因此可以忽略。

         说来说去都是并行特性,那么它到底对执行计划有多大的影响呢?我们可以通过实验来体会一下它的神奇之处:

        这里我们使用MV_AUC的表结构进行测试:首先创建表MV_AUCTION,缺省情况下表不具备并行特性,即该表的并行度为1

 
   
>   MV_AUCTION
  2  (
  3    .......
       .......
       .......
 23  )
 24    hash (ID)
 25  (
 26     HASH_P1,
 27     HASH_P2,
 28     HASH_P3,
 29     HASH_P4,
 30     HASH_P5,
 31     HASH_P6,
 32     HASH_P7,
 33     HASH_P8,
 34     HASH_P9,
 35     HASH_P10,
 36     HASH_P11,
 37     HASH_P12,
 38     HASH_P13,
 39     HASH_P14,
 40     HASH_P15,
 41     HASH_P16
 42  );

表已创建。

>   IDX_AUCTIONS_ID  MV_AUCTION (AUCTION_ID);

索引已创建。

>   IDX_CATID  MV_AUCTION (CATEGORY);

索引已创建。

>   IDX_SPUID  MV_AUCTION (SPU_ID);

索引已创建。

>  autotrace 
>  *  MV_AUCTION;

执行计划

Plan hash value: 2641310786





| Id  | Operation          | Name         |   | Bytes | Cost (%CPU)| Time
  | Pstart| Pstop |





|   0 |     |              |     1 |   742 |     2   (0)| 00:00:0

1 |       |       |

|   1 |   HASH |              |     1 |   742 |     2   (0)| 00:00:0

1 |     1 |    16 |

|   2 |     FULL| MV_AUCTION  |     1 |   742 |     2   (0)| 00:00:0

1 |     1 |    16 |

上面的执行计划显示的操作为全表扫描(TABLE ACCESS FULL),并且哈希分区的16个分区进行了单独扫描,并没有用到分区的并行特性。

接下来创建与表MV_AUCTION同样结构的表MV_AUCTION_P,这里我们设置它的并行度为8:

 
   
>   MV_AUCTION_P
  2  (
  3    .......
       .......
       .......
 23  )
 24    hash (ID)
 25  (
 26     HASH_P1,
 27     HASH_P2,
 28     HASH_P3,
 29     HASH_P4,
 30     HASH_P5,
 31     HASH_P6,
 32     HASH_P7,
 33     HASH_P8,
 34     HASH_P9,
 35     HASH_P10,
 36     HASH_P11,
 37     HASH_P12,
 38     HASH_P13,
 39     HASH_P14,
 40     HASH_P15,
 41     HASH_P16
 42  );


表已创建。

>   IDX_AUCTIONS_ID20090819  MV_AUCTION_P (AUCTION_ID);

索引已创建。

>   IDX_CATID20090819  MV_AUCTION_P (CATEGORY);

索引已创建。

>   IDX_SPUID20090819  MV_AUCTION_P (SPU_ID);

索引已创建。

>   MV_AUCTION_P ;

表已更改。

>  *  MV_AUCTION_P;

执行计划

Plan hash value: 1383468301





| Id  | Operation            | Name         |   | Bytes | Cost (%CPU)| Time
    | Pstart| Pstop |    TQ  |-| PQ Distrib |





|   0 |       |              |     1 |   742 |     2   (0)| 00:00

:01 |       |       |        |      |            |

|   1 |  PX COORDINATOR      |              |       |       |            |
    |       |       |        |      |            |

|   2 |   PX SEND QC (RANDOM)| :TQ10000     |     1 |   742 |     2   (0)| 00:00

:01 |       |       |  Q1,00 | P->S | QC (RAND)  |

|   3 |    PX BLOCK ITERATOR |              |     1 |   742 |     2   (0)| 00:00

:01 |     1 |    16 |  Q1,00 | PCWC |            |

|   4 |       FULL| MV_AUCTION_P |     1 |   742 |     2   (0)| 00:00

:01 |     1 |    16 |  Q1,00 | PCWP |            |






Note

   - dynamic sampling used for this 

可以看出,上面的执行计划跟先前的大不相同,同样为全表扫描,分区并行使得执行效率大大提高(PX COORDINATOR;PX SEND QC (RANDOM);PX BLOCK ITERATOR)。

当然,我们可以通过使用HINT来达到同样的效果:

 
   
>   *  MV_AUCTION;

执行计划

Plan hash value: 56807953





| Id  | Operation            | Name       |   | Bytes | Cost (%CPU)| Time
  | Pstart| Pstop |    TQ  |-| PQ Distrib |





|   0 |       |            |     1 |   742 |     2   (0)| 00:00:0

1 |       |       |        |      |            |

|   1 |  PX COORDINATOR      |            |       |       |            |
  |       |       |        |      |            |

|   2 |   PX SEND QC (RANDOM)| :TQ10000   |     1 |   742 |     2   (0)| 00:00:0

1 |       |       |  Q1,00 | P->S | QC (RAND)  |

|   3 |    PX BLOCK ITERATOR |            |     1 |   742 |     2   (0)| 00:00:0

1 |     1 |    16 |  Q1,00 | PCWC |            |

|   4 |       FULL| MV_AUCTION |     1 |   742 |     2   (0)| 00:00:0

1 |     1 |    16 |  Q1,00 | PCWP |            |



Note

   - dynamic sampling used for this 

不过,在应用并行特性的时候有一点需要特别注意:给表加并行度,会使全表扫描成本降低,有可能cbo会选择全表来代替索引扫描。最主要的是并行意味着需要更多的资源,除非你资源充足,通过启用并行来提高效率才是明智之选。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/15203236/viewspace-613641/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/15203236/viewspace-613641/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 以下是实现该需求的 Scala 代码示例: ```scala import java.sql.{Connection, DriverManager, ResultSet} import org.apache.hadoop.conf.Configuration import org.apache.hadoop.fs.{FileSystem, Path} import org.apache.hadoop.hive.conf.HiveConf import org.apache.hadoop.hive.metastore.api.Table import org.apache.hadoop.hive.ql.exec.{DDLTask, Utilities} import org.apache.hadoop.hive.ql.metadata.{Hive, HiveException, Partition} import org.apache.hadoop.hive.ql.parse.{BaseSemanticAnalyzer, ParseUtils} import org.apache.hadoop.hive.ql.plan.{DDLWork, LoadTableDesc} import org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory import org.apache.hadoop.hive.serde2.typeinfo.TypeInfoUtils import org.apache.hadoop.hive.serde2.typeinfo.TypeInfoUtils.TypeSearchParameters import org.apache.hadoop.io.Text import org.apache.hadoop.mapred.TextInputFormat import org.apache.spark.sql.{DataFrame, SaveMode, SparkSession} import org.apache.spark.sql.types.{StructField, StructType} import scala.collection.JavaConversions._ object MySQLToHive { def main(args: Array[String]): Unit = { // 初始化 SparkSession val spark = SparkSession.builder() .appName("MySQLToHive") .enableHiveSupport() .getOrCreate() // 设置 MySQL 连接信息 val mysqlUrl = "jdbc:mysql://mysql_host:3306/mysql_database" val mysqlUser = "mysql_username" val mysqlPassword = "mysql_password" // 设置 Hive 目标表信息 val hiveDatabase = "hive_database" val hiveTable = "table1" // 获取 MySQL 最新数据 val mysqlDF = spark.read.format("jdbc") .option("url", mysqlUrl) .option("dbtable", s"(SELECT * FROM $hiveDatabase.$hiveTable WHERE update_time > (SELECT MAX(update_time) FROM $hiveDatabase.$hiveTable)) AS tmp") .option("user", mysqlUser) .option("password", mysqlPassword) .load() // 获取 Hive 表结构信息 val hiveTableSchema = spark.sql(s"DESCRIBE $hiveDatabase.$hiveTable") .select("col_name", "data_type") .collect() .map(row => StructField(row.getString(0), TypeInfoUtils.getTypeInfoFromTypeString(row.getString(1)).getTypeName)) val hiveTableStructType = StructType(hiveTableSchema) // 将 MySQL 数据写入临时目录 mysqlDF.write .format("csv") .option("header", "false") .mode(SaveMode.Overwrite) .save(s"/tmp/$hiveDatabase/$hiveTable") // 获取 Hive 数据库和表的元数据 val hiveConf = new HiveConf() val hive = Hive.get(hiveConf) val db = hive.getDatabase(hiveDatabase) val table = db.getTable(hiveTable) // 创建 Hive 表对应的临时表 val tempTableName = s"${hiveTable}_temp" val tempTablePath = new Path(s"/tmp/$hiveDatabase/$tempTableName") val tempTable = new Table(table) val tempTableDesc = new LoadTableDesc(tempTablePath, tempTableName, tempTable, null, true, null, null, false, false) Utilities.copyTableSchemaToTableDesc(table, tempTableDesc) val tempTableDDL = DDLTask.getCreateTableStatement(tempTableDesc) spark.sql(tempTableDDL) // 加载临时表数据到 Hive 表 val tempTablePartition = new Partition(db.getTable(hiveTable), null) val tempTableLoadDesc = new LoadTableDesc(tempTablePath.toString, table.getDbName, table.getTableName, Array(tempTablePartition), true) Utilities.addInputsForEmptyPartition(tempTableLoadDesc, List(new Path(s"/tmp/$hiveDatabase/$tempTableName/part-00000"))) val tempTableLoadWork = new DDLWork(new java.util.ArrayList[LoadTableDesc](), new java.util.ArrayList[DDLTask]()) tempTableLoadWork.add(tempTableLoadDesc, null) Utilities.loadPartition(hiveConf, tempTableLoadWork, true, null) // 删除临时目录 val fs = FileSystem.get(hiveConf) fs.delete(new Path(s"/tmp/$hiveDatabase"), true) // 更新 Hive 表的元数据 val newSchema = StructType(table.getAllCols.map(col => StructField(col.getName, TypeInfoFactory.stringTypeInfo.getTypeName))) val serdeProperties = Map("field.delim" -> ",", "serialization.format" -> ",", "line.delim" -> "\n") val tableProperties = table.getParameters val newTableSchema = ParseUtils.parseColumns(newSchema) val newTable = new Table(table.getDbName, hiveTable, table.getOwner, table.getInputFormatClass, table.getOutputFormatClass, newTableSchema, Nil, serdeProperties, table.isTemporary) newTable.setCreateTime(table.getCreateTime) newTable.setLastAccessTime(table.getLastAccessTime) newTable.setRetention(table.getRetention) newTable.setPartitionKeys(table.getPartitionKeys) newTable.setTableType(table.getTableType) newTable.setParameters(tableProperties) try { db.alterTable(hiveTable, newTable) } catch { case e: HiveException => e.printStackTrace() } // 关闭 SparkSession spark.stop() } } ``` 该代码实现了以下功能: 1. 从 MySQL 数据库中获取最新的数据。 2. 将最新数据写入到 Hive 表对应的临时目录。 3. 创建 Hive 表对应的临时表,并将临时目录中的数据加载到临时表。 4. 根据临时表的元数据更新 Hive 表的元数据。 5. 删除临时目录。 需要注意的是,该代码实现了增量抽取的逻辑,只会抽取 MySQL 中更新时间大于 Hive 中最近更新时间的数据。另外,该代码仅适用于非分区表,如果需要抽取分区表的数据,需要稍作修改。 ### 回答2: 编写 Scala 工程代码将 MySQL 库中表 table1 的数据增量抽取到 Hive 的 ods 库中对应表 table1,可以按照以下步骤进行: 1. 首先,需要确保已经在 Scala 工程中引入所需的依赖库,如 MySQL 驱动和 Hive 驱动。 2. 在 Scala 工程中建立 MySQL 和 Hive 的连接,分别创建对应的连接对象。 3. 通过 MySQL 连接对象,执行增量查询语句,查询 MySQL 中 table1 表中的新增或更新数据。可以使用某个字段(如时间戳或增量ID)进行增量查询,只获取最新的数据。 4. 将查询结果存储在 Scala 的数据结构中,如 List 或 DataFrame。 5. 通过 Hive 连接对象,将 Scala 中的数据结构写入到 ods 库中的 table1 表中。可以使用 Hive 的写入 API 进行数据写入操作。 6. 在写入数据之前,可以先检查 ods 库中的 table1 表是否存在,如果不存在则可以先创建该表。 7. 若表已存在,可以根据需求选择是否先清空表中的数据,再进行插入操作。可以使用 Hive 的 TRUNCATE TABLE 或 DELETE 语句进行数据清除。 8. 最后,关闭 MySQL 和 Hive 的连接。 通过以上步骤,即可在 Scala 工程中编写代码将 MySQL 库中 table1 表的数据增量抽取到 Hive 的 ods 库中对应的 table1 表中。 ### 回答3: 要编写Scala工程代码将MySQL库中表table1的数据增量抽取到Hive的ods库中对应表table1中,可以按照以下步骤进行: 1. 首先,通过Scala编写一个MySQL的数据源连接器,用于连接MySQL数据库,设置数据库连接参数,包括数据库URL、用户名、密码等。 2. 创建一个Hive数据源连接器,用于连接Hive数据库,同样设置连接参数。 3. 使用Scala编写一个增量抽取函数,用于查询MySQL表table1中的最新数据。 4. 编写一个定时任务,用于定期执行增量抽取函数。可以使用定时调度框架如Quartz或者Akka Scheduler进行任务调度。 5. 在增量抽取函数中,可以使用MySQL的时间戳字段或者自增ID字段来判断数据的增量。首次运行时,可以抽取全部数据,并将抽取的数据存储到Hive的ods库的table1中。 6. 之后的增量抽取过程中,根据上一次抽取的最新记录的时间戳或者ID,查询MySQL表table1中大于该时间戳或者ID的数据,并将新增的数据插入到Hive的ods库的table1中。 7. 更新最新记录的时间戳或者ID,用于下次增量抽取。 8. 编写日志记录函数,用于记录增量抽取的过程中的日志信息,方便跟踪和排查问题。 9. 编写异常处理代码,处理异常情况,如数据库连接失败、数据抽取失败等情况。 10. 对于大量数据的增量抽取,可以考虑并行处理,使用Scala的并发特性进行优化,提高抽取效率。 通过以上步骤,编写的Scala工程代码可以实现MySQL表table1数据的增量抽取,并将抽取的数据存储到Hive的ods库的table1中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值