Spark2.2.0
Spark SQLは、JDBCベースのMysqlへの書き込みが非常に遅く、Mysqlの負荷は比較的高いです。
SparkSQL JDBCのいくつかのパラメーターは次のとおりです
url | 接続するJDBC URL。リスト:jdbc:mysql:// ip:3306 |
dbtable | 読み取る必要があるJDBCテーブルは、括弧内のサブクエリで置き換えることができます。たとえば、(select * from table_name)as t1として、クエリ結果にエイリアスを追加する必要があります) |
運転者 | このURLへの接続に使用されるJDBCドライバーのクラス名。com.mysql.jdbc.Driverとしてリストされます。 |
|
3つのフィールドすべてを指定する必要があります。データベースの読み取り、複数のワーカーからのデータの並列読み取り、テーブルの分割方法。
|
numPartitions |
データベースと並行して読み書きされるパーティションの数も、同時JDBC接続の数を決定します。RDDパーティションの数がこの値を超えると、Sparkはデータベースに書き込む前に合体(numPartitions)を呼び出して、パーティションの数をこの値に減らします。 |
フェッチサイズ |
データの読み取りにのみ適用されます。JDBCフェッチサイズは、毎回フェッチされる行数を決定するために使用されます。これは、JDBCドライバーがパフォーマンスを調整するのに役立ちます。これらのドライバーは、デフォルトでフェッチサイズが小さくなっています(たとえば、Oracleは一度に10行をフェッチします)。 |
バッチサイズ | データの書き込みにのみ適用されます。挿入ごとの行数を決定するために使用されるJDBCバッチサイズ。これは、JDBCドライバーがパフォーマンスを調整するのに役立ちます。デフォルトは1000です。 |
isolationLevel | データの書き込みにのみ適用されます。トランザクション分離レベルは現在の接続に適用されます。これは、JDBCによる接続オブジェクト定義に対応するNONE、READ_COMMITTED、READ_UNCOMMITTED、REPEATABLE_READ、またはSERIALIZABLEであり、デフォルト値は標準のトランザクション分離レベルREAD_UNCOMMITTEDです。 |
切り捨てる | データの書き込みにのみ適用されます。SaveMode.Overwriteが有効になっている場合、このオプションはMySQLでテーブルを切り捨てます(既存のテーブルを削除して再構築するのではなく)。これはより効果的で、テーブルのメタデータ(インデックスなど)が削除されないようにすることができます。ただし、新しいデータのモードが異なる場合など、場合によっては機能しません。デフォルトはfalseです。 |
createTableOptions | データの書き込みにのみ適用されます。このオプションを使用すると、テーブルの作成時に特定のデータベーステーブルとパーティションオプションを設定できます(CREATE TABLE t(名前文字列)ENGINE = InnoDBなど)。 |
createTableColumnTypes |
データの書き込みにのみ適用されます。テーブルを作成するときに、テーブルフィールドのデータ型を指定します。指定されたデータ型は、sparkSQLの型と一致している必要があります |
url:urlの後に、パラメーターrewriteBatchedStatements = trueを追加して、MySQLサービスがバッチ書き込みを開始することを示します。このパラメーターは、バッチ書き込みの重要なパラメーターであり、パフォーマンス
バッチサイズを大幅に改善できます:MySQLに書き込まれるDataFrameライターバッチの数。また、パフォーマンスパラメータ
isolationLevel を改善するために:トランザクション分離レベル、DataFrameは書き込みのためにトランザクションを開く必要はありません、
切り捨てなし:上書きモードで使用可能、テーブルが上書きされたときに元のデータが上書きされてもテーブル構造は削除されません
SparkSQLはJDBCに基づいてMysqlデータベースに書き込みます。
結論:dfがMysqlに書き込むときは、パーティションメソッドに従って書き込み、JDBC、PrepareStatementを呼び出してMysql SQLをアセンブルします。
https://blog.csdn.net/IT_xhf/article/details/85336074
https://www.jianshu.com/p/429e64663b0e
override def createRelation(
sqlContext: SQLContext,
mode: SaveMode,
parameters: Map[String, String],
df: DataFrame): BaseRelation = {
val jdbcOptions = new JDBCOptions(parameters)
val url = jdbcOptions.url
val table = jdbcOptions.table
val createTableOptions = jdbcOptions.createTableOptions
val isTruncate = jdbcOptions.isTruncate
val conn = JdbcUtils.createConnectionFactory(jdbcOptions)()
try {
val tableExists = JdbcUtils.tableExists(conn, url, table)
if (tableExists) {
mode match {
case SaveMode.Overwrite =>
if (isTruncate && isCascadingTruncateTable(url) == Some(false)) {
// In this case, we should truncate table and then load.
truncateTable(conn, table)
saveTable(df, url, table, jdbcOptions)
} else {
// Otherwise, do not truncate the table, instead drop and recreate it
dropTable(conn, table)
createTable(df.schema, url, table, createTableOptions, conn)
saveTable(df, url, table, jdbcOptions)
}
case SaveMode.Append =>
saveTable(df, url, table, jdbcOptions)
case SaveMode.ErrorIfExists =>
throw new AnalysisException(
s"Table or view '$table' already exists. SaveMode: ErrorIfExists.")
case SaveMode.Ignore =>
// With `SaveMode.Ignore` mode, if table already exists, the save operation is expected
// to not save the contents of the DataFrame and to not change the existing data.
// Therefore, it is okay to do nothing here and then just return the relation below.
}
} else {
createTable(df.schema, url, table, createTableOptions, conn)
saveTable(df, url, table, jdbcOptions)
}
} finally {
conn.close()
}
createRelation(sqlContext, parameters)
}
最后通过org.apache.spark.sql.execution.datasources.jdbc.JdbcUtils#saveTable函数完成数据的插入
/**
* Saves the RDD to the database in a single transaction.
*/
def saveTable(
df: DataFrame,
tableSchema: Option[StructType],
isCaseSensitive: Boolean,
options: JDBCOptions): Unit = {
val url = options.url
val table = options.table
val dialect = JdbcDialects.get(url)
val rddSchema = df.schema
val getConnection: () => Connection = createConnectionFactory(options)
val batchSize = options.batchSize
val isolationLevel = options.isolationLevel
val insertStmt = getInsertStatement(table, rddSchema, tableSchema, isCaseSensitive, dialect)
val repartitionedDF = options.numPartitions match {
case Some(n) if n <= 0 => throw new IllegalArgumentException(
s"Invalid value `$n` for parameter `${JDBCOptions.JDBC_NUM_PARTITIONS}` in table writing " +
"via JDBC. The minimum value is 1.")
case Some(n) if n < df.rdd.getNumPartitions => df.coalesce(n)
case _ => df
}
repartitionedDF.foreachPartition(iterator => savePartition(
getConnection, table, iterator, rddSchema, insertStmt, batchSize, dialect, isolationLevel)
)
}
/**
* Returns an Insert SQL statement for inserting a row into the target table via JDBC conn.
*/
def getInsertStatement(
table: String,
rddSchema: StructType,
tableSchema: Option[StructType],
isCaseSensitive: Boolean,
dialect: JdbcDialect): String = {
val columns = if (tableSchema.isEmpty) {
rddSchema.fields.map(x => dialect.quoteIdentifier(x.name)).mkString(",")
} else {
val columnNameEquality = if (isCaseSensitive) {
org.apache.spark.sql.catalyst.analysis.caseSensitiveResolution
} else {
org.apache.spark.sql.catalyst.analysis.caseInsensitiveResolution
}
// The generated insert statement needs to follow rddSchema's column sequence and
// tableSchema's column names. When appending data into some case-sensitive DBMSs like
// PostgreSQL/Oracle, we need to respect the existing case-sensitive column names instead of
// RDD column names for user convenience.
val tableColumnNames = tableSchema.get.fieldNames
rddSchema.fields.map { col =>
val normalizedName = tableColumnNames.find(f => columnNameEquality(f, col.name)).getOrElse {
throw new AnalysisException(s"""Column "${col.name}" not found in schema $tableSchema""")
}
dialect.quoteIdentifier(normalizedName)
}.mkString(",")
}
val placeholders = rddSchema.fields.map(_ => "?").mkString(",")
s"INSERT INTO $table ($columns) VALUES ($placeholders)"
}
/**
* Saves a partition of a DataFrame to the JDBC database. This is done in
* a single database transaction (unless isolation level is "NONE")
* in order to avoid repeatedly inserting data as much as possible.
*
* It is still theoretically possible for rows in a DataFrame to be
* inserted into the database more than once if a stage somehow fails after
* the commit occurs but before the stage can return successfully.
*
* This is not a closure inside saveTable() because apparently cosmetic
* implementation changes elsewhere might easily render such a closure
* non-Serializable. Instead, we explicitly close over all variables that
* are used.
*/
def savePartition(
getConnection: () => Connection,
table: String,
iterator: Iterator[Row],
rddSchema: StructType,
insertStmt: String,
batchSize: Int,
dialect: JdbcDialect,
isolationLevel: Int): Iterator[Byte] = {
val conn = getConnection()
var committed = false
var finalIsolationLevel = Connection.TRANSACTION_NONE
if (isolationLevel != Connection.TRANSACTION_NONE) {
try {
val metadata = conn.getMetaData
if (metadata.supportsTransactions()) {
// Update to at least use the default isolation, if any transaction level
// has been chosen and transactions are supported
val defaultIsolation = metadata.getDefaultTransactionIsolation
finalIsolationLevel = defaultIsolation
if (metadata.supportsTransactionIsolationLevel(isolationLevel)) {
// Finally update to actually requested level if possible
finalIsolationLevel = isolationLevel
} else {
logWarning(s"Requested isolation level $isolationLevel is not supported; " +
s"falling back to default isolation level $defaultIsolation")
}
} else {
logWarning(s"Requested isolation level $isolationLevel, but transactions are unsupported")
}
} catch {
case NonFatal(e) => logWarning("Exception while detecting transaction support", e)
}
}
val supportsTransactions = finalIsolationLevel != Connection.TRANSACTION_NONE
try {
if (supportsTransactions) {
conn.setAutoCommit(false) // Everything in the same db transaction.
conn.setTransactionIsolation(finalIsolationLevel)
}
val stmt = conn.prepareStatement(insertStmt)
val setters = rddSchema.fields.map(f => makeSetter(conn, dialect, f.dataType))
val nullTypes = rddSchema.fields.map(f => getJdbcType(f.dataType, dialect).jdbcNullType)
val numFields = rddSchema.fields.length
try {
var rowCount = 0
while (iterator.hasNext) {
val row = iterator.next()
var i = 0
while (i < numFields) {
if (row.isNullAt(i)) {
stmt.setNull(i + 1, nullTypes(i))
} else {
setters(i).apply(stmt, row, i)
}
i = i + 1
}
stmt.addBatch()
rowCount += 1
if (rowCount % batchSize == 0) {
stmt.executeBatch()
rowCount = 0
}
}
if (rowCount > 0) {
stmt.executeBatch()
}
} finally {
stmt.close()
}
if (supportsTransactions) {
conn.commit()
}
committed = true
Iterator.empty
} catch {
case e: SQLException =>
val cause = e.getNextException
if (cause != null && e.getCause != cause) {
// If there is no cause already, set 'next exception' as cause. If cause is null,
// it *may* be because no cause was set yet
if (e.getCause == null) {
try {
e.initCause(cause)
} catch {
// Or it may be null because the cause *was* explicitly initialized, to *null*,
// in which case this fails. There is no other way to detect it.
// addSuppressed in this case as well.
case _: IllegalStateException => e.addSuppressed(cause)
}
} else {
e.addSuppressed(cause)
}
}
throw e
} finally {
if (!committed) {
// The stage must fail. We got here through an exception path, so
// let the exception through unless rollback() or close() want to
// tell the user about another problem.
if (supportsTransactions) {
conn.rollback()
}
conn.close()
} else {
// The stage must succeed. We cannot propagate any exception close() might throw.
try {
conn.close()
} catch {
case e: Exception => logWarning("Transaction succeeded, but closing failed", e)
}
}
}
}
SparkSQL読み取りデータベースパラメーターケース
https://www.cnblogs.com/wwxbi/p/6978774.html
パラメータケース
partitionColumn, lowerBound,upperBound使用案例
https://blog.csdn.net/wiborgite/article/details/84944596