插入数据
此页面将展示使用 SQL 语言,配合各种编程语言将数据插入到 TiDB 中。
在开始之前
在阅读本页面之前,你需要准备以下事项:
插入行
假设你需要插入多行数据,那么会有两种插入的办法,假设需要插入 3 个玩家数据:
- 一个多行插入语句:
1. INSERT INTO `player` (`id`, `coins`, `goods`) VALUES (1, 1000, 1), (2, 230, 2), (3, 300, 5);
- 多个单行插入语句:
1. INSERT INTO `player` (`id`, `coins`, `goods`) VALUES (1, 1000, 1);
2. INSERT INTO `player` (`id`, `coins`, `goods`) VALUES (2, 230, 2);
3. INSERT INTO `player` (`id`, `coins`, `goods`) VALUES (3, 300, 5);
一般来说使用一个多行插入语句,会比多个单行插入语句快。
- SQL
- Java
- Golang
- Python
在 SQL 中插入多行数据的示例:
1. CREATE TABLE `player` (`id` INT, `coins` INT, `goods` INT);
2. INSERT INTO `player` (`id`, `coins`, `goods`) VALUES (1, 1000, 1), (2, 230, 2);
有关如何使用此 SQL,可查阅连接到 TiDB 集群文档部分,按文档步骤使用客户端连接到 TiDB 集群后,输入 SQL 语句即可。
在 Java 中插入多行数据的示例:
1. // ds is an entity of com.mysql.cj.jdbc.MysqlDataSource
2. try (Connection connection = ds.getConnection()) {
3. connection.setAutoCommit(false);
5. PreparedStatement pstmt = connection.prepareStatement("INSERT INTO player (id, coins, goods) VALUES (?, ?, ?)"))
7. // first player
8. pstmt.setInt(1, 1);
9. pstmt.setInt(2, 1000);
10. pstmt.setInt(3, 1);
11. pstmt.addBatch();
13. // second player
14. pstmt.setInt(1, 2);
15. pstmt.setInt(2, 230);
16. pstmt.setInt(3, 2);
17. pstmt.addBatch();
19. pstmt.executeBatch();
20. connection.commit();
21. } catch (SQLException e) {
22. e.printStackTrace();
23. }
另外,由于 MySQL JDBC Driver 默认设置问题,你需更改部分参数,以获得更好的批量插入性能:
| 参数 | 作用 | 推荐场景 | 推荐配置 |
|---|---|---|---|
useServerPrepStmts |
是否使用服务端开启预处理语句支持 | 在需要多次使用预处理语句时 | true |
cachePrepStmts |
客户端是否缓存预处理语句 | useServerPrepStmts=true 时 |
true |
prepStmtCacheSqlLimit |
预处理语句最大大小(默认 256 字符) | 预处理语句大于 256 字符时 | 按实际预处理语句大小配置 |
prepStmtCacheSize |
预处理语句最大缓存数量 (默认 25 条) | 预处理语句数量大于 25 条时 | 按实际预处理语句数量配置 |
rewriteBatchedStatements |
是否重写 Batch 语句 | 需要批量操作时 | true |
allowMultiQueries |
开启批量操作 | 因为一个客户端 Bug 在 rewriteBatchedStatements = true 和 useServerPrepStmts = true 时,需设置此项 |
true |
MySQL JDBC Driver 还提供了一个集成配置项:useConfigs。当它配置为 maxPerformance 时,相当于配置了一组配置,以 mysql:mysql-connector-java:8.0.28 为例,useConfigs=maxPerformance 包含:
1. cachePrepStmts=true
2. cacheCallableStmts=true
3. cacheServerConfiguration=true
4. useLocalSessionState=true
5. elideSetAutoCommits=true
6. alwaysSendSetIsolation=false
7. enableQueryTimeouts=false
8. connectionAttributes=none
9. useInformationSchema=true
你可以自行查看 mysql-connector-java-{version}.jar!/com/mysql/cj/configurations/maxPerformance.properties 来获得对应版本 MySQL JDBC Driver 的 useConfigs=maxPerformance 包含配置。
在此处给出一个较为的通用场景的 JDBC 连接字符串配置,以 Host: 127.0.0.1,Port: 4000,用户名: root,密码: 空,默认数据库: test为例:
1. jdbc:mysql://127.0.0.1:4000/test?user=root&useConfigs=maxPerformance&useServerPrepStmts=true&prepStmtCacheSqlLimit=2048&prepStmtCacheSize=256&rewriteBatchedStatements=true&allowMultiQueries=true
有关 Java 的完整示例,可参阅:
- TiDB 和 Java 的简单 CRUD 应用程序 - 使用 JDBC
- TiDB 和 Java 的简单 CRUD 应用程序 - 使用 Hibernate
- Build the TiDB Application using Spring Boot
在 Golang 中插入多行数据的示例:
1. package main
3. import (
4. "database/sql"
5. "strings"
7. _ "github.com/go-sql-driver/mysql"
8. )
10. type Player struct {
11. ID string
12. Coins int
13. Goods int
14. }
16. func bulkInsertPlayers(db *sql.DB, players []Player, batchSize int) error {
17. tx, err := db.Begin()
18. if err != nil {
19. return err
20. }
22. stmt, err := tx.Prepare(buildBulkInsertSQL(batchSize))
23. if err != nil {
24. return err
25. }
27. defer stmt.Close()
29. for len(players) > batchSize {
30. if _, err := stmt.Exec(playerToArgs(players[:batchSize])...); err != nil {
31. tx.Rollback()
32. return err
33. }
35. players = players[batchSize:]
36. }
38. if len(players) != 0 {
39. if _, err := tx.Exec(buildBulkInsertSQL(len(players)), playerToArgs(players)...); err != nil {
40. tx.Rollback()
41. return err
42. }
43. }
45. if err := tx.Commit(); err != nil {
46. tx.Rollback()
47. return err
48. }
50. return nil
51. }
53. func playerToArgs(players []Player) []interface{} {
54. var args []interface{}
55. for _, player := range players {
56. args = append(args, player.ID, player.Coins, player.Goods)
57. }
58. return args
59. }
61. func buildBulkInsertSQL(amount int) string {
62. return "INSERT INTO player (id, coins, goods) VALUES (?, ?, ?)" + strings.Repeat(",(?,?,?)", amount-1)
63. }
有关 Golang 的完整示例,可参阅:
在 Python 中插入多行数据的示例:
1. import MySQLdb
3. connection = MySQLdb.connect(
4. host="127.0.0.1",
5. port=4000,
6. user="root",
7. password="",
8. database="bookshop",
9. autocommit=True
10. )
11. with get_connection(autocommit=True) as connection:
13. with connection.cursor() as cur:
14. player_list = random_player(1919)
15. for idx in range(0, len(player_list), 114):
16. cur.executemany("INSERT INTO player (id, coins, goods) VALUES (%s, %s, %s)", player_list[idx:idx + 114])
有关 Python 的完整示例,可参阅:
- TiDB 和 Python 的简单 CRUD 应用程序 - 使用 PyMySQL
- TiDB 和 Python 的简单 CRUD 应用程序 - 使用 mysqlclient
- TiDB 和 Python 的简单 CRUD 应用程序 - 使用 mysql-connector-python
- TiDB 和 Python 的简单 CRUD 应用程序 - 使用 SQLAlchemy
- TiDB 和 Python 的简单 CRUD 应用程序 - 使用 peewee
批量插入
如果你需要快速地将大量数据导入 TiDB 集群,最好的方式并不是使用 INSERT 语句,这并不是最高效的方法,而且需要你自行处理异常等问题。推荐使用 PingCAP 提供的一系列工具进行数据迁移:
- 数据导出工具:Dumpling。可以导出 MySQL 或 TiDB 的数据到本地或 Amazon S3 中。
- 数据导入工具:TiDB Lightning。可以导入
Dumpling导出的数据、CSV 文件,或者 Amazon Aurora 生成的 Apache Parquet 文件。同时支持在本地盘或 Amazon S3 云盘读取数据。 - 数据同步工具:TiDB Data Migration。可同步 MySQL、MariaDB、Amazon Aurora 数据库到 TiDB 中。且支持分库分表数据库的迁移。
- 数据备份恢复工具:Backup & Restore (BR)。相对于
Dumpling,BR 更适合大数据量的场景。
避免热点
在设计表时需要考虑是否存在大量插入行为,若有,需在表设计期间对热点进行规避。请查看创建表 - 选择主键部分,并遵从选择主键时应遵守的规则。
更多有关热点问题的处理办法,请参考 TiDB 热点问题处理文档。
主键为 AUTO_RANDOM 表插入数据
在插入的表主键为 AUTO_RANDOM 时,这时默认情况下,不能指定主键。例如 bookshop 数据库中,可以看到 users 表 的 id 字段含有 AUTO_RANDOM 属性。
此时,不可使用类似以下 SQL 进行插入:
1. INSERT INTO `bookshop`.`users` (`id`, `balance`, `nickname`) VALUES (1, 0.00, 'nicky');
将会产生错误:
1. ERROR 8216 (HY000): Invalid auto random: Explicit insertion on auto_random column is disabled. Try to set @@allow_auto_random_explicit_insert = true.
这是旨在提示你,不建议在插入时手动指定 AUTO_RANDOM 的列。这时,你有两种解决办法处理此错误:
- (推荐) 插入语句中去除此列,使用 TiDB 帮你初始化的
AUTO_RANDOM值。这样符合AUTO_RANDOM的语义。
1. INSERT INTO `bookshop`.`users` (`balance`, `nickname`) VALUES (0.00, 'nicky');
- 如果你确认一定需要指定此列,那么可以使用 SET 语句通过更改用户变量的方式,允许在插入时,指定 AUTO_RANDOM 的列。
1. SET @@allow_auto_random_explicit_insert = true;
2. INSERT INTO `bookshop`.`users` (`id`, `balance`, `nickname`) VALUES (1, 0.00, 'nicky');
使用 HTAP
在 TiDB 中,使用 HTAP 能力无需你在插入数据时进行额外操作。不会有任何额外的插入逻辑,由 TiDB 自动进行数据的一致性保证。你只需要在创建表后,开启列存副本同步,就可以直接使用列存副本来加速你的查询。
