华为云用户手册

  • 示例2:从MY迁移数据 下面示例演示如何通过CopyManager从MY向GaussDB进行数据迁移的过程。 1 2 3 4 5 6 7 8 9101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384 import java.io.StringReader;import java.sql.Connection;import java.sql.DriverManager;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;import org.postgresql.copy.CopyManager;import org.postgresql.core.BaseConnection;public class Migration{ public static void main(String[] args) { String url = new String("jdbc:postgresql://localhost:8000/postgres"); //数据库URL String user = new String("username"); //GaussDB数据库用户名 String pass = new String("passwd"); //GaussDB数据库密码 String tablename = new String("migration_table_1"); //定义表信息 String delimiter = new String("|"); //定义分隔符 String encoding = new String("UTF8"); //定义字符集 String driver = "org.postgresql.Driver"; StringBuffer buffer = new StringBuffer(); //定义存放格式化数据的缓存 try { //获取源数据库查询结果集 ResultSet rs = getDataSet(); //遍历结果集,逐行获取记录 //将每条记录中各字段值,按指定分隔符分割,由换行符结束,拼成一个字符串 //把拼成的字符串,添加到缓存buffer while (rs.next()) { buffer.append(rs.getString(1) + delimiter + rs.getString(2) + delimiter + rs.getString(3) + delimiter + rs.getString(4) + "\n"); } rs.close(); try { //建立目标数据库连接 Class.forName(driver); Connection conn = DriverManager.getConnection(url, user, pass); BaseConnection baseConn = (BaseConnection) conn; baseConn.setAutoCommit(false); //初始化表信息 String sql = "Copy " + tablename + " from STDIN with (DELIMITER " + "'" + delimiter + "'" +","+ " ENCODING " + "'" + encoding + "'"); //提交缓存buffer中的数据 CopyManager cp = new CopyManager(baseConn); StringReader reader = new StringReader(buffer.toString()); cp.copyIn(sql, reader); baseConn.commit(); reader.close(); baseConn.close(); } catch (ClassNotFoundException e) { e.printStackTrace(System.out); } catch (SQLException e) { e.printStackTrace(System.out); } } catch (Exception e) { e.printStackTrace(); } } //******************************** // 从源数据库返回查询结果集 //********************************* private static ResultSet getDataSet() { ResultSet rs = null; try { Class.forName("com.MY.jdbc.Driver").newInstance(); Connection conn = DriverManager.getConnection("jdbc:MY://10.119.179.227:3306/jack?useSSL=false&allowPublicKeyRetrieval=true", "jack", "xxxxxxxxx"); Statement stmt = conn.createStatement(); rs = stmt.executeQuery("select * from migration_table"); } catch (SQLException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return rs; }} 父主题: 使用COPY FROM STDIN导入数据
  • 处理数据导入错误 根据获取的错误信息,请对照下表,处理数据导入错误。 表2 处理数据导入错误 错误信息 原因 解决办法 missing data for column "r_reason_desc" 数据源文件中的列数比外表定义的列数少。 对于TEXT格式的数据源文件,由于转义字符(\)导致delimiter(分隔符)错位或者quote(引号字符)错位造成的错误。 示例:目标表存在3列字段,导入的数据如下所示。由于存在转义字符“\”,分隔符“|”被转义为第二个字段的字段值,导致第三个字段值缺失。 BE|Belgium\|1 由于列数少导致的报错,选择下列办法解决: 在数据源文件中,增加列“r_reason_desc”的字段值。 在创建外表时,将参数“fill_missing_fields”设置为“on”。即当导入过程中,若数据源文件中一行数据的最后一个字段缺失,则把最后一个字段的值设置为NULL,不报错。 对由于转义字符导致的错误,需检查报错的行中是否含有转义字符(\)。若存在,建议在创建外表时,将参数“noescaping”(是否不对'\'和后面的字符进行转义)设置为true。 extra data after last expected column 数据源文件中的列数比外表定义的列数多。 在数据源文件中,删除多余的字段值。 在创建外表时,将参数“ignore_extra_data”设置为“on”。即在导入过程中,若数据源文件比外表定义的列数多,则忽略行尾多出来的列。 invalid input syntax for type numeric: "a" 数据类型错误。 在数据源文件中,修改输入字段的数据类型。根据此错误信息,请将输入的数据类型修改为numeric。 null value in column "staff_id" violates not-null constraint 非空约束。 在数据源文件中,增加非空字段信息。根据此错误信息,请增加“staff_id”列的值。 duplicate key value violates unique constraint "reg_id_pk" 唯一约束。 删除数据源文件中重复的行。 通过设置关键字“DISTINCT”,从SELECT结果集中删除重复的行,保证导入的每一行都是唯一的。 1 openGauss=# INSERT INTO reasons SELECT DISTINCT * FROM foreign_tpcds_reasons; value too long for type character varying(16) 字段值长度超过限制。 在数据源文件中,修改字段值长度。根据此错误信息,字段值长度限制为VARCHAR2(16)。
  • 示例1:通过本地文件导入导出数据 在使用JAVA语言基于GaussDB进行二次开发时,可以使用CopyManager接口,通过流方式,将数据库中的数据导出到本地文件或者将本地文件导入数据库中,文件格式支持CSV、TEXT等格式。 样例程序如下,执行时需要加载GaussDB的JDBC驱动。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99100101102 import java.sql.Connection; import java.sql.DriverManager; import java.io.IOException;import java.io.FileInputStream;import java.io.FileOutputStream;import java.sql.SQLException; import org.postgresql.copy.CopyManager; import org.postgresql.core.BaseConnection; public class Copy{ public static void main(String[] args) { String urls = new String("jdbc:postgresql://localhost:8000/postgres"); //数据库URL String username = new String("username"); //用户名 String password = new String("passwd"); //密码 String tablename = new String("migration_table"); //定义表信息 String tablename1 = new String("migration_table_1"); //定义表信息 String driver = "org.postgresql.Driver"; Connection conn = null; try { Class.forName(driver); conn = DriverManager.getConnection(urls, username, password); } catch (ClassNotFoundException e) { e.printStackTrace(System.out); } catch (SQLException e) { e.printStackTrace(System.out); } // 将表migration_table中数据导出到本地文件d:/data.txt try { copyToFile(conn, "d:/data.txt", "(SELECT * FROM migration_table)"); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } //将d:/data.txt中的数据导入到migration_table_1中。 try { copyFromFile(conn, "d:/data.txt", tablename1); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 将表migration_table_1中的数据导出到本地文件d:/data1.txt try { copyToFile(conn, "d:/data1.txt", tablename1); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static void copyFromFile(Connection connection, String filePath, String tableName) throws SQLException, IOException { FileInputStream fileInputStream = null; try { CopyManager copyManager = new CopyManager((BaseConnection)connection); fileInputStream = new FileInputStream(filePath); copyManager.copyIn("COPY " + tableName + " FROM STDIN ", fileInputStream); } finally { if (fileInputStream != null) { try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } public static void copyToFile(Connection connection, String filePath, String tableOrQuery) throws SQLException, IOException { FileOutputStream fileOutputStream = null; try { CopyManager copyManager = new CopyManager((BaseConnection)connection); fileOutputStream = new FileOutputStream(filePath); copyManager.copyOut("COPY " + tableOrQuery + " TO STDOUT", fileOutputStream); } finally { if (fileOutputStream != null) { try { fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } } 父主题: 使用COPY FROM STDIN导入数据
  • 深层复制 数据导入后,如果需要修改表的分区键、或者将行存表改列存、添加PCK(Partial Cluster Key)约束等场景下,可以使用深层复制的方式对表进行调整。深层复制是指重新创建表,然后使用批量插入填充表的过程。 GaussDB提供了三种深层复制的方式供用户选择。 使用CREATE TABLE执行深层复制 使用CREATE TABLE LIKE执行深层复制 通过创建临时表并截断原始表来执行深层复制 父主题: 导入数据
  • \copy命令 \copy命令格式以及说明参见表1。 表1 \copy元命令说明 语法 说明 \copy { table [ ( column_list ) ] | ( query ) } { from | to } { filename | stdin | stdout | pstdin | pstdout } [ with ] [ binary ] [ delimiter [ as ] 'character' ] [ null [ as ] 'string' ] [ csv [ header ] [ quote [ as ] 'character' ] [ escape [ as ] 'character' ] [ force quote column_list | * ] [ force not null column_list ] ] 在任何gsql客户端登录数据库成功后,可以使用该命令进行数据的导入/导出。但是与SQL的COPY命令不同,该命令读取/写入的文件是本地文件,而非数据库服务器端文件;所以,要操作的文件的可访问性、权限等,都是受限于本地用户的权限。 说明: \COPY只适合小批量、格式良好的数据导入,不会对非法字符做预处理,也无容错能力,无法适用于含有异常数据的场景。导入数据应优先选择COPY。
  • 操作步骤 假设存在表customer_t,表结构如下: 123456 openGauss=# CREATE TABLE customer_t( c_customer_sk integer, c_customer_id char(5), c_first_name char(6), c_last_name char(8) ) ; 可以使用如下DML命令对表进行数据更新。 使用INSERT向表中插入数据。 向表customer_t中插入一行。 1 openGauss=# INSERT INTO customer_t (c_customer_sk, c_customer_id, c_first_name,c_last_name) VALUES (3769, 5, 'Grace','White'); 向表customer_t中插入多行数据。 12345 openGauss=# INSERT INTO customer_t (c_customer_sk, c_customer_id, c_first_name,c_last_name) VALUES (6885, 1, 'Joes', 'Hunter'), (4321, 2, 'Lily','Carter'), (9527, 3, 'James', 'Cook'),(9500, 4, 'Lucy', 'Baker'); 更多关于INSERT的使用方法,请参见向表中插入数据。 使用UPDATE更新表中数据。修改字段c_customer_id值为0。 1 openGauss=# UPDATE customer_t SET c_customer_id = 0; 更多关于UPDATE的使用方法,请参见UPDATE。 使用DELETE删除表中的行。 可以使用WHERE子句指定需要删除的行,若不指定即删除表中所有的行,只保留数据结构。 1 openGauss=# DELETE FROM customer_t WHERE c_last_name = 'Baker'; 更多关于DELETE的使用方法,请参见DELETE。 使用TRUNCATE命令快速从表中删除所有的行。 1 openGauss=# TRUNCATE TABLE customer_t; 更多关于TRUNCATE的使用方法,请参见TRUNCATE。 删除表时,DELETE语句每次删除一行数据而TRUNCATE语句是通过释放表存储的数据页来删除数据,使用TRUNCATE语句比使用DELETE语句更加快速。 使用DELETE语句删除表时,仅删除数据,不释放存储空间。使用TRUNCATE语句删除表时,删除数据且释放存储空间。
  • 操作步骤 使用CREATE TABLE LIKE语句创建表customer_t的副本customer_t_copy。 1 openGauss=# CREATE TABLE customer_t_copy (LIKE customer_t); 使用INSERT INTO…SELECT语句向副本填充原始表中的数据。 1 openGauss=# INSERT INTO customer_t_copy (SELECT * FROM customer_t); 删除原始表。 1 openGauss=# DROP TABLE customer_t; 使用ALTER TABLE语句将副本重命名为原始表名称。 1 openGauss=# ALTER TABLE customer_t_copy RENAME TO customer_t;
  • 对表执行VACUUM 如果导入过程中,进行了大量的更新或删除行时,应运行VACUUM FULL命令,然后运行ANALYZE命令。大量的更新和删除操作,会产生大量的磁盘页面碎片,从而逐渐降低查询的效率。VACUUM FULL可以将磁盘页面碎片恢复并交还操作系统。 对表执行VACUUM FULL。 以表product_info为例,VACUUM FULL命令如下: 1 openGauss=# VACUUM FULL product_info VACUUM 父主题: 导入数据
  • 操作步骤 执行如下步骤对表customer_t进行深层复制。 使用CREATE TABLE语句创建表customer_t的副本customer_t_copy。 123456 openGauss=# CREATE TABLE customer_t_copy( c_customer_sk integer, c_customer_id char(5), c_first_name char(6), c_last_name char(8) ) ; 使用INSERT INTO…SELECT语句向副本填充原始表中的数据。 1 openGauss=# INSERT INTO customer_t_copy (SELECT * FROM customer_t); 删除原始表。 1 openGauss=# DROP TABLE customer_t; 使用ALTER TABLE语句将副本重命名为原始表名称。 1 openGauss=# ALTER TABLE customer_t_copy RENAME TO customer_t;
  • 表自动分析 GaussDB提供了GUC参数autovacuum用于控制数据库自动清理功能的启动。 autovacuum设置为on时,系统定时启动autovacuum线程来进行表自动分析,如果表中数据量发生较大变化达到阈值时,会触发表自动分析,即autoanalyze。 对于空表而言,当表中插入数据的行数大于50时,会触发表自动进行ANALYZE。 对于表中已有数据的情况,阈值设定为50+10%*reltuples,其中reltuples是表的总行数。 autovacuum自动清理功能的生效还依赖于下面两个GUC参数: track_counts 参数需要设置为on,表示开启收集收据库统计数据功能。 autovacuum_max_workers参数需要大于0,该参数表示能同时运行的自动清理线程的最大数量。 autoanalyze只支持默认采样方式,不支持百分比采样方式。 多列统计信息(当前特性是实验室特性,使用时请联系华为工程师提供技术支持)仅支持百分比采样,因此autoanalyze不收集多列统计信息。 autoanalyze支持行存表和列存表,不支持外表、临时表、unlogged表和toast表。
  • 并发写入事务的潜在死锁情况 只要事务涉及多个表的或者同一个表相同行的更新时,同时运行的事务就可能在同时尝试写入时变为死锁状态。事务会在提交或回滚时一次性解除其所有锁定,而不会逐一放弃锁定。 例如,假设事务T1和T2在大致相同的时间开始: 如果T1开始对表A进行写入且T2开始对表B进行写入,则两个事务均可继续而不会发生冲突;但是,如果T1完成了对表A的写入操作并需要开始对表B进行写入,此时操作的行数正好与T2一致,它将无法继续,因为T2仍保持对表B对应行的锁定,此时T2开始更新表A中与T1相同的行数,此时也将无法继续,产生死锁,在锁等待超时内,前面事务提交释放锁,后面的事务可以继续执行更新,等待时间超时后,事务抛错,有一个事务退出。 如果T1,T2都对表A进行写入,此时T1更新1-5行的数据,T2更新6-10行的数据,两个事务不会发生冲突,但是,如果T1完成后开始对表A的6-10行数据进行更新,T2完成后开始更新1-5行的数据,此时两个事务无法继续,在锁等待超时内,前面事务提交释放锁,后面的事务可以继续执行更新,等待时间超时后,事务抛错,有一个事务退出。 父主题: 管理并发写入操作
  • 写入和读写操作 关于写入和读写操作的命令: INSERT,可向表中插入一行或多行数据。 UPDATE,可修改表中现有数据。 DELETE,可删除表中现有数据。 COPY,导入数据。 INSERT和COPY是纯写入的操作。并发写入操作,需要等待,对同一个表的操作,当事务T1的INSERT或COPY未解除锁定时,事务T2的INSERT或COPY需等待,事务T1解除锁定时,事务T2正常继续。 UPDATE和DELETE是读写操作(先查询出要操作的行)。UPDATE和DELETE执行前需要先查询数据,由于并发事务彼此不可见,所以UPDATE和DELETE操作是读取事务发生前提交的数据的快照。写入操作,是行级锁,当事务T1和事务T2并发更新同一行时,后发生的事务T2会等待,根据设置的等待时长,若超时事务T1未提交则事务T2执行失败;当事务T1和事务T2并发更新的行不同时,事务T1和事务T2都会执行成功。 父主题: 管理并发写入操作
  • 操作步骤 使用CREATE TEMP TABLE AS语句创建表customer_t的临时表副本customer_t_temp。 1 openGauss=# CREATE TEMP TABLE customer_t_temp AS SELECT * FROM customer_t; 与使用永久表相比,使用临时表可以提高性能,但存在丢失数据的风险。临时表只在当前会话可见,本会话结束后将自动删除。如果数据丢失是不可接受的,请使用永久表。 临时表与普通表的存放位置无差,也可指定tablespace存放。本地临时表应用过多可能会导致系统表膨胀,但总体影响在可接受范围内。 截断当前表customer_t。 1 openGauss=# TRUNCATE customer_t; 使用INSERT INTO…SELECT语句从副本中向原始表中填充数据。 1 openGauss=# INSERT INTO customer_t (SELECT * FROM customer_t_temp); 删除临时表副本customer_t_temp。 1 openGauss=# DROP TABLE customer_t_temp;
  • 并发写入示例 本章节以表test为例,分别介绍相同表的INSERT和DELETE并发,相同表的并发INSERT,相同表的并发UPDATE,以及数据导入和查询的并发的执行详情。 1 CREATE TABLE test(id int, name char(50), address varchar(255)); 相同表的INSERT和DELETE并发 相同表的并发INSERT 相同表的并发UPDATE 数据导入和查询的并发 父主题: 管理并发写入操作
  • 事务隔离说明 GaussDB基于MVCC(多版本并发控制)并结合两阶段锁的方式进行事务管理,其特点是读写之间不阻塞。SELECT是纯读操作,UPDATE和DELETE是读写操作。 读写操作和纯读操作之间并不会发生冲突,读写操作之间也不会发生冲突。每个并发事务在事务开始时创建事务快照,并发事务之间不能检测到对方的更改。 读已提交隔离级别中,如果事务T1提交后,事务T2就可以看到事务T1更改的结果。 可重复读级别中,如果事务T1提交事务前事务T2开始执行,则事务T1提交后,事务T2依旧看不到事务T1更改的结果,保证了一个事务开始后,查询的结果前后一致,不受其他事务的影响。 读写操作,支持的是行级锁,不同的事务可以并发更新同一个表,只有更新同一行时才需等待,后发生的事务会等待先发生的事务提交后,再执行更新操作。 READ COMMITTED:读已提交隔离级别,事务只能读到已提交的数据而不会读到未提交的数据,这是缺省值。 REPEATABLE READ: 事务只能读到事务开始之前已提交的数据,不能读到未提交的数据以及事务执行期间其它并发事务提交的修改。 父主题: 管理并发写入操作
  • 操作步骤 创建源表products,并插入数据。 1 2 3 4 5 6 7 8 91011 openGauss=# CREATE TABLE products ( product_id INTEGER, product_name VARCHAR2(60), category VARCHAR2(60) );openGauss=# INSERT INTO products VALUES (1502, 'olympus camera', 'electrncs'),(1601, 'lamaze', 'toys'),(1666, 'harry potter', 'toys'),(1700, 'wait interface', 'books'); 创建目标表newproducts,并插入数据。 1 2 3 4 5 6 7 8 9101112 openGauss=# CREATE TABLE newproducts ( product_id INTEGER, product_name VARCHAR2(60), category VARCHAR2(60) ); openGauss=# INSERT INTO newproducts VALUES (1501, 'vivitar 35mm', 'electrncs'),(1502, 'olympus ', 'electrncs'),(1600, 'play gym', 'toys'),(1601, 'lamaze', 'toys'), (1666, 'harry potter', 'dvd'); 使用MERGE INTO 语句将源表products的数据合并至目标表newproducts。 1234567 openGauss=# MERGE INTO newproducts np USING products p ON (np.product_id = p.product_id ) WHEN MATCHED THEN UPDATE SET np.product_name = p.product_name, np.category = p.category WHEN NOT MATCHED THEN INSERT VALUES (p.product_id, p.product_name, p.category) ; 上述语句中使用的参数说明,请见表1。更多信息,请参见MERGE INTO。 表1 MERGE INTO语句参数说明 参数 说明 举例 INTO 子句 指定需要更新或插入数据的目标表。 目标表支持指定别名。 取值:newproducts np 说明:名为newproducts,别名为np的目标表。 USING子句 指定源表。源表支持指定别名。 取值:products p 说明:名为products,别名为p的源表。 ON子句 指定目标表和源表的关联条件。 关联条件中的字段不支持更新。 取值:np.product_id = p.product_id 说明:指定的关联条件为,目标表newproducts的product_id字段和源表products的product_id字段相等。 WHEN MATCHED子句 当源表和目标表中数据针对关联条件可以匹配上时,选择WHEN MATCHED子句进行UPDATE操作。 仅支持指定一个WHEN MATCHED子句。 WHEN MATCHED子句可缺省,缺省时,对于满足ON子句条件的行,不进行任何操作。 取值:WHEN MATCHED THEN UPDATE SET np.product_name = p.product_name, np.category = p.category 说明:当满足ON子句条件时,将目标表newproducts的product_name、category字段的值替换为源表products相对应字段的值。 WHEN NOT MATCHED子句 当源表和目标表中数据针对关联条件无法匹配时,选择WHEN NOT MATCHED子句进行INSERT操作。 仅支持指定一个WHEN NOT MATCHED子句。 WHEN NOT MATCHED子句可缺省。 不支持INSERT子句中包含多个VALUES。 WHEN MATCHED和WHEN NOT MATCHED子句顺序可以交换,可以缺省其中一个,但不能同时缺省。 取值:WHEN NOT MATCHED THEN INSERT VALUES (p.product_id, p.product_name, p.category) 说明:将源表products中,不满足ON子句条件的行插入目标表newproducts。 查询合并后的目标表newproducts。 1 openGauss=# SELECT * FROM newproducts; 返回信息如下: product_id | product_name | category------------+----------------+----------- 1501 | vivitar 35mm | electrncs 1502 | olympus camera | electrncs 1666 | harry potter | toys 1600 | play gym | toys 1601 | lamaze | toys 1700 | wait interface | books(6 rows)
  • 相同表的INSERT和DELETE并发 事务T1: 123 START TRANSACTION;INSERT INTO test VALUES(1,'test1','test123');COMMIT; 事务T2: 123 START TRANSACTION;DELETE test WHERE NAME='test1';COMMIT; 场景1: 开启事务T1,不提交的同时开启事务T2,事务T1执行INSERT完成后,执行事务T2的DELETE,此时显示DELETE 0,由于事务T1未提交,事务2看不到事务插入的数据; 场景2: READ COMMITTED级别 开启事务T1,不提交的同时开启事务T2,事务T1执行INSERT完成后,提交事务T1,事务T2再执行DELETE语句时,此时显示DELETE 1,事务T1提交完成后,事务T2可以看到此条数据,可以删除成功。 REPEATABLE READ级别 开启事务T1,不提交的同时开启事务T2,事务T1执行INSERT完成后,提交事务T1,事务T2再执行DELETE语句时,此时显示DELETE 0,事务T1提交完成后,事务T2依旧看不到事务T1的数据,一个事务中前后查询到的数据是一致的。 父主题: 并发写入示例
  • 数据导入和查询的并发 事务T1: 123 START TRANSACTION;COPY test FROM '...';COMMIT; 事务T2: 123 START TRANSACTION;SELECT * FROM test;COMMIT; 场景1: 开启事务T1,不提交的同时开启事务T2,事务T1开始执行COPY,事务T2开始执行SELECT,事务T1和事务T2都执行成功。事务T2中查询看不到事务T1新COPY进来的数据。 场景2: READ COMMITTED级别 开启事务T1,不提交的同时开启事务T2,事务T1开始执行COPY,然后提交,事务T2查询,可以看到事务T1中COPY的数据。 REPEATABLE READ级别 开启事务T1,不提交的同时开启事务T2,事务T1开始执行COPY,然后提交,事务T2 查询,看不到事务T1中COPY的数据。 父主题: 并发写入示例
  • 相同表的并发INSERT 事务T1: 123 START TRANSACTION;INSERT INTO test VALUES(2,'test2','test123');COMMIT; 事务T2: 123 START TRANSACTION;INSERT INTO test VALUES(3,'test3','test123');COMMIT; 场景1: 开启事务T1,不提交的同时开启事务T2,事务T1执行INSERT完成后,执行事务T2的INSERT语句,可以执行成功,读已提交和可重复读隔离级别下,此时在事务T1中执行SELECT语句,看不到事务T2中插入的数据,事务T2中执行查询语句看不到事务T1中插入的数据。 场景2: READ COMMITTED级别 开启事务T1,不提交的同时开启事务T2,事务T1执行INSERT完成后直接提交,事务T2中执行INSERT语句后执行查询语句,可以看到事务T1中插入的数据。 REPEATABLE READ级别 开启事务T1,不提交的同时开启事务T2,事务T1执行INSERT完成后直接提交,事务T2中执行INSERT语句后执行查询语句,看不到事务T1中插入的数据。 父主题: 并发写入示例
  • 调优流程 调优流程如图1所示。 图1 GaussDB性能调优流程 调优各阶段说明,如表1所示。 表1 GaussDB性能调优流程说明 阶段 描述 确定性能调优范围 获取数据库节点的CPU、内存、I/O和网络资源使用情况,确认这些资源是否已被充分利用,是否存在瓶颈点。 SQL调优指南 审视业务所用SQL语句是否存在可优化空间,包括: 通过ANALYZE语句生成表统计信息:ANALYZE语句可收集与数据库中表内容相关的统计信息,统计结果存储在系统表PG_STATISTIC中。执行计划生成器会使用这些统计数据,以确定最有效的执行计划。 分析执行计划:EXPLAIN语句可显示SQL语句的执行计划,EXPLAIN PERFORMANCE语句可显示SQL语句中各算子的执行时间。 查找问题根因并进行调优:通过分析执行计划,找到可能存在的原因,进行针对性的调优,通常为调整数据库级SQL调优参数。 编写更优的SQL:介绍一些复杂查询中的中间临时数据缓存、结果集缓存、结果集合并等场景中的更优SQL语法。
  • 相同表的并发UPDATE 事务T1: 123 START TRANSACTION;UPDATE test SET address='test1234' WHERE name='test1';COMMIT; 事务T2: 123 START TRANSACTION;UPDATE test SET address='test1234' WHERE name='test2';COMMIT; 事务T3: 123 START TRANSACTION;UPDATE test SET address='test1234' WHERE name='test1';COMMIT; 场景1: 开启事务T1,不提交的同时开启事务T2,事务T1开始执行UPDATE,事务T2开始执行UPDATE,事务T1和事务T2都执行成功。更新不同行时,更新操作拿的是行级锁,不会发生冲突,两个事务都可以执行成功。 场景2: 开启事务T1,不提交的同时开启事务T3,事务T1开始执行UPDATE,事务T3开始执行UPDATE,事务T1执行成功,事务T3等待超时后会出错。更新相同行时,事务T1未提交时,未释放锁,导致事务T3执行不成功。 父主题: 并发写入示例
  • 性能因素 多个性能因素会影响数据库性能,了解这些因素可以帮助定位和分析性能问题。 系统资源 数据库性能在很大程度上依赖于磁盘的I/O和内存使用情况。为了准确设置性能指标,用户需要了解数据库部署硬件的基本性能。CPU,硬盘,磁盘控制器,内存和网络接口等这些硬件性能将显著影响数据库的运行速度。 负载 负载等于数据库系统的需求总量,它会随着时间变化。总体负载包含用户查询,应用程序,并行作业,事务以及数据库随时传递的系统命令。比如:多用户在执行多个查询时会提高负载。负载会显著地影响数据库的性能。了解工作负载高峰期可以帮助用户更合理地利用系统资源,更有效地完成系统任务。 吞吐量 使用系统的吞吐量来定义处理数据的整体能力。数据库的吞吐量以每秒的查询次数、每秒的处理事务数量或平均响应时间来测量。数据库的处理能力与底层系统(磁盘I/O,CPU速度,存储器带宽等)有密切的关系,所以当设置数据库吞吐量目标时,需要提前了解硬件的性能。 竞争 竞争是指两组或多组负载组件尝试使用冲突的方式使用系统的情况。比如,多条查询视图同一时间更新相同的数据,或者多个大量的负载争夺系统资源。随着竞争的增加,吞吐量下降。 优化 数据库优化可以影响到整个系统的性能。在执行SQL制定、数据库配置参数、表设计、数据分布等操作时,启用数据库查询优化器打造最有效的执行计划。
  • 调优范围确定 性能调优主要通过查看数据库节点的CPU、内存、I/O和网络这些硬件资源的使用情况,确认这些资源是否已被充分利用,是否存在瓶颈点,然后针对性调优。 如果某个资源已达瓶颈,则: 检查关键的操作系统参数和数据库参数是否合理设置。 通过查询最耗时的SQL语句、跑不出来的SQL语句,找出耗资源的SQL,进行SQL调优指南。 如果所有资源均未达瓶颈,则表明性能仍有提升潜力。可以查询最耗时的SQL语句,或者跑不出来的SQL语句,进行针对性的SQL调优指南。
  • SQL调优指南 SQL调优的唯一目的是“资源利用最大化”,即CPU、内存、磁盘IO三种资源利用最大化。所有调优手段都是围绕资源使用开展的。所谓资源利用最大化是指SQL语句尽量高效,节省资源开销,以最小的代价实现最大的效益。比如做典型点查询的时候,可以用seqscan+filter(即读取每一条元组和点查询条件进行匹配)实现,也可以通过indexscan实现,显然indexscan可以以更小的代价实现相同的效果。 根据硬件资源和客户的业务特征确定合理的数据库部署方案和表定义是数据库在多数情况下满足性能要求的基础。下文的调优说明假设您已根据“软件安装”指引在安装过程中按照合理的数据库方案完成了安装,且已经根据“开发设计建议”的指引进行了数据库设计。 Query执行流程 SQL执行计划介绍 调优流程 更新统计信息 审视和修改表定义 典型SQL调优点 经验总结:SQL语句改写规则 SQL调优关键参数调整 使用Plan Hint进行调优 使用向量化执行引擎进行调优 父主题: 性能调优
  • 操作步骤 参考连接数据库,连接数据库。 查看阻塞的查询语句及阻塞查询的表、模式信息。 1 2 3 4 5 6 7 8 91011 SELECT w.query as waiting_query,w.pid as w_pid,w.usename as w_user,l.query as locking_query,l.pid as l_pid,l.usename as l_user,t.schemaname || '.' || t.relname as tablenamefrom pg_stat_activity w join pg_locks l1 on w.pid = l1.pidand not l1.granted join pg_locks l2 on l1.relation = l2.relationand l2.granted join pg_stat_activity l on l2.pid = l.pid join pg_stat_user_tables t on l1.relation = t.relidwhere w.waiting; 该查询返回线程ID、用户信息、查询状态,以及导致阻塞的表、模式信息。 使用如下命令结束相应的会话。其中,139834762094352为线程ID。 1 SELECT PG_TERMINATE_BACKEND(139834762094352); 显示类似如下信息,表示结束会话成功。 PG_TERMINATE_BACKEND---------------------- t(1 row) 显示类似如下信息,表示用户正在尝试结束当前会话,此时仅会重连会话,而不是结束会话。 FATAL: terminating connection due to administrator commandFATAL: terminating connection due to administrator commandThe connection to the server was lost. Attempting reset: Succeeded. gsql客户端使用PG_TERMINATE_BACKEND函数终止本会话后台线程时,客户端不会退出而是自动重连。
  • 调优手段之GUC参数 查询优化的主要目的是为查询语句选择高效的执行方式。 如下SQL语句: 12 select count(1) from customer inner join store_sales on (ss_customer_sk = c_customer_sk); 在执行customer inner join store_sales的时候,GaussDB支持Nested Loop、Merge Join和Hash Join三种不同的Join方式。优化器会根据表customer和表store_sales的统计信息估算结果集的大小以及每种join方式的执行代价,然后对比选出执行代价最小的执行计划。 正如前面所说,执行代价计算都是基于一定的模型和统计信息进行估算,当因为某些原因代价估算不能反映真实的cost的时候,我们就需要通过guc参数设置的方式让执行计划倾向更优规划。
  • 调优手段之统计信息 GaussDB优化器是典型的基于代价的优化 (Cost-Based Optimization,简称CBO)。在这种优化器模型下,数据库根据表的元组数、字段宽度、NULL记录比率、distinct值、MCV值、HB值等表的特征值,以及一定的代价计算模型,计算出每一个执行步骤的不同执行方式的输出元组数和执行代价(cost),进而选出整体执行代价最小/首元组返回代价最小的执行方式进行执行。这些特征值就是统计信息。从上面描述可以看出统计信息是查询优化的核心输入,准确的统计信息将帮助规划器选择最合适的查询规划,一般来说我们通过analyze语法收集整个表或者表的若干个字段的统计信息,周期性地运行ANALYZE,或者在对表的大部分内容做了更改之后马上运行它是个好习惯。
  • 使用分区表 分区表是把逻辑上的一张表根据某种方案分成几张物理块进行存储。这张逻辑上的表称之为分区表,物理块称之为分区。分区表是一张逻辑表,不存储数据,数据实际是存储在分区上的。分区表和普通表相比具有以下优点: 改善查询性能:对分区对象的查询可以仅搜索自己关心的分区,提高检索效率。 增强可用性:如果分区表的某个分区出现故障,表在其他分区的数据仍然可用。 方便维护:如果分区表的某个分区出现故障,需要修复数据,只修复该分区即可。 GaussDB支持的分区表为一级分区表和二级分区表,其中一级分区表包括范围分区表、间隔分区表、列表分区表、哈希分区表四种,二级分区表包括范围分区、列表分区、哈希分区两两组合的九种。 范围分区表:将数据基于范围映射到每一个分区。这个范围是由创建分区表时指定的分区键决定的。分区键经常采用日期,例如将销售数据按照月份进行分区。 间隔分区表:是一种特殊的范围分区表,相比范围分区表,新增间隔值定义,当插入记录找不到匹配的分区时,可以根据间隔值自动创建分区。 列表分区表:将数据中包含的键值分别存储再不同的分区中,依次将数据映射到每一个分区,分区中包含的键值由创建分区表时指定。 哈希分区表:将数据根据内部哈希算法依次映射到每一个分区中,包含的分区个数由创建分区表时指定。 二级分区表:由范围分区、列表分区、哈希分区任意组合得到的分区表,其一级分区和二级分区均可以使用前面三种定义方式。 父主题: 审视和修改表定义
  • 执行计划显示信息 除了设置不同的执行计划显示格式外,还可以通过不同的EXPLAIN用法,显示不同详细程度的执行计划信息。常见有如下几种,关于更多用法请参见EXPLAIN语法说明。 EXPLAIN statement: 只生成执行计划,不实际执行。其中statement代表SQL语句。 EXPLAIN ANALYZE statement:生成执行计划,进行执行,并显示执行的概要信息。显示中加入了实际的运行时间统计,包括在每个规划节点内部花掉的总时间(以毫秒计)和它实际返回的行数。 EXPLAIN PERFORMANCE statement:生成执行计划,进行执行,并显示执行期间的全部信息。 为了测量运行时在执行计划中每个节点的开销,EXPLAIN ANALYZE或EXPLAIN PERFORMANCE会在当前查询执行上增加性能分析的开销。在一个查询上运行EXPLAIN ANALYZE或EXPLAIN PERFORMANCE有时会比普通查询明显的花费更多的时间。超支的数量依赖于查询的本质和使用的平台。 因此,当定位SQL运行慢问题时,如果SQL长时间运行未结束,建议通过EXPLAIN命令查看执行计划,进行初步定位。如果SQL可以运行出来,则推荐使用EXPLAIN ANALYZE或EXPLAIN PERFORMANCE查看执行计划及其实际的运行信息,以便更精准地定位问题原因。
  • 操作步骤 收集SQL中涉及到的所有表的统计信息。在数据库中,统计信息是规划器生成计划的源数据。没有收集统计信息或者统计信息陈旧往往会造成执行计划严重劣化,从而导致性能问题。从经验数据来看,10%左右性能问题是因为没有收集统计信息。具体请参见更新统计信息。 通过查看执行计划来查找原因。如果SQL长时间运行未结束,通过EXPLAIN命令查看执行计划,进行初步定位。如果SQL可以运行出来,则推荐使用EXPLAIN ANALYZE或EXPLAIN PERFORMANCE查看执行计划及实际运行情况,以便更精准地定位问题原因。有关执行计划的详细介绍请参见SQL执行计划介绍。 审视和修改表定义。 针对EXPLAIN或EXPLAIN PERFORMANCE信息,定位SQL慢的具体原因以及改进措施,具体参见典型SQL调优点。 通常情况下,有些SQL语句可以通过查询重写转换成等价的,或特定场景下等价的语句。重写后的语句比原语句更简单,且可以简化某些执行步骤达到提升性能的目的。查询重写方法在各个数据库中基本是通用的。经验总结:SQL语句改写规则介绍了几种常用的通过改写SQL进行调优的方法。
共100000条