Spark系列 —— Spark整合讀寫MySQL及問題彙總

Spark主要是以JDBC驅動的方式讀寫MySQL的。在提交Spark作業時別忘記了打包驅動包mysql-connector-java-5.1.47.jar(用合適自己項目的版本)。
 

Spark讀取MySQL表可選配置參數

詳見: https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html

 

Spark讀取MySQL表

Spark提供了兩種(並行)讀MySQL的方式:基於整型列並行讀取和基於範圍並行讀取。
 

1. 基於整型列設置並行度

先上代碼,對着代碼再做詳細的解釋:

def readByIntegralColumn(spark: SparkSession): Unit = {
    val options = Map(
        "url" -> "jdbc:mysql://host:3306/dbName", // jdbc的url
        "dbtable" -> "tableName", //MySQL表名
        "user" -> "userName", //用戶名
        "password" -> "passwd", //密碼
        "driver" -> "com.mysql.jdbc.Driver", //驅動
        "partitionColumn" -> "id", //被用來分區的整型列
        "lowerBound" -> "1", //要讀取的整型列的下界(包含下界)
        "UpperBound" -> "400000", //要讀取的整型列的上界(不包含上界)
        "numPartitions" -> "10" //分區數
    )

    spark.read
        .format("jdbc")
        .options(options)
        .load()
        .write
        .saveAsTable("dbName.tableName")
}

上面代碼中,要讀取MySQL中id值從1到400000的行,並劃分10個分區,每個分區平均讀取40000條記錄。
 

2. 基於範圍設置並行度

同樣先上代碼,對着代碼再做詳細的解釋:

def readByRange(spark: SparkSession): Unit = {
	//用戶名和密碼
    val prop = new Properties()
    prop.put("user", "userName")
    prop.put("password", "passwd")

    val url = "jdbc:mysql://host:3306//dbName" //url
    val table = "tableName" //表名

    //predicates參數就相當於在where表達式中的範圍, 有幾個範圍就有幾個分區進行並行讀取
    val predicates = Array[String](
        "created_at < '2018-08-01 00:00:00'",
        "created_at >= '2018-08-01 00:00:00' && created_at < '2018-10-01 00:00:00'",
        "created_at >= '2018-10-01 00:00:00'")

    spark.read
        .jdbc(url, table, predicates, prop)
        .write.saveAsTable("dbName.tableName")
}

上面代碼中,基於MySQL中記錄的創建時間來劃分分區,predicates中設置的範圍區間數就是分區數。當然,也可以是使用其他任何可以進行區間查詢的列來設置分區數。

注意:不要在集羣上並行創建太多分區,否則可能會給MySQL產生很大的訪問壓力,甚至可能會導致數據庫系統崩潰
 

Spark寫入MySQL表

Spark寫MySQL比較簡單,直接看代碼:

def writeMySQL(spark: SparkSession): Unit = {
    val host = "hostname" //MySQL服務器地址
    val port = "3306" //端口號
    val userName = "userName" //用戶名
    val password = "password" //訪問密碼
    val dbName = "dbName" //庫名
    val jdbcUrl = s"jdbc:mysql://${host}:${port}/${dbName }" //jdbc url

    import java.util.Properties
    val prop = new Properties()
    prop.put("user", userName)
    prop.put("password", password)

    spark.read.table("db.test")
            .coalesce(10) //調節寫入並行度(增加並行度要用repartition(n))
            .write
            .mode("append") //追加寫入()
            .jdbc(jdbcUrl, "test", prop)
}

寫入的時候需要注意並行度,以免給MySQL帶來太大寫入壓力。
 

Spark讀寫MySQL問題彙總

1. Spark寫MySQL覆蓋表結構問題

問題:你在MySQL中創建了一個表user,現在你要通過Spark將DataFrame中的數據寫入到表user中,代碼如下:

df
    .coalesce(2)
    .write
    .mode("overwrite")
    .jdbc("url", "db.user", props)

原因分析:因爲每次都是要覆蓋之前表中的所有數據,所以寫入mode類型爲overwrite。而overwrite模式的底層執行機制是,先把之前的表user刪掉(drop table db.user),然後再根據要寫入的DataFrame的schema及字段類型創建新的表,這就造成你原來的建表語句就失效了(比如,之前指定的主鍵、字段類型等都被覆蓋了)。

造成這個問題的原因就是,DataFrame在寫MySQL的時候,有一個選項"truncate", 默認值是false, false情況下覆蓋寫(overwrite)是執行"drop table",而true情況下覆蓋寫纔會執行"truncate table"。

解決方法:將選項"truncate"設置爲true即可,代碼如下:

df
    .coalesce(2)
    .write
    .option("truncate", "true")
    .mode("overwrite")
    .jdbc("url", "db.user", props)
2. Spark讀MySQL寫Hive,boolean(true、false)類型自動轉成了0和1

問題:當MySQL中某個列表示的是tinyint(1)的Boolean類型時(true或false),寫到Hive後,查詢顯示的是1或0。

原因分析:因爲MySQL沒有內建的Boolean類型,而是通過tinyint(1)來代替Boolean類型的,0代表false,1代表true。所以,當你往MySQL中插入true或false的時候,MySQL會自動轉換成1或0進行存儲。因此,當你從MySQL表讀取true和false的時候,其實讀取的是1和0,那麼寫入到Hive之後也是1和0。

解決方法:在jdbc url中配置參數tinyInt1isBit=false,如下所示:

url=jdbc:mysql://hostname:3306/dbname?tinyInt1isBit=false
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章