0
点赞
收藏
分享

微信扫一扫

循序渐进丨10000字讲透 MogDB 数据库开发规范

陆公子521 2024-11-13 阅读 4

1. 适用范围

参考 MogDB 5.0.6版本

2. 驱动使用规范

2.1. JDBC驱动

MogDB 提供三种JDBC jar包:

  • mogdb-jdbc-5.0.0.7.mg.jar
  • mogdb-jdbc-5.0.0.7.og.jar
  • mogdb-jdbc-5.0.0.7.pg.jar

三种jar包功能一致,仅仅是为了解决和 PostgreSQL 之间的JDBC驱动包名冲突。例如使用mogdb-jdbc-5.0.0.7.og.jar驱动包,引入驱动类名字为org.opengauss.Driver。不同的连接有不同的兼容性行为需求,比如有的连接期望隐去numeric小数点后末尾的0,有的期望不隐去,就可以在连接上设置 behavior_compat_options = hide_tailing_zero,示例如下:

jdbc:opengauss://127.0.0.1:5436/test?options=-c behavior_compat_options=hide_tailing_zero,display_leading_zero,bpchar_coerce_compat,allow_like_indexable

常用参数说明:

  • fetchsize
    原理:fetchsize在设置为n后,数据库服务器端在执行查询后,调用者在执行resultset.next()的时候,JDBC会先与服务器端进行通信,取n条数据到JDBC的客户端中,然后返回第一条给调用者,当调用者取到第n+1条数据的时候,会再次到数据库服务端去拿数据。
    作用:避免了数据库一下把所有结果全部传输到客户端来,将客户端的内存资源撑爆掉。
    建议:建议根据自身的业务查询数据数量和客户端机器内存情况来配置此参数,设置fetchsize时要关闭自动提交(autocommit=false),否则会导致fetchsize无法生效。
  • batchMode
    作用:用于确定是否使用batch模式连接。默认值为on,开启后可以提升批量更新的性能,同时批量更新的返回值会发生改变,例如,批量插入三条数据,在开启时返回值为[3,0,0],在关闭后返回值为[1,1,1]。
    建议:如果本身业务框架(例如hibernate)在批量更新时会检测返回值,可以通过调整此参数来解决。
  • autosave
    作用:共有3种: “always”, “never”, “conservative”。如果查询失败,指定驱动程序应该执行的操作。在autosave=always模式下,JDBC驱动程序在每次查询之前设置一个保存点,并在失败时回滚到该保存点。在autosave=never模式(默认)下,无保存点。在autosave=conservative模式下,每次查询都会设置保存点,但是只会在"statement XXX无效"等情况下回滚并重试。
    建议:根据业务实际情况设置,目前一些客户业务场景有使用该特性。
  • setAutocommit
    作用:值为true时,执行每个语句都会自动开启事务,在执行结束后自动提交事务,即每个语句都是一个事务。值为false时,会自动开启一个事务,事务需要通过执行SQL手动提交。当该参数为true时默认值,与MogDB 5.0.6 兼容特性compat_oracle_txn_control存在冲突,需要连接串中关闭该兼容特性。
    建议:根据业务特征进行调整,如果基于性能或者其它方面考虑,需要关闭autocommit时,需要应用程序自己来保证事务的提交。例如,在指定的业务SQL执行完之后做显式提交,特别是客户端退出之前务必保证所有的事务已经提交。

2.2. ODBC驱动

ODBC(Open Database Connectivity,开放数据库互连)是由Microsoft公司基于X/OPEN CLI提出的用于访问数据库的应用程序编程接口。应用程序通过ODBC提供的API与数据库进行交互,增强了应用程序的可移植性、扩展性和可维护性。

ODBC驱动中通过SQLSetConnectAttr 函数设置事务行为,默认为SQL_AUTOCOMMIT_ON自动提交,如果设置OFF ret = SQLSetConnectAttr(hdbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, 0);对于ODBC驱动发起的任意SQL语句需要及时进行提交或者回滚。

如ODBC驱动中查询语句不进行及时提交,数据库中会存在事务,项目中建议使用compat_oracle_txn_control特性:select不开启事务。

odbc.ini文件配置参数:

循序渐进丨10000字讲透 MogDB 数据库开发规范_字段


循序渐进丨10000字讲透 MogDB 数据库开发规范_字段_02

2.3. PSYCOPG2驱动

Psycopg是一种用于执行SQL语句的PythonAPI,可以为 MogDB 数据库提供统一访问接口,应用程序可基于它进行数据操作。Psycopg2是对libpq的封装,主要使用C语言实现,既高效又安全。它具有客户端游标和服务器端游标、异步通信和通知、支持“COPY TO/COPY FROM”功能。

默认Psycopg在执行第一个命令之前打开一个事务:如果不调用commit(),任何数据操作的效果都将丢失。

该驱动默认autocommit 为关闭,对于发起的任意SQL都需要调用commit进行提交。

建立连接时,可通过 options 参数开启 compat_oracle_txn_control 特性:

conn = psycopg2.connect(..., optinotallow="-c behavior_compat_optinotallow=compat_oracle_txn_control")

  • 自动 savepoint

前提:connect.autocommit=False 关闭自动提交时,该特性才会生效

当不开启自动提交时,用户需显式的调用 connect.commit() 来提交,在提交之前的所有sql都将视为同一个事务,如果执行某个 SQL 报错时,再调用 rollback 时会将所有未提交的操作都回滚掉。

用户可以通过设置 connect.autosavepoint=True (默认为 False) 来启用自动创建 savepoint 功能。当启用该参数后,在未提交前每执行一个 SQL,都会自动创建一个 savepoint(一个事务中仅保留一个savepoint),如果执行 SQL 报错时,驱动会自动回滚当前报错的SQL,而不会回滚所有操作,用户捕获到SQL执行报错后,仍旧可以调用 connect.commit() 来提交报错SQL之前的所有的操作。

以下为四种设置方式任选一种即可:

在字符串中设置 autosavepoint 参数时,‘’, ‘0’, ‘off’, ‘false’, ‘n’, ‘no’ 这些值会被视为 False, 其他值均会视为 True

  1. 第一种:在 URL 形式的DSN中设置参数

conn = psycopg2.connect("postgres://user:password@ip1:port,ip2:port:.../dbname?autosavepoint=true")

  1. 第二种:在键值对形式的DSN中添加

conn = psycopg2.connect("user=username host=ip dbname=xxx autosavepoint=true")

  1. 第三种:连接时传递参数

conn = psycopg2.connect(user=username,host=ip, dbname=xxx, autosavepoint=True)

  1. 第四种:创建连接后设置

conn = psycopg2.connect(user=username,host=ip, dbname=xxx)
conn.autosavepoint = True

  • 新增占位符
    扩展了原有的占位符,默认使用 % 作为占位符,现在可以使用美元符号 $ 作为占位符。

示例:

import psycopg2 as pg
conn = pg.connect(database = 'testdb' ...)
cursor = conn.cursor()


cursor.execute("delete from map")


for i in range(0, 10):
    cursor.execute("insert into map values($1, $2)", [i, i + 1], place_holder = '$')


cursor.execute("select * from map")
print(cursor.fetchall())


cursor.execute("select key from map where key > $2 and value > $1", [3, 5], place_holder = '$')
print(cursor.fetchall())


for i in range(0, 10):
    if i % 2 == 1:
        cursor.execute("delete from map where key = $1 and value = $2", [i, i + 1], place_holder = '$')


cursor.execute("select * from map")
print(cursor.fetchall())

特别的,新增的占位符参数 place_holder 可以作为一个参数传入类似 execute 这样的函数,默认值是百分号。也就是说如果要使用美元符号作为占位符,那么必须指定参数 place_holder = ‘$’。

要强调的一点是,占位符同一时刻只有一个可以生效,例如当设定 place_holder = ‘%’ 时,所有的美元符号都将作为普通字符而不是转义字符处理。反之亦然。

当使用 % 作为占位符是,SQL 中如果要包含 % 字符,需使用 %% 转义,同样的,当 place_holder = ‘$’ 时要输出一个美元符号,就需要使用 $$ 进行转义。

  • extras 模块新增批处理函数
    extras.execute_prepared_batch
    extras.execute_params_batch

使用示例:

相比提交单条数据,批量提交时需使用二维数据作为数据参数

注意:由于这两个批处理接口都是提交给服务端进行解析和绑定变量,所以SQL 中的占位符需要是数据库中支持的格式,需使用 $1, $2 这样的格式进行指定。

execute_prepared_batch 
# 创建一个 prepare statement: prep
cur.execute("prepare prep as insert into test values ($1, $2)")
# 执行 prep 这个函数,将批量参数传递进去
execute_prepared_batch(cur, "prep", [[1, '1'], [2, '2'], [3, '3']])
Copy
execute_params_batch
# 无需提前创建  prepare statement, 直接将 sql 和数据一起提交
execute_params_batch(cur, "insert into t3 values ($1, $2)", [[1, '1'], [2, '2'], [3, '3']])

3. 数据库使用规范

3.1. 限制

MogDB 具有如下使用限制:

循序渐进丨10000字讲透 MogDB 数据库开发规范_SQL_03

3.2. ACID区别

  • 原子性(Atomicity)
    事务中的写操作是一次性执行的,不能分解成更小的部分。如果在执行事务时出现故障,同一个事务中的写入将被回滚。应用程序可以安全地重试同一事务,而不会产生任何副作用。
    区别:MogDB 原子性一个事务中如果有失败的SQL那么整个事务将回滚,oracle 中失败的SQL不影响正常的SQL最终可进行提交。
  • 隔离(Isolation)
    当有来自两个不同事务的并发写入时,这两个事务是相互隔离的。最严格的隔离是 “序列化”(serializability),即每个事务运行时都像数据库中唯一运行的事务一样。

关于原子性目前某交易所现场采样驱动savepoint 方式进行处理。

区别:MogDB 内核针对delete + insert 并发下mvcc。

说明:简而言之就是如果事务A delete了对应行,事务B提交时会直接忽略对应行,而不是重新获取当前读状态;而如果事务A update了对应行,则此时事务B才会去重新获取当前读状态并基于该状态继续做更新操作。(for update 也是一样情况)

3.3. 重连机制

MogDB 目前不具备类似 Oracle 的Transparent Application Continuity能力(即故障重连和事务回放能力)。对于VIP场景下的主备切换,MogDB 数据库应用目前需要在客户端或者中间件增加故障检测和重连机制。

建议jdbc psycopg2 在配置数据库连接时设置参数connection_timeout=10为保证RTO时间连接重试次数不低于6次,ODBC驱动无需设置默认即可。

4. 字符集转码规范

4.1. 转码规则

MogDB 支持在服务器和客户端之间的自动字符集转换。转换信息存储在系统表pg_conversion中,MogDB 自带一些预定义的转换。

4.2. 非法字符集入库

在服务端和客户端编码一致时,通过驱动PBE等方式写入的数据,如果出现非法字符,会被默认修改为 ‘?’ 存储,且没有提示信息,导致实际写入的数据与期望的不匹配。

session 级别 bool 类型变量 emit_illegal_bind_chars,可控制是否入库,默认为 off,兼容旧版本行为。

循序渐进丨10000字讲透 MogDB 数据库开发规范_数据_04

5. 命名规范

5.1. 对象命名统一规范

数据库对象,如database、schema、table、column、 view、 index、constraint、sequence、 function、trigger等命名统一标准如下:

  • MogDB 存储方式默认为小写;
  • 使用小写字母、下划线的组合表以;
  • 命名尽量采用富有意义英文词汇;
  • 不使用双引号即"包围,除非必须包含大写字母或空格等特殊字符;
  • 长度不能超过63个字符;
  • 不建议以PG、GS、DBA开头(避免与系统DB object混淆),不建议以数字开头;
  • 禁止使用保留字,保留关键字参考官方文档。

5.2. 临时及备份对象命名

临时或备份的数据库对象名,如table,建议添加日期,如 test.t_trade_record_2024_12_08 (其中test 为schema,trade_record为表名,2024_12_08为备份日期)。

5.3. 变量命名

命名应该使用英文单词,避免使用拼音,特别不应该使用拼音简写。命名不允许使用中文或者特殊字符。

5.4. 分区表命名

  • 分区表的表名遵循普通表的正常命名规则。
  • 按时间范围分区(每月一个分区),分区名字为PART_YYYYMM。
    举例:PART_202404、PART_202405

6. 设计规范

6.1. table设计

  • 设计表结构时,应该规划好,避免经常添加字段,或者修改字段类型或长度;
  • 必须为表添加注释信息,表名与注释信息相匹配;
  • 禁止使用unlogged关键字新建表,默认创建非压缩行表;
  • 作为表间连接关系的字段,数据类型必须保持严格一致,避免索引无法正常使用;
  • 不建议CHAR或其他字符类型来存储日期值,如果使用,则不能在此字段上做运算,需要在数据规范中严格定义;
  • 字段必须添加能够清楚表示其含义的注释,状态类字段的注释中必须明确列出各状态值的说明;
  • 表结构中字段定义的数据类型与应用程序中的定义保持一致,表之间字段校对规则一致,避免报错或无法使用索引的情况发生;说明:比如A表user_id字段数据类型定义为char,但是SQL语句查询为 where user_id=1234。

6.2. partition table设计

  • MogDB 数据库支持的分区表为范围分区表;
  • 分区表的分区个数最多支持1048575个;
  • 对于数据量比较大的表,根据表数据的属性进行分区,以得到较好的性能;
  • 普通表若要转成分区表,需要新建分区表,然后把普通表中的数据导入到新建的分区表中。因此在初始设计表时,请根据业务提前规划是否使用分区表;
  • 建议有定期历史数据删除需求的业务,表按时间分区,删除时不要使用DELETE操作,而是DROP或者TRUNCATE对应的分区;
  • 在分区表中不建议使用全局索引,因为做分区维护操作时可能会导致全局索引失效,造成难以维护。

6.3. column设计

  • 建议可以采用数值类型的场合,则避免采用字符类型;
  • 建议可以采用varchar(N) 就避免采用char(N), 可以采用varchar(N) 就避免采用text、varchar;
  • 字符类型建议只选用这些char(N)、varchar(N)及text;
  • MogDB数据库默认兼容Oracle,not null 约束不允许传入空字符串,空字符串默认会转换为null;
  • 建议使用NUMERIC(precision、scale)来存储货币金额和其它要求精确计算的数值,而不建议使用real、double precision。

6.4. 序列设计

  • 建议生产环境使用Large Sequence;
  • Sequence默认最大值为2^63-1,如果使用了Large标识则最大值可以支持到2^127-1;
  • 不建议定义Sequence cache,因为定义cache后不能保证序列的连续性,MogDB 目前不支持全局共享cache。

6.5. constraint设计

6.5.1.主键约束

  • 每个table必须包含主键;
  • 建议主键的一步到位的写法:id serial primary key 或id bigserial primary key;
  • 建议内容系统中size较大的table主键的等效写法如下,便于后续维护:

create table test(id serial not null );
create unique index CONCURRENTLY ON test (id);

6.5.2.唯一约束

除主键外,需存在唯一性约束的,可通过创建以“uk_”为前缀的唯一索引实现。

6.5.3.外键约束

禁止使用外键约束

6.5.4.非空列

所有非空列须在建表之初建议明确标识“NOT NULL”。

6.5.5.检查约束

对于字段有检查性约束,一般要求指定check规则。例如:性别、状态等字段。

6.6. index设计

  • 注意 MogDB 同一列支持创建多个重复索引,创建之前务必先查看是否已存在索引;
  • MogDB 提供的index类型: 行存表支持的索引类型:btree(行存表缺省值)、gin、gist。列存表支持的索引类型:Psort(列存表缺省值)、btree、gin;
  • 建议create 或 drop index 时,加 CONCURRENTLY参数,这是个好习惯,达到与写入数据并发的效果,列存表、分区表和临时表不支持CONCURRENTLY方式创建索引;
  • 建议对于频繁update、delete的包含于index定义中的column的table, 用create index CONCURRENTLY,drop index CONCURRENTLY的方式进行维护其对应index;
  • 建议对where中带多个字段and条件的高频query,参考数据分布情况,建多个字段的联合index;
  • 每个表的index数量建议不能超过5个以上;
  • 频繁DML(写次数明显超过读次数)的表,不要建立太多的索引;
  • 无用的索引以及重复索引应删除,避免对执行计划及数据库性能造成负面影响;
  • 复合索引的建立需要进行仔细分析:
  • 正确选择复合索引中的第一个字段,一般是选择性较好的且在where子句中常用的字段上;
  • 复合索引的几个字段是否经常同时以AND方式出现在where子句中?单字段查询是否极少甚至没有?如果是,则可以建立复合索引;否则考虑单字段索引;
  • 如果复合索引中包含的字段经常单独出现在where子句中,则分解为多个单字段索引;
  • 如果既有单字段索引,又有以这个字段为首列的复合索引,一般可考虑删除单字段索引;
  • 复合索引第一个字段一般不使用时间字段,因为时间字段多用于范围扫描,而前面的字段使用范围扫描后,后续字段无法用于索引过滤;
  • 复合索引字段个数不能超过4个。

6.7. view设计

  • 尽量使用简单视图,尽可能少使用复杂视图;
    简单视图定义:数据来自单个表,且无分组(DISTINCT/GROUP BY)、无函数。
    复杂视图定义:数据来自多个表,或有分组,有函数,表的个数不能超过3个;
  • 尽量不要使用嵌套视图,如果必须使用,不能超过2层嵌套;
  • 如需要对视图依赖的对象进行DDL操作,需要开启view_independent 参数。

6.8. 排序设计

由于 MogDB 存储的数据顺序如发生DML顺序会发生变化,如业务上需要有序展示务必在代码中添加ororder by进行手工排序控制。

7. 语法规范

7.1. SQL 开发注意事项

在关系型数据库中,索引主要有两种作用。其一是用来确保数据唯一性,我们通常创建主键或者唯一索引来确保数据唯一性;其二是用来快速查找表中少量数据。

数据库在执行查询时会使用成本估算(cost)来确定是走全表扫描还是走索引扫描。成本估算(cost)不一定100%可靠,因为它是估算值,它并不是真实值。因此,开发人员、DBA要掌握什么时候走索引,什么时候走全表扫描。

查询表中大量数据应该走全表扫描,查询表中少量数据应该走索引扫描。假设一个表有100w行记录,要查询表中90w行数据,这种情况就应该走全表扫描。要查询表中几十行,几百行,几千行,应该走索引扫描。一般来说查询表中5%以内的数据走索引性能更好,查询表中5%以上的数据走全表扫描性能更好。

  • 使用NOT EXISTS或者LEFT JOIN代替NOT IN。NOT IN子查询执行计划会固定走Nested Loop Anti Join,NOT IN子查询会被Materialize生成临时表,主表有多少行,NOT IN 生成的临时表就会被扫描多少次。如果主表数据量特别大,这个时候就会出现严重性能问题(SQL有可能跑不出结果);
  • 如果主表数据量很大,子表数据量很小,不建议使用标量子查询,应该将标量子查询改写为LEFT JOIN的写法;
  • 使用with as时要注意子查询的用途,如果不想子查询生成临时表,可以使用with tab as not materialized语法,如果想让子查询生成临时表,可以使用with tab as materialized语法;
  • 在FROM后面的子查询叫内联视图。如果内联视图中有GROUP BY,且内联视图与其他表有关联条件,这个时候要特别注意。如果内联视图中的表是大表,与内联视图关联的表是小表,可以考虑改写SQL为先关联,再GROUP BY。

7.2. HINT 使用

Plan Hint为用户提供了直接影响执行计划生成的手段,用户可以通过指定join顺序,join、scan方法,指定结果行数,等多个手段来进行执行计划的调优,以提升查询的性能。在优化SQL时可采样HINT 方式进行执行计划固定。

常用HINT 如下:

  • 强制走全表扫描:

/*+ tablescan(表名/别名) */
/*+ no indexscan(表名/别名) */
select /*+ tablescan(e) */ * from emp e where empno=7369;
select /*+ no indexscan(emp) */ * from emp where empno=7369;

  • 强制走索引扫描+回表:

/*+ indexscan(表名/别名) */
/*+ indexscan(表名/别名 索引名) */
/*+ no tablescan(表名/别名) */
select /*+ indexscan(t) */ * from test01 t where object_id >100;
select /*+ indexscan(t idx_test01_objectid) */ * from test01 t where object_id >100;
select /*+ no tablescan(t) */ * from test01 t where object_id >100;

注意:
1.如果where条件列没有合适的索引,会提示unused hint;
2.索引名可以省略,如果发现强制走索引,索引没走对,加上索引名。

  • 强制走仅索引扫描:

/*+ indexonlyscan(表名/别名) */
sselect /*+ indexonlyscan(t) */ object_id from test01 t where object_id >100;

注意:
1.如果where条件列没有合适的索引,会提示unused hint;
2.查询列是*,没有被索引完全包含,会提示WARNING: unused hint: IndexOnlyScan(t)。

  • 强制走嵌套循环:

/*+ nestloop(e d) */
select /*+ nestloop(e d) leading((e d)) */ * from emp e ,dept d where e.deptno =d.deptno;

nestloop(e d)只是提示e d走嵌套循环,并没有指定谁作为驱动表,leading控制驱动表顺序的时候要加双括号(()),如果是走NL,leading((e d))表示e nl d。

  • 强制走哈希连接:

/*+ hashjoin(e d) */
select /*+ hashjoin(e d) leading((e d)) */ * from emp e ,dept d where e.deptno =d.deptno;

hashjoin(e d)只是提示e d走哈希连接,并没有指定谁作为驱动表,leading控制哪个表作为驱动表的时候要加双括号(()),如果是走HASH,leading((e d))表示d hash e。

内联视图中如果没有GROUP BY、UNION、ROWNUM、树形查询、分析函数等,那么它就会被展开,这个时候对内联视图的别名添加HINT无效:

mogdb=> explain select /*+ nestloop(d e) */ * from (select * from emp) e, dept d where e.deptno = d.deptno;
WARNING:  Error hint: NestLoop(d e), relation name "e" is not found.
                            QUERY PLAN                             
-------------------------------------------------------------------
 Hash Join  (cost=1.09..2.41 rows=14 width=66)
   Hash Cond: (emp.deptno = d.deptno)
   ->  Seq Scan on emp  (cost=0.00..1.14 rows=14 width=45)
   ->  Hash  (cost=1.04..1.04 rows=4 width=21)
         ->  Seq Scan on dept d  (cost=0.00..1.04 rows=4 width=21)
(5 rows)

执行计划中提示WARNING: Error hint: NestLoop(d e), relation name “e” is not found,说明HINT无效。

内联视图中有GROUP BY:

mogdb=> explain select /*+ nestloop(e d) */ * from (select sum(sal),deptno from emp group by deptno) e, dept d where e.deptno = d.deptno; 
                              QUERY PLAN                               
-----------------------------------------------------------------------
 Nested Loop  (cost=1.21..2.50 rows=3 width=58)
   Join Filter: (emp.deptno = d.deptno)
   ->  Seq Scan on dept d  (cost=0.00..1.04 rows=4 width=21)
   ->  Materialize  (cost=1.21..1.28 rows=3 width=37)
         ->  HashAggregate  (cost=1.21..1.24 rows=3 width=42)
               Group By Key: emp.deptno
               ->  Seq Scan on emp  (cost=0.00..1.14 rows=14 width=10)
(7 rows)

执行计划走了NL,说明HINT生效了。

  • 指定返回行数

/*+ rows(e #100) */     指定e返回100行
/*+ rows(e +100) */     指定e在原来的预估上+100行
/*+ rows(e -100) */      指定e在原来的预估上-100行
/*+ rows(e *100) */      指定e在原来的预估上*100行
/*+ rows(e d #100) */    指定e和d关联之后返回100行

  • 并行
    当SQL语句中有多个大表关联,此时SQL可能要跑很久,可以开启并行加速查询性能,HINT如下:

/*+ set(query_dop 4) */

  • 向量化
    如果两个表关联方式是HASH JOIN,且关联之后有GROUP BY,可以开启向量化提升查询性能,HINT如下:

/*+ set(try_vector_engine_strategy force) */

  • 禁止子查询生成临时表

/*+ set(enable_material off) */

  • 禁止位图扫描

/*+ set(enable_bitmapscan off) */

  • 禁止排序合并连接

/*+ set(enable_mergejoin off) */

7.3. 关于NULL

  • A兼容性下,数据库将空字符串作为NULL处理,
  • 说明:
  • NULL 的判断:IS NULL ,IS NOT NULL。
  • 注意boolean 类型取值 true,false,NULL。
  • 小心NOT IN 集合中带有NULL元素。

mydb=# SELECT * FROM (VALUES(1),(2)) v(a) ;  a 
---
 1
 2
(2 rows)
mydb=# select 1 NOT IN (1,NULL);  
?column?
---------
f
(1 row)
mydb=# select 2 NOT IN (1,NULL);  
?column?
---------
(1 row)
mydb=# SELECT * FROM (VALUES(1),(2)) v(a) WHERE a NOT IN (1, NULL);  a 
---
(0 rows)

  • 建议:使用count(1) 或count(*) 来统计行数,而不建议使用count(col) 来统计行数,因为NULL值不会计入。
  • 规则:count(多列列名)时,多列列名必须使用括号,例如count( (col1,col2,col3) )。
  • 注意:
  • 多列的count,即使所有列都为NULL,该行也被计数,所以效果与count(*) 一致。
  • count(distinct col) 计算某列的非NULL不重复数量,NULL不被计数
  • count(distinct (col1,col2,…) ) 计算多列的唯一值时,NULL会被计数,同时NULL与NULL会被认为是相同的。
  • NULL 的count与sum

select count(1), count(a), sum(a)  from (SELECT * FROM (VALUES (NULL), (2) ) v(a)) as foo where a is NULL;  
count | count | sum 
-------+-------+-----
     1 |     0 |
(1 row)

  • 判断两个值是否相同(将NULL视为相同的值)

select null is distinct from null;  
?column? 
--------- 
f 
(1 row)


select null is distinct from 1;  
?column? 
--------- 
t 
(1 row)
select null is not distinct from null;  
?column? 
--------- 
t 
(1 row)


select null is not distinct from 1;  
?column? 
--------- 
f
(1 row)

7.4. 关于索引失效

在书写SQL语句时经常会在查询中使用函数及表达式,建议尽量不要在条件列上使用函数及表达式。在条件列上使用函数或者表达式的时候会导致使用不上该条件列上的索引,从而影响SQL的查询效率。尽量把函数或者表达式用在条件值上,避免使用在条件列上。示例:

select name from tab where id+100>1000;

可以改写为如下形式:

select name from tab where id>1000-100;

查询语句中尽量不要使用左模糊查询。示例:

select id from tab where name like ‘%ly’;

查询中尽量不要使用负向查询,如not in/like,示例:

select id from tab where name not in (‘ly’,’ty’);

7.5. 确保使用到所有变量和参数

声明变量也会产生一定的系统开销,并会显得代码不够严谨,在编译时未使用的变量会有告警,需修改以确保没有任何告警。

8. 操作规范

8.1. DDL操作

  • MogDB DDL 如果在事务块中可以进行回滚操作,在事务块中的DDL必须显示进行提交。
  • DB object 尤其是COLUMN 加COMMENT,便于后续新人了解业务及维护
  • 以不阻塞DML的方式创建索引(加ShareUpdateExclusiveLock锁)。创建索引时,一般会阻塞其他语句对该索引所依赖表的访问。使用CONCURRENTLY方式可以实现创建过程中不阻塞DML。普通CREATE INDEX命令可以在事务内执行,但是CREATE INDEX CONCURRENTLY不可以在事务内执行。
  • 以不阻塞DML的方式重建索引,普通REINDEX命令可以在事务内执行,但是REINDEX CONCURRENTLY不可以在事务内执行。
  • 向大表中add column时,将 alter table t add column col datatype not null default xxx;分解,避免填充default值导致的过长时间锁表

alter table t add column col datatype ;
alter table t alter column col set default xxx;
update table t  set column= DEFAULT where id in ( select id from t where column is null limit
1000 ) ; 
alter table t alter column col set not null;

8.2. DML操作

建议清空表时使用truncate,不建议使用delete。truncate需要持有表级别X锁,如果有带有事务属性的select 没有提交也会阻塞truncate操作。

8.3. DQL操作

  • 建议排序以最终结果为排序依据,而不以中间态进行排序;
  • 非必须时禁止使用select *,只取所需字段,以减少包括不限于网络带宽消耗,避免表结构变更对程序的影响(比如某些prepare query);
  • 复杂的统计查询可以尝试窗口函数 Window Functions;
  • 避免关联字段数据类型不一致,禁止使用隐式类型转换;
  • 不同字段的or语句使用union代替;
  • 不要将空的变量值直接与比较运算符(符号)比较。如果变量可能为空,应使用 IS NULL 或 IS NOT NULL 进行比较。

8.4. DCL操作

  • MogDB 数据库中用户和SCHEMA是分离的,针对SCEHMA有单独的权限管控。
  • 用户a在将对象操作权限授予给用户b时,也需将对象所在SCHEMA的usage权限同时授予给用户b。SCHEMA的usage权限只需要授予一次即可。

grant select on s1.t1 to b;
grant usage on schema s1 to b;

  • 用户a需要把将来创建的对象的权限授予给用户b时,通过提前设置用户a的default privileges,并将对象所在SCHEMA的usage权限授予给用户b。可用于只读用户一次性授权。

alter default privileges for user a in schema s1 grant select on tables to b;
grant  usage on schema s1 to b;

  • 用户a需要将自身所有权限授予给用户b时,可以将自身作为role赋予给用户b。可用于同权用户一次性授权。

grant a to b;

8.5. 数据导入

  • 建议大批量的数据入库时,使用copy,不建议使用insert,以提高写入速度,目前psycopg2 、jdbc 均有copy 接口;
  • 导入数据前需要先删除相关索引,导入完成后重建,提高数据导入速度。

8.6. 事务操作

  • 如驱动使用手工提交事务(autocommit=off)所有发起的语句必须手工提交包括select查询语句;
  • 事务中的SQL逻辑尽可能的简单,让每个事务的粒度尽可能小,尽量lock少的资源,避免lock 、deadlock的产生,事务执行完及时提交;
  • 执行CRAETE、DROP、ALTER等DDL操作,尤其多条,不要显式的开transaction,因为加lock的mode非常高,极易产生deadlock;
  • state 为 idle in transaction 的连接,如果出现在主库,会lock住相应的资源,可导致后续产生lock,甚至deadlock;出现在备库可导致同步延迟。目前超过1800s 空闲事务数据库会自动中断;
  • MogDB 中新增特性SELECT自动提交事务,参数为compat_oracle_txn_control。

8.7. 其他

  • 建议在需要使用explain analyze 查看实际真正执行计划与时间时,该操作会去执行SQL,如果是写入 query,强烈建议先开启事务, 然后回滚。
  • 对于频繁更新或者删除,膨胀率较高的表,应找窗口期执行表重组,降低高水位

9. 兼容性

9.1. Oracle 兼容参数

MogDB 创建数据库使用A兼容模式,现场使用兼容参数说明如下:

  • display_leading_zero:设置此配置项时,对于-1~0和0~1之间的小数,显示小数点前的0。不设置此配置项时,对于-1~0和0~1之间的小数,不显示小数点前的0。
  • compat_oracle_txn_control:Oracle 的事务状态控制与 MogDB 存在差异,Oracle 对于只读命令不启动事务,对于写命令隐式启动事务,显式结束事务。MogDB 的事务状态控制方式为隐式启动的事务隐式结束,显式启动的事务显式结束。对于读写等任何命令均需要在事务语义下执行。当前驱动在非自动提交模式下,会显式地给内核发Begin,事务需要用户显式发Commit才能结束。这样会造成一个问题:一个表之前被一个只读连接访问过,则该连接就会一直存在一个Open状态的事务,该事务持有的资源不会被释放,从而阻塞truncate业务的执行。本功能实现兼容 Oracle 事务状态的机制,驱动在非自动提交模式下不发Begin,对于只读命令,内核自动提交,对于写命令,需要用户显式提交。(在驱动为非自动提交模式下,配置此选项开启select自动提交事务功能,autocommit on 情况下该参数不能开启);
  • char_coerce_compat:控制char(n)类型向其它变长字符串类型转换时的行为。默认情况下char(n)类型转换其它变长字符串类型时会省略尾部的空格,开启该参数后,转换时不再省略尾部的空格,并且在转换时如果char(n)类型的长度超过其它变长字符串类型时将会报错。设置此选项时,unknow类型字符串会消除多余空格。该参数仅在sql_compatibility参数的值为A时生效;
  • bpchar_coerce_compat:控制bpchar和text运算时隐式转换text为bpchar,使bpchar_col = ‘xxx’::text条件可直接运用索引或者分区裁剪,提升查询效率;
  • allow_like_indexable :允许like 走索引,解决性能问题;
  • hide_tailing_zero :numeric显示配置项。不设置此项时,numeric按照指定精度显示。设置此项时,所有输出numeric的场景均隐藏小数点后的末尾0。

9.2. 数据类型

9.2.1 数值类型

整数类型:

循序渐进丨10000字讲透 MogDB 数据库开发规范_SQL_05


任意精度型:

循序渐进丨10000字讲透 MogDB 数据库开发规范_字段_06


numeric类型支持小数点前最大131072位,小数点后最大16383位,是只在创建表时未指定numeric精度情况下可以存储的长度。

序列整型:

循序渐进丨10000字讲透 MogDB 数据库开发规范_数据_07

浮点类型:

循序渐进丨10000字讲透 MogDB 数据库开发规范_数据_08

9.2.2 字符类型

在开发使用中,MogDB 只允许使用char(n)、varchar(n)、text字符类型。

循序渐进丨10000字讲透 MogDB 数据库开发规范_字段_09

NCHAR为bpchar类型的别名,NCHAR(n)为bpchar(n)类型的别名。

NVARCHAR2、NVARCHAR 与oracle的不一样无法存储Unicode字符集。

9.2.3 时间类型

循序渐进丨10000字讲透 MogDB 数据库开发规范_数据_10

  • A兼容性下,数据类型DATE会被替换为TIMESTAMP(0) WITHOUT TIME ZONE。

Oracle 与 MogDB 时间类型对比

  • Oracle 日期时间类型
  • 循序渐进丨10000字讲透 MogDB 数据库开发规范_数据_11

  • MogDB 日期时间类型
  • 循序渐进丨10000字讲透 MogDB 数据库开发规范_数据_12

  • 对应关系及差异
  • 循序渐进丨10000字讲透 MogDB 数据库开发规范_SQL_13

9.2.4 二进制类型

循序渐进丨10000字讲透 MogDB 数据库开发规范_字段_14

除了每列的大小限制以外,每个元组的总大小也不可超过1GB-8203字节(即1073733621字节)。

9.3 关键字

数据库保留关键字,不允许自定义使用;非保留或空是指可以自定义使用,MogDB 保留字详情,请参考官网。

10. 禁用对象

禁止使用与特定数据库密切耦合的,或非行业标准的、不利于开发代码移植的对象类型。罗列如下:

  • 存储过程
  • 函数
  • 触发器
  • 同义词
  • 虚拟列

11. Kettle连接MogDB

Kettle 是一款国外开源的ETL工具,在使用该工具连接 MogDB 时请使用 MogDB Kettle Database plugin插件,在安装完kettle-database-mogdb-1.0.0.zip插件后Kettle中会有 MogDB 数据源选项。

循序渐进丨10000字讲透 MogDB 数据库开发规范_数据_15

如直接使用Kettle接口,严禁使用Generic database方式连接,MogDB 使用该方式会丢失精度,可以使用 PostgreSQL 方式然后驱动使用mogdb-jdbc-5.0.0.8.pg.jar替换原生 PostgreSQL 驱动。

关于作者

彭冲,云和恩墨技术研究院技术顾问,专注于MogDB / openGauss / PG数据库新技术、新特性及行业发展,对数据库版本新特性有丰富的实践研究。


举报

相关推荐

0 条评论