MySQL Enterprise Audit in Oracle MySQL Cloud Service

Oracle Cloud Service似乎是唯一个即使你只提交了创建RDS服务的请求,也会自动帮你创建一个虚拟主机,并且提供主机登录权限的服务提供商,也许这就是Oracle一直在宣称的IaaS+PaaS的整合。

在创建MySQL Cloud Service时,会要求提供一个SSH登录密钥,所有的Oracle云服务主机登录用户都是opc,因此在服务创建完毕,收到通知邮件之后,就可以通过以下命令登录到云主机上。

ssh -i id_rsa_oracle_cloud_enmotech opc@your_host_ip

登录到主机以后,屏幕欢迎词是个小惊喜,Oracle贴心得显示了MySQL运行状态和存储使用量的提示。
MySQL Cloud Service Host

在比较了多方的RDS之后,Oracle MySQL Cloud Service确实如一直以来宣传的那样,提供了最多的安全选件功能,如果我们用MySQL Workbench登录到数据库中,在Server Status页面可以看到:SSL连接,PAM验证,密码校验,数据库审计等多种安全功能全部是开启的。

Oracle RDS security

而与之相比,阿里云MySQL RDS提供的安全功能就少的可怜了。说少不合适,是一项安全功能都没开启。
Aliyun RDS security

Oracle对于数据库安全性的看重确实超越了大多数数据库提供商,而这也带来了一些小麻烦。

在创建MySQL云服务的时候,需要指定数据库root用户的密码,这个密码有比较强的安全要求,需要有大写、小写英文字母,有数字,同时还需要有特殊字符(比如#)。因此在后续创建Login Path时,在mysql_config_editor命令提示输入密码的时候,需要在密码前后加上双引号(比如”Passw#rd”),否则会一直出现拒绝访问的提示。

--opc用户没有办法直接登录MySQL,需要切换到oracle用户
[opc@mysql-cloud-mysql-1 ~]$ mysql -uroot -p
Please switch to "oracle" user to use mysql client
[opc@mysql-cloud-mysql-1 ~]$ sudo su - oracle
--使用密码直接登录是没有问题的
[oracle@mysql-cloud-mysql-1 ~]$ mysql -uroot -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1509246
Server version: 5.7.17-enterprise-commercial-advanced-log MySQL Enterprise Server - Advanced Edition (Commercial)

Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> exit
Bye
--创建Login Path,仍然输入上述一样的密码
[oracle@mysql-cloud-mysql-1 ~]$ mysql_config_editor set --host=localhost --user=root --password
Enter password:
--检查确认Login Path已经创建成功
[oracle@mysql-cloud-mysql-1 ~]$ mysql_config_editor print --login-path=client
[client]
user = root
password = *****
host = localhost
--直接登录报错
[oracle@mysql-cloud-mysql-1 ~]$ mysql
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
--在密码前后加上双引号重新创建Login Path
[oracle@mysql-cloud-mysql-1 ~]$ mysql_config_editor set --host=localhost --user=root --password
Enter password:
WARNING : 'client' path already exists and will be overwritten.
 Continue? (Press y|Y for Yes, any other key for No) : Y
--再次登录,成功
[oracle@mysql-cloud-mysql-1 ~]$ mysql
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1510011
Server version: 5.7.17-enterprise-commercial-advanced-log MySQL Enterprise Server - Advanced Edition (Commercial)

Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> status
--------------
mysql  Ver 14.14 Distrib 5.7.17, for linux-glibc2.5 (x86_64) using  EditLine wrapper

Connection id:		1510011
Current database:
Current user:		root@localhost
SSL:			Not in use
Current pager:		stdout
Using outfile:		''
Using delimiter:	;
Server version:		5.7.17-enterprise-commercial-advanced-log MySQL Enterprise Server - Advanced Edition (Commercial)
Protocol version:	10
Connection:		Localhost via UNIX socket
Server characterset:	utf8mb4
Db     characterset:	utf8mb4
Client characterset:	utf8
Conn.  characterset:	utf8
UNIX socket:		/tmp/mysql.sock
Uptime:			10 days 13 hours 24 min 56 sec

Threads: 13  Questions: 50733146  Slow queries: 0  Opens: 6203  Flush tables: 1  Open tables: 4808  Queries per second avg: 55.610
--------------

在进行Audit功能的检查之前,对于默认的mysql命令行提示只有mysql> 这样简陋的显示不能忍,要加上当前登录的用户和数据库名称。在oracle用户的.my.cnf下增加以下行。

[mysql]
prompt=\\u@\\h [\\d]>\\_

再次登录,mysql命令行的提示就比较顺眼了。

[oracle@mysql-cloud-mysql-1 ~]$ mysql
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1510385
Server version: 5.7.17-enterprise-commercial-advanced-log MySQL Enterprise Server - Advanced Edition (Commercial)

Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

root@localhost [(none)]> show databases;
+-------------------------+
| Database                |
+-------------------------+
| information_schema      |
| mem                     |
| mem__advisor_text       |
| mem__advisors           |
| mem__bean_config        |
| mem__config             |
| mem__enterprise         |
| mem__events             |
| mem__instruments        |
| mem__instruments_config |
| mem__inventory          |
| mem__quan               |
| mysql                   |
| performance_schema      |
| sys                     |
| testdb                  |
+-------------------------+
16 rows in set (0.00 sec)

root@localhost [(none)]> use testdb
Database changed
root@localhost [testdb]> show tables;
Empty set (0.00 sec)

root@localhost [testdb]>

接下来进入本文的正题,在Oracle MySQL Cloud Service中默认是如何设置Audit的?
首先先检查一下audit_log的相关参数设置。

root@localhost [testdb]> show variables like 'audit_log_%';
+-----------------------------+--------------+
| Variable_name               | Value        |
+-----------------------------+--------------+
| audit_log_buffer_size       | 1048576      |
| audit_log_connection_policy | ALL          |
| audit_log_current_session   | ON           |
| audit_log_exclude_accounts  |              |
| audit_log_file              | audit.log    |
| audit_log_filter_id         | 0            |
| audit_log_flush             | OFF          |
| audit_log_format            | NEW          |
| audit_log_include_accounts  |              |
| audit_log_policy            | ALL          |
| audit_log_rotate_on_size    | 1073741824   |
| audit_log_statement_policy  | ERRORS       |
| audit_log_strategy          | ASYNCHRONOUS |
+-----------------------------+--------------+
13 rows in set (0.01 sec)

那么audit_log_file=audit.log表示仍然使用了默认的名字,到MySQL的数据文件目录中检查一下audit.log文件的存在。

[oracle@mysql-cloud-mysql-1 mysql]$ ls -l audit*
-rw-r----- 1 oracle oracle  838348830 Apr 21 13:42 audit.log
-rw-r----- 1 oracle oracle 1073742130 Apr 14 23:20 audit.log.14922120508218349.xml

这里可以看到总共的audit日志已经有1.9GB之大,在第一个audit.log达到audit_log_rotate_on_size参数设置的大小之后,自动切换成了新的audit.log。

题外话,之所以有这么巨大的audit.log,是由于启用了MySQL Enterprise Monitor,不断地记录了Monitor进程的登录和退出,为了方便后面观察audit.log,先将Monitor停止。

[oracle@mysql-cloud-mysql-1 monitor]$ pwd
/u01/bin/enterprise/monitor
[oracle@mysql-cloud-mysql-1 monitor]$ ./mysqlmonitorctl.sh stop
Stopping tomcat service . [ OK ]

接下来进行一些常规操作,并观察audit.log文件的输出内容。

1. 用root用户登录,在audit文件中显示一条Connect类型的记录。TIMESTAMP记录了时间,USER标签记录了登录的用户,HOST标签记录了登录的机器,COMMAND_CLASS为connect。

NewImage

2. 尝试直接CTAS一张测试表,在enforce_gtid_consistency=ON时会报1786错误,这是GTID特性决定的。

root@localhost [testdb]> create table mytables as select * from information_schema.tables;
ERROR 1786 (HY000): Statement violates GTID consistency: CREATE TABLE ... SELECT.

在audit.log中也记录下了这次报错的操作,STATUS标签为错误代码1786,STATUS_CODE标签值为1表示这条SQL没有成功执行,SQLTEXT标签记录了整个SQL语句。

NewImage

3. 下面的三条语句,select,create table,insert均没有在audit.log中记录下来。

root@localhost [testdb]> select count(*) from information_schema.tables;
+----------+
| count(*) |
+----------+
|      591 |
+----------+
1 row in set (0.01 sec)

root@localhost [testdb]> create table mytables like information_schema.tables;
Query OK, 0 rows affected (0.10 sec)

root@localhost [testdb]> insert into mytables select * from information_schema.tables;
Query OK, 592 rows affected (1.94 sec)
Records: 592  Duplicates: 0  Warnings: 0

这样的行为是由audit_log_statement_policy=ERRORS参数决定的,默认只记录报错的SQL,而不会记录所有的执行语句。

4. 将audit_log_statement_policy参数修改为ALL,再执行同样的select语句。

root@localhost [testdb]> SET GLOBAL audit_log_statement_policy = ALL;
Query OK, 0 rows affected (0.00 sec)

root@localhost [testdb]> select count(*) from mytables;
+----------+
| count(*) |
+----------+
|      592 |
+----------+
1 row in set (0.00 sec)

可以发现audit.log中已经有记录了。

NewImage

结论:在Oracle MySQL Cloud Service中默认会启动MySQL Enterprise Audit组件,并且设置即记录用户登录又记录执行语句(audit_log_policy=ALL),用户登录情况则不管登录成功还是失败每次都记录(audit_log_connection_policy=ALL),而执行语句则只记录执行失败的语句(audit_log_statement_policy=ERRORS)。

How to Set MySQL Group Replication into Multi-Primary Mode

在MySQL 5.7.17版本中发布的MySQL Group Replication(后文简称为MGR)被很多人称为MySQL复制方案的正规军,可以一举取代现在的MySQL Replication,Semisynchronous replication,甚至是可以取代之前最成功的MySQL集群方案Galera。

MGR有两种模式,一种是Single-Primary,一种是Multi-Primary,单主或者多主。

在前一种模式Single-Primary中,无论集群中有多少个节点,只有一个节点允许写入,其它节点都是只读的,这个允许写入的节点被称为主节点,只有当这个主节点出现问题从集群中被踢出,才会在剩余的节点中选举出另外一个节点成为新的主节点,并且将该节点置为可写模式。

这个过程可以通过log清晰地看到。

2017-03-16T02:04:32.689278Z 0 [Note] Plugin group_replication reported: 'getstart group_id 4317e324'
2017-03-16T02:04:33.081743Z 0 [Note] Plugin group_replication reported: 'Unsetting super_read_only.'
2017-03-16T02:04:33.081756Z 28 [Note] Plugin group_replication reported: 'A new primary was elected, enabled conflict detection until the new primary applies all relay logs'

而在后一种模式Multi-Primary中,所有的节点都是主节点,都可以同时被读写,看上去这似乎更好,但是因为多主的复杂性,在功能上如果设置了多主模式,则会有一些使用的限制,比如不支持Foreign Keys with Cascading Constraints。

在多主模式下,集群中的节点退出集群,也不再会出现重新选举的动作,因为本来所有的节点都是Primary节点。

前面这些并不是本文的重点,实际上在5.7.17的官方文档中有详细地描述如何设置Single-Primary MGR的方法。
Deploying Group Replication in Single-Primary Mode

但是不确认是什么原因,却没有单独的章节来描述如何设置集群为Multi-Primary模式。只是在最后语焉不详地提及了一句:Multi-primary mode groups (members all configured with group_replication_single_primary_mode=OFF) 让读者可以知道跟group_replication_single_primary_mode参数有关。虽然确实也就是跟这个参数有关,但是文档写的这样半半拉拉也确实值得吐槽。

以下为设置Multi-Primary MGR的方法。假设集群之前已经处于Single-Primary模式。

--group_replication_single_primary_mode=ON,表示启动了Single-Primary模式,那么修改为OFF就意味着要启动Multi-Primary模式。
(root@localhost) [(none)]> show variables like 'group_replication_single_primary_mode';
+----------------------------------------------------+-------------------------------------------------+
| Variable_name                                      | Value                                           |
+----------------------------------------------------+-------------------------------------------------+
| group_replication_single_primary_mode              | ON                                              |
+----------------------------------------------------+-------------------------------------------------+

--如果MGR已经启动,则无法动态修改该参数
(root@localhost) [(none)]> set global group_replication_single_primary_mode=off;
ERROR 3093 (HY000): Cannot change into or from single primary mode while Group Replication is running.

--停止复制
(root@localhost) [(none)]> stop GROUP_REPLICATION;
Query OK, 0 rows affected (8.67 sec)

--设置单主模式参数为off
(root@localhost) [(none)]> set global group_replication_single_primary_mode=off;
Query OK, 0 rows affected (0.00 sec)

--该参数设置为ON,则禁用了在多主模式下一些可能产生未知数据冲突的操作
(root@localhost) [(none)]> set global group_replication_enforce_update_everywhere_checks=ON;
Query OK, 0 rows affected (0.00 sec)

--设置为第一个准备启动MGR(bootstrap)的节点
(root@localhost) [(none)]> SET GLOBAL group_replication_bootstrap_group=ON;
Query OK, 0 rows affected (0.00 sec)

--启动复制
(root@localhost) [(none)]> START GROUP_REPLICATION;
Query OK, 0 rows affected (1.29 sec)

--为了防止后续由于意外再启动另外一个复制组,关闭bootstrap参数
(root@localhost) [(none)]> SET GLOBAL group_replication_bootstrap_group=OFF;
Query OK, 0 rows affected (0.00 sec)

--此时可以从视图中看到整个集群只有一个节点是ONLINE
(root@localhost) [(none)]> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE |
+---------------------------+--------------------------------------+-------------+-------------+--------------+
| group_replication_applier | 72ad2062-08a3-11e7-a513-5bfce171938d | bogon       |       24801 | ONLINE       |
+---------------------------+--------------------------------------+-------------+-------------+--------------+
1 row in set (0.00 sec)

可以加入第二个节点了。

--同样设置单主模式参数为off
(root@localhost) [(none)]> set global group_replication_single_primary_mode=off;
Query OK, 0 rows affected (0.00 sec)

--设置update检查参数为on
(root@localhost) [(none)]> set global group_replication_enforce_update_everywhere_checks=ON;
Query OK, 0 rows affected (0.00 sec)

--启动复制
(root@localhost) [(none)]> start group_replication;
Query OK, 0 rows affected (5.42 sec)

--此时检查视图,可以发现集群中已经存在两个节点
(root@localhost) [(none)]> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+
| CHANNEL_NAME              | MEMBER_ID                            | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE |
+---------------------------+--------------------------------------+-------------+-------------+--------------+
| group_replication_applier | 9003e830-08a3-11e7-8ae3-e62d2f6366d2 | bogon       |       24802 | ONLINE       |
| group_replication_applier | 96fe2b5a-08a3-11e7-b383-d7f02f17e847 | localhost   |       24803 | ONLINE       |
+---------------------------+--------------------------------------+-------------+-------------+--------------+
2 rows in set (0.00 sec)

--group_replication_primary_member值为空,表示启动的是Multi-Primary Mode,否则该参数显示的是单主模式中的Primary节点
(root@localhost) [(none)]> SELECT * FROM performance_schema.global_status WHERE VARIABLE_NAME='group_replication_primary_member';
+----------------------------------+----------------+
| VARIABLE_NAME                    | VARIABLE_VALUE |
+----------------------------------+----------------+
| group_replication_primary_member |                |
+----------------------------------+----------------+
1 row in set (0.00 sec)

同样的方法可以加入第三个节点,在当前版本中MGR最多支持一个集群中拥有9个节点。

如果需要在MySQL重启之后这些参数仍然生效,那么需要将这些参数加入到my.cnf文件中,一个典型的配置了MGR的my.cnf如下所示。

[mysqld]

# server configuration
datadir=/Users/Kamus/mysql_data/s2
basedir=/usr/local/Cellar/mysql/5.7.17

port=24802
socket=/tmp/s2.sock

#
# Replication configuration parameters
#
server_id=2
gtid_mode=ON
enforce_gtid_consistency=ON
master_info_repository=TABLE
relay_log_info_repository=TABLE
binlog_checksum=NONE
log_slave_updates=ON
log_bin=binlog
binlog_format=ROW

#
# Group Replication configuration
#
transaction_write_set_extraction=XXHASH64
loose-group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
loose-group_replication_start_on_boot=off
loose-group_replication_local_address= "127.0.0.1:24902"
loose-group_replication_group_seeds= "127.0.0.1:24901,127.0.0.1:24902,127.0.0.1:24903"
loose-group_replication_bootstrap_group= off

#
## Group Replication configuration multi-primary mode
##
# loose-group_replication_single_primary_mode=off
# loose-group_replication_enforce_update_everywhere_checks=ON

Public preview of the next release of SQL Server on Linux

SQL Server for Linux

当微软宣布即将发布SQL Server for Linux版本的时候,有些人觉得很兴奋,有些人觉得然并卵,但是既然Gartner在2016年的数据库管理系统魔力象限图中将微软列在了第一位,超过了一直以来的霸主Oracle,那么无论如何这个SQL Server for Linux版本的发布都是值得关注的,微软将这个版本称为SQL Server vNext on Linux。vNext,好直白的期望。

Gartner-OP-DBMS-MQ-2016
Gartner-OP-DBMS-MQ-2016

微软在2016年11月中旬正式发布了SQL Server for Linux的第一个公众预览版,这条产品线将支持所有的企业级Linux平台,在第一个预览版中支持Red Hat Enterprise Linux 7.2和Ubuntu Linux 16.04,并且支持在macOS和Linux中的Docker容器,后续还会支持Suse Linux Enterprise Server,另外,微软承诺Linux上的SQL Server绝对不会是“SQL Server Lite”这样的阉割版数据库,而会是一个具备SQL Server 2016完整功能集的真正的企业级数据库,比如 in-memory OLTP,还有always-on encryption和row-level security这样的企业级安全功能。在现在的预览版中以下这些功能还不支持,但是后续会逐渐支持。

SQL Server Unsupported features and services

本文中会对SQL Server for Linux的安装、配置、使用做简单的测试,说实话,如果Oracle数据库的安装使用也能这样简单就太好了。

本文使用的测试环境是AWS的一个EC2实例,Red Hat Enterprise Linux 7.2,整个安装过程,从开始下载一直到数据库启动结束,不超过20分钟,这其中还包括了下载RPM包的15分钟。

第一步:用root用户下载安装镜像库的repo文件

sudo curl -o /etc/yum.repos.d/mssql-server.repo https://packages.microsoft.com/config/rhel/7/mssql-server-2017.repo

第二步:通过yum安装

sudo yum install -y mssql-server

第三步:设置数据库并启动
Update@2017-05-11
与之前不同,设置数据库的命令整合到mssql-conf命令中,而不再有sqlservr-setup命令。

[root@opreative-server-3 ~]# /opt/mssql/bin/mssql-conf setup
The license terms for this product can be downloaded from
http://go.microsoft.com/fwlink/?LinkId=746388
and found in /usr/share/doc/mssql-server/LICENSE.TXT.

Do you accept the license terms? [Yes/No]:Y
Setting up Microsoft SQL Server
Enter the new SQL Server system administrator password:
Confirm the new SQL Server system administrator password:
Starting Microsoft SQL Server...
Enabling Microsoft SQL Server to run at boot...
Created symlink from /etc/systemd/system/multi-user.target.wants/mssql-server.service to /usr/lib/systemd/system/mssql-server.service.
Setup completed successfully.

[11:24:34:794][ec2-user@ip-172-31-11-228 ~]$ sudo /opt/mssql/bin/sqlservr-setup
[11:24:34:876]Microsoft(R) SQL Server(R) Setup
[11:24:34:876]
[11:24:34:876]You can abort setup at anytime by pressing Ctrl-C. Start this program
[11:24:34:876]with the –help option for information about running it in unattended
[11:24:34:876]mode.
[11:24:34:876]
[11:24:34:876]The license terms for this product can be downloaded from
[11:24:34:877]http://go.microsoft.com/fwlink/?LinkId=746388 and found
[11:24:34:877]in /usr/share/doc/mssql-server/LICENSE.TXT.
[11:24:34:880]
[11:24:45:937]Do you accept the license terms? If so, please type “YES”: YES
[11:24:45:938]
[11:24:55:008]Please enter a password for the system administrator (SA) account:
[11:24:59:810]Please confirm the password for the system administrator (SA) account:
[11:24:59:810]
[11:24:59:811]Setting system administrator (SA) account password…
[11:25:03:006]
[11:25:07:728]Do you wish to start the SQL Server service now? [y/n]: y
[11:25:11:927]Do you wish to enable SQL Server to start on boot? [y/n]: y
[11:25:12:022]Created symlink from /etc/systemd/system/multi-user.target.wants/mssql-server.service to /usr/lib/systemd/system/mssql-server.service.
[11:25:12:058]Created symlink from /etc/systemd/system/multi-user.target.wants/mssql-server-telemetry.service to /usr/lib/systemd/system/mssql-server-telemetry.service.
[11:25:12:087]
[11:25:12:088]Setup completed successfully.

That’s it! SQL Server for Linux数据库就安装完毕并正常启动了。

可以通过systemctl来检查mssql-server的服务的启动状态。

[root@opreative-server-3 ~]# systemctl status mssql-server
● mssql-server.service - Microsoft SQL Server Database Engine
   Loaded: loaded (/usr/lib/systemd/system/mssql-server.service; enabled; vendor preset: disabled)
   Active: active (running) since Thu 2017-05-11 17:00:17 CST; 34s ago
     Docs: https://docs.microsoft.com/en-us/sql/linux
 Main PID: 16147 (sqlservr)
   CGroup: /system.slice/mssql-server.service
           ├─16147 /opt/mssql/bin/sqlservr
           └─16166 /opt/mssql/bin/sqlservr

May 11 17:00:20 opreative-server-3 sqlservr[16147]: 2017-05-11 17:00:20.52 spid18s     Server is listening on [ 0.0.0.0  1433].
May 11 17:00:20 opreative-server-3 sqlservr[16147]: 2017-05-11 17:00:20.53 Server      Server is listening on [ 127.0.0.1  1434].
May 11 17:00:20 opreative-server-3 sqlservr[16147]: 2017-05-11 17:00:20.53 Server      Dedicated admin connection support was established for listening locally on port 1434.
May 11 17:00:20 opreative-server-3 sqlservr[16147]: 2017-05-11 17:00:20.53 spid18s     SQL Server is now ready for client connections. This is an informational message... required.
May 11 17:00:20 opreative-server-3 sqlservr[16147]: 2017-05-11 17:00:20.86 spid11s     Starting up database 'tempdb'.
May 11 17:00:21 opreative-server-3 sqlservr[16147]: 2017-05-11 17:00:21.03 spid11s     The tempdb database has 1 data file(s).
May 11 17:00:21 opreative-server-3 sqlservr[16147]: 2017-05-11 17:00:21.03 spid22s     The Service Broker endpoint is in disabled or stopped state.
May 11 17:00:21 opreative-server-3 sqlservr[16147]: 2017-05-11 17:00:21.03 spid22s     The Database Mirroring endpoint is in disabled or stopped state.
May 11 17:00:21 opreative-server-3 sqlservr[16147]: 2017-05-11 17:00:21.05 spid22s     Service Broker manager has started.
May 11 17:00:21 opreative-server-3 sqlservr[16147]: 2017-05-11 17:00:21.08 spid7s      Recovery is complete. This is an informational message only. No user action is required.
Hint: Some lines were ellipsized, use -l to show in full.

[ec2-user@ip-172-31-11-228 ~]$ systemctl status mssql-server
● mssql-server.service – Microsoft(R) SQL Server(R) Database Engine
Loaded: loaded (/usr/lib/systemd/system/mssql-server.service; enabled; vendor preset: disabled)
Active: active (running) since Sun 2016-12-04 22:25:08 EST; 1min 59s ago
Main PID: 16558 (sqlservr)
CGroup: /system.slice/mssql-server.service
├─16558 /opt/mssql/bin/sqlservr
└─16571 /opt/mssql/bin/sqlservr

安装完毕以后可以通过自带的mssql-conf命令进行一些简单的配置,可配置项不多,主要是监听端口,默认的数据文件所在目录,日志所在目录,备份所在目录等。

[ec2-user@ip-172-31-11-228 ~]$ sudo /opt/mssql/bin/mssql-conf list
Supported settings using the 'set' option: 
tcpport
defaultbackupdir
defaultdumpdir
defaultlogdir
defaultdatadir
For all command line options use -h or --help

如果希望在服务器上直接通过sqlcmd命令行登入数据库,还需要额外安装一个mssql-tools的RPM包,这个安装包里包括sqlcmd和bcp(Bulk import-export utility)。

第一步:用root用户下载安装mssql-tools的repo文件

sudo curl -o /etc/yum.repos.d/msprod.repo https://packages.microsoft.com/config/rhel/7/prod.repo

第二步:通过yum安装

sudo yum install -y mssql-tools

然后就可以通过sqlcmd来登入数据库,并使用Transact-SQL (T-SQL)语言来进行各种操作和管理了。我并非专业的SQL Server DBA,只是浅尝辄止而已。登录以后,创建了一个新数据库testdb,然后进入testdb,创建了一张新表inventory,然后在inventory表中插入了2条记录。

[ec2-user@ip-172-31-11-228 ~]$ sqlcmd -S localhost -U SA -P 'PASSWORD OF SA'
1> SELECT Name from sys.Databases;
2> GO
Name                                                                                                                            
--------------------------------------------------------------------------------------------------------------------------------
master                                                                                                                          
tempdb                                                                                                                          
model                                                                                                                           
msdb                                                                                                                            

(4 rows affected)
1> create database testdb;
2> go
1> SELECT Name from sys.Databases;
2> go
Name                                                                                                                            
--------------------------------------------------------------------------------------------------------------------------------
master                                                                                                                          
tempdb                                                                                                                          
model                                                                                                                           
msdb                                                                                                                            
testdb                                                                                                                          

(5 rows affected)
1> use testdb
2> go
Changed database context to 'testdb'.
1> CREATE TABLE inventory (id INT, name NVARCHAR(50), quantity INT);
2> GO
1> INSERT INTO inventory VALUES (1, 'banana', 150);
2> INSERT INTO inventory VALUES (2, 'orange', 154);
3> GO

(1 rows affected)

(1 rows affected)
1> SELECT * FROM inventory WHERE quantity > 152;
2> GO
id          name                                               quantity   
----------- -------------------------------------------------- -----------
          2 orange                                                     154

(1 rows affected)

除了sqlcmd命令行之外,在Windows操作系统下有全套的SQL Server Management Studio (SSMS) ,可以直接使用这个具有丰富功能的图形化管理工具来直接管理SQL Server vNext on Linux。多说一句,由于是使用1433端口连接服务器上的数据库,因此需要在AWS EC2中将该服务器实例所属的Security Group中的Inbound策略中开放TCP 1433端口。

SQL Server Management Studio

【结论】SQL Server vNext on Linux预览版在安装、操作过程中非常顺畅,在后续有更多功能加入以后,应该是企业级数据库非常优秀的选择。先不说是否会有Oracle用户迁移到SQL Server上,这至少给了现在正在使用SQL Server的客户们更广阔的选择空间,现在他们运行在Windows Server上的SQL Server数据库也可以移植到Linux中了,无论如何,这是一个很大的进步,微软这步要是能早一些迈出可能就更好了。

Why I returned my purchase of New MacBook Pro with Multi-Touch Bar

是的,我把刚刚收到不到一周的新MacBook Pro退货了,听上去挺不可思议的。同时,我还退掉了刚刚收到的仍未拆封的两根接口线,一根是USB-C转Thunderbolt2,一根是USB-C转Lightning。实际上为了这台新MacBook Pro,我还早早地就购买了4个USB-C转USB3.0的转接头,他们由于收到货太久了,没法儿退货。 我拥有苹果刚刚开始使用Intel x86芯片做Macbook处理器时的第一代MacBook,那是2005年时候的事情,然后一直到今年,在10年多的时间里,几乎每一代MacBook Pro的新品我都拥有,从13寸到15寸,从普通显示屏到Retina显示屏,从普通机械硬盘到SSD,从Megasafe到Megasafe II,我甚至为家人购买了新的只有一个USB-C接口的Macbook,对于其上饱受很多程序员诟病的一代蝶式键盘,我也同样很喜欢。另外我还拥有从iPhone4开始的每一代iPhone,从第一代iPad开始的每一代iPad(嗯,不包括iPad Pro,因为实在找不到购买的理由)。在购买最新的MacBook Pro with Multi-Touch Bar之前,我在工作和生活中使用的是2015年年初出的那一批13寸顶配MacBook Pro。 你们一定要说这是在炫耀,实际上我只是想说,我是一个经验丰富的苹果产品使用者,还是一个彻头彻尾纯正的果粉,然而,在收到新MacBook Pro不到一周后的今天,我退货了。下面是退货的理由,总得给自己一个交代不是吗?在你阅读下面这些牵强附会的理由之前,如果你还在使用MacBook Air(像我的一个朋友那样),或者你在使用任何一款Windows笔记本,那么我仍然强烈建议你购买这一代MacBook Pro,但是如果你像我一样已经在使用上一款MacBook Pro,那么建议你可以体会一下。 1. Multi-Touch Bar。 Multi-Touch Bar是这一代MacBook Pro的最大卖点,也是看发布会时最让人惊艳的功能。 MacBook Pro with Multi-Touch Bar 虽然,这一段窄窄的触摸屏,苹果实现的非常不错,它拥有特别合适的位置和形状,显示在其上的各种图标也很优雅。然而除了最右侧的Touch ID让我们在解锁笔记本的时候感觉到很爽之外,在我使用这台本子的一周内,这条触摸屏仍然很少场景会被使用到。大部分时间,Touch Bar上都在不停地变换着用苹果内置的中文输入法时出现的备选字,然而谁会不用数字键去选择备选字,而是用手指戳那个触摸屏呢?连盲打都实现不了。不过值得一提的是,聊天时候输入emoji用触控条真的很方便。 找不到能够频繁使用Multi-Touch Bar的场景倒也罢了,由于取代了原本最上面一条的实体按键,还有一些不太方便的场景。 在很多情况下,调节声音和亮度的按钮是龟缩在整个Touch Bar的右侧的,需要调小音量,OK,你需要点一下音量按钮,然后在变幻出来的进度条上去拖动,这样的拖动让我非常抓狂,因为完全丧失了一下一下按音量键来确认到底哪个音量更合适的控制感。想一下,在只想快进和后退几格的时候,你是会用按键还是会去拖进度条? 在最开始让程序员抓狂的ESC键,现在还在,但是变成了虚拟键,虽然这部分是可以盲打的,在这个虚拟ESC键的周边敲击都有效,然而对于一个频繁会被使用到的按键,在大多数按键都有实体键反馈的时候,忽然小指触到一个平平的地方,这种需要迅速转换的体验,总让人疑惑是不是点击到了ESC。这是一种让人烦躁的感觉。 2. 超大的触控板。 触控板 MacBook Pro原本的触控板已经很大了,还需要更大吗?问题是大了以后会很容易误触啊。因为实际上在你打字的时候,你的两个大拇指甚至是手掌的半坨肉都是很容易会碰到触控板的,这个误触的频率已经到了有时候会让人心里暗骂“我靠”的地步,有些严重。 3. 四个USB-C接口。 即使拥有可以左右开工,找任意舒服的方位充电的好处,忽然间全部变成了USB-C仍然让人有些头痛。Megasafe这么优雅的电源接口就这样被丢弃了,着实让人心痛。特别是当我想到以前的各种线缆在很长一段时间里都又要再接一个转接口,这样的处理方案让我很纠结。一点儿也不优雅!也许再等几年,当所有的外接设备(U盘,移动硬盘,鼠标,演讲遥控器,网银U盾)都有了天生USB-C接口,这个问题就不存在了。 4. 电源绕线。 这是最让我沮丧的地方,完全不能理解为什么从新MacBook开始,苹果就取消了那么贴心的设计。以后再也不能像下面这样电源和线缆融为一体了。 img_0230 我甚至能想像出来,不出几周,线缆就会变成这样,这太让人忧伤了。 img_0240 【结论】新MacBook Pro仍然是笔记本电脑中最值得购买的机型(没有之一),但是如果你的手头上已经有一款运行状况良好的上一代MacBook Pro,就像我现在这样,那么值得更新换代的动机则不会那么强烈。

Network Settings Configuration in Oracle Cloud

对于Oracle DBA而言,网络上的配置可能都是短板,而如果在公有云中进行Oracle数据库的部署,那么几乎要求一个DBA变成全栈工程师,因为已经不需要你进行网络基础架构的安装,那么对整个环境进行简单的网络设定配置就成为必不可少的技能之一。

本文会着重介绍Oracle Cloud中关于网络设定的概念和设置方法。

在这之前要先简单介绍Oracle Cloud。

Screen Shot 2016-04-09 at 2.56.16 PM

也许你已经听说过Amazon AWS、Microsoft Azure,甚至你已经在使用阿里云、华为云、腾讯云、青云,而Oracle无疑是这场云大战中的迟到者,但是从过去的2015年看,Oracle在Cloud市场上的表现却是不俗的,Oracle一直宣称自己是全球唯一最全面、最集成的一朵完整的云,提供了IaaS,PaaS,SaaS三个层面的所有产品,而让Oracle Cloud特别引人注意的,无疑是在RDBMS全球市场中占据霸主地位的Oracle数据库的云化。

Oracle Database在Oracle Cloud中目前有以下几种产品方式。

1. Database as a Service 提供一个客户独占的虚拟机,可以选择在其上安装的是11g还是12c数据库,包括可以选择安装一个2节点的RAC;客户对于虚拟机和运行在其上的数据库有完全的管理权限,包括操作系统的root权限,还有数据库的SYSDBA权限,提供一键备份和一键打补丁等简便的管理功能。

2. Exadata Service 提供一个运行在Exadata上的11.2.0.4或者12.1.0.2的数据库,客户可以选择是全配、半配还是四分之一配,还可以选择是否是RAC,同样有操作系统的root权限和数据库的SYSDBA权限。

3. Database Schema Service 提供一个在11g数据库中的Schema,客户可以选择存储空间大小(5GB-50GB),只能通过RESTful网络服务接口来访问该Schema中的表数据,毫无疑问,没有数据库的SYSDBA权限,操作系统也是完全不可见的。

4. Database as a Service – Managed(该服务产品目前还没有正式发布) 提供一个Oracle数据库给客户,客户拥有SYSDBA权限,可以以包括SQL*Net在内的各种方式访问数据库,操作系统不可见,Oracle原厂承担必要的维护工作,比如备份和PITR恢复、打补丁和升级。

本文中使用的产品是Database as a Service,一套Oracle 12.1.0.2 RAC。CDB架构,初始化创建时候设定了一个PDB。

Screen Shot 2016-04-05 at 4.07.20 PM

在数据库创建完毕以后,Oracle也默认创建了承载Oracle数据库的虚拟机,也就是底层的IaaS架构,在本文中是两台2CPU4核,15GB内存的虚拟机,每台机器有161GB存储空间(这其中包括了安装操作系统和Oracle软件的75GB本地空间,以及ASM使用的66GB共享空间)。

Screen Shot 2016-04-05 at 5.19.23 PM

以上这些都不是本文的重点,我们要介绍的是Oracle Cloud的网络设定。要知道,新创建完的虚拟机虽然可以通过SSH来访问,但是数据库默认是无法通过监听来远程访问的,必须要进行网络设定的修改。

在Oracle Cloud中网络层面的设定要涉及到以下几个概念,我们从管理界面中可以看到这些名词。

Screen-Shot-2016-04-05-at-2_15_14-PM

Security Lists 每个Security List包含了一组Oracle Compute Cloud Service实例(每个Oracle Compute Cloud Service实例可以简单地认为就是一台Oracle Cloud虚拟机,用AWS的语言说是一个EC2实例,用阿里云的语言说是一个ECS实例,以下简称为OCCS),在同一个Security List中的机器可以无障碍地通过任何网络端口进行数据交互,但是与外界的机器进行交互就要看这个Security List中对于inbound和outbound的策略定义了。Inbound策略控制了想进入这个Security List中的网络防火墙定义,Outbound策略控制了想传出这个Security List中的网络防火墙定义。 inbound和outbound的策略都有3种,分别是permit,reject和deny。 permit:允许任何数据包传输(在inbound策略中就是传入,在outbound策略中就是传出) reject:丢弃所有数据包,但是有回传信息告知数据包被丢弃 deny:丢弃所有数据包,并且不回传任何信息 如下这张图清晰地表现了不同Security List中不同的inbound和outbound策略下数据的可能流向。

Screen Shot 2016-04-05 at 6.25.13 PM

默认创建的Security List的inbound策略都是deny,下图中有关联实例的命名为oracle-cloud-enmotech/1/db_1/ora_db的Security List是正在使用的,可以看到inbound策略是deny。 Screen Shot 2016-04-09 at 2.02.44 PM

也就意味着所有的数据包不能传入到新建的机器上和数据库里。那么为什么新建的机器可以通过SSH访问,而又确实无法通过数据库监听来远程访问?这就需要提到下面这个概念-Security Rules。

不过在解释Security Rules之前,需要先解释另外两个概念,Security Applications和Security IP Lists。

Security Applications Security Application就是一组网络协议和端口的组合,从下图中可以清晰地看出来。

命名为ssh的Security Application就是tcp协议的22端口,这就是ssh默认的端口。 Screen Shot 2016-04-09 at 1.41.50 PM

命名为ora_scan_listener的Security Application就是tcp协议的1521端口,这就是数据库SCAN监听默认的端口。 Screen Shot 2016-04-09 at 1.42.13 PM

再比如命名为ora_monitor_12c的Security Application就是tcp协议的5500端口,这就是12c新的EM Express默认使用的端口。 Screen Shot 2016-04-09 at 1.43.25 PM

Security IP Lists 另外一个关键的概念,就是Security IP List,这里定义的是一组IP地址或者子网,也就是防火墙设置中在源(source)和目标(destination)中设置的值。 比如在如下图的默认设置中,public-internet就表示了所有的IP地址。

Screen-Shot-2016-04-09-at-1_51_58-PM

最后,进入Security Rules的解释。

Security Rules 一个Security Rule实际上就是一个防火墙规则,用于定义对于特定的应用访问OCCS的防火墙策略。这些防火墙是在Security List之上设置的特例。简单地说,Security List的inbound策略是deny的话,那么只有在Security Rule中明确设置可以通过的端口才被允许通过;如果Security List的inbound策略是permit的话,就不需要再设置任何Security Rule,因为所有端口都被允许了。很明显,Oracle强烈建议第一种设置方法,而不要轻易设置Security List的inbound策略是permit。

为什么新建的机器可以通过SSH访问?因为SSH的Security Rule是默认打开的(Status=Enabled)。 Screen Shot 2016-04-09 at 1.30.38 PM

解读一下,图中的各个设定的含义。 Security Application=ssh,是这个防火墙规则针对的是tcp协议的22端口; Source=public-internet,是这个防火墙规则的源定义,是一个Security IP List的值,表示任何一个IP; Destination=oracle-cloud-enmotech/1/db_1/ora_db,是这个防火墙规则的目标定义,是一个Security List的值,就是上面提到的inbound策略为deny的那个Security List,这表示在这个Security List中的所有OCCS都是目标。

再通俗易懂地解释一下,就是该防火墙定义允许任何一个IP通过22端口访问本次创建的Database之下的所有OCCS,也就是允许通过SSH来访问RAC的2个节点。

为什么无法通过数据库监听来远程访问?因为跟SCAN Listener相关的Security Rule是默认关闭的(Status=Disabled)。 Screen Shot 2016-04-09 at 1.31.19 PM

修改很简单,将状态从Disabled修改为Enabled即可。

剩下在网络设定管理界面中的另外两个名词,就比较简单了。

IP Reservations 为虚拟主机保留的公网IP,可以通过这个IP直接访问主机。 Screen_Shot_2016-04-09_at_2_07_44_PM

SSH Public Keys 登录OCCS,不是通过用户名和密码,而必须要通过SSH Key,在创建Database的过程中就会要求上传Public Key,也可以在这个界面再次更新或者上传新的Public Key。 Screen_Shot_2016-04-09_at_2_08_10_PM

结论 通过以上的设置,Oracle Cloud基本上组成了一套完善的网络防火墙体系,可以细粒度的控制哪些IP通过哪些端口访问数据库或者数据库主机。

Oracle ACE Spotlight awards

Yes, it’s me. 能够在Oracle官方站点的ACE主页面作为Spotlight人物被介绍,很荣幸。Oracle的技术生涯可以用此做个注解了。

BTW, 上面那张ACE全家福的二排正中间也能找到我,2014年OOW期间的照片,应该是迄今为止中国ACE专家在旧金山会和最全的一届。

Oracle_ACE_Spotlight

Oracle 12.2 Sharded Database Management – Series I

##什么是Sharding

> Sharding is a data tier architecture in which data is horizontally partitioned across independent databases. Each database in such configuration is called a shard. All of the shards together make up a single logical database which is referred to as a sharded database or SDB.

Sharding是指数据层的水平分区,实际上在之前的Oracle版本中,分区已经是数据仓库系统非常常用的技术手段,但是在12.2之前,一个分区表的所有分区只能存储在一个数据库中,而在12.2之后,一个分区表的多个分区可以存储在不同的数据库里,这就被称为Sharding。为什么Sharding这么被大家期待?因为可能很多人都在说,Oracle的水平扩展能力不够强,虽然有RAC,但是集群节点越多内耗就越多,这样的水平扩展能力跟Hadoop之类的方案相比是不足的。我们先不评判这样的看法是不是正确,Oracle 12.2要告诉大家的是,要Sharding?要分库分表?要线性水平扩展?没问题,给你。

假设这样的分库分表一共跨了10个Oracle数据库,那么这10个Oracle数据库对于前端应用来说是透明的,是一个统一的逻辑数据库,称为一个sharded数据库,或者简称为一个SDB,而在这个SDB中每个数据库被称为一个shard。
一张大表可以根据规则被分割到每个shard中,在每个shard里拥有相同的字段结构,但是却拥有不同的数据,这样的一张表被称为sharded table。

##Sharding适合所有的数据库应用吗?
既然Sharding听上去很厉害,那么是不是现在只要遇到有性能问题的数据库,一律都可以使用Sharding技术来解决呢?当然不,Sharding不会也不可能是FAST=TRUE这样的参数。一个适合Sharding技术的应用,必须有非常好的数据模型,和清晰的数据分布策略(比如是一致性哈希,范围或者列表分区),并且访问这些数据也是总要通过shard key来过滤的,只有这样,才能在整个Sharded数据库架构中很好地将请求路由到合适的数据库上。这样的shard key可能会是客户编号,国家编号,身份证号码等。

##Sharding有哪些缺点?
在我们颂扬一项技术之前,先看一下这项技术会带来哪些不便,是更理智的做法,要知道世界上并无十全十美的事物。

在Wikipedia([https://en.wikipedia.org/wiki/Shard_(database_architecture)])中对于Shard的坏处有如下的定义。

> •Increased complexity of SQL – Increased bugs because the developers have to write more complicated SQL to handle sharding logic.
> •Sharding introduces complexity – The sharding software that partitions, balances, coordinates, and ensures integrity can fail.
> •Single point of failure – Corruption of one shard due to network/hardware/systems problems causes failure of the entire table.
> •Failover servers more complex – Failover servers must themselves have copies of the fleets of database shards.
> •Backups more complex – Database backups of the individual shards must be coordinated with the backups of the other shards.
> •Operational complexity added – Adding/removing indexes, adding/deleting columns, modifying the schema becomes much more difficult.

1. 增加了SQL的复杂性。因为开发人员必须要写更复杂的SQL来处理sharding的逻辑。
2. Sharding本身带来的复杂性。sharding软件需要照顾分区,数据平衡,访问协调,数据完整性。
3. 单点故障。一个shard损坏可能导致整张表不可访问。
4. 失效接管服务器也更复杂。因为负责失效接管的服务器必须拥有任何可能损坏的shard上的数据。
5. 备份也更复杂。多个shard可能都需要同时备份。
6. 维护也更复杂。比如增加删除索引,增减删除字段,修改表定义等等,都变得更困难。

庆幸的是,这上面的大部分缺点,在Oracle 12.2 Sharding中都无需担心。

##Oracle Sharding Architecture
跟那些NoSQL数据库架构不一样,Oracle Sharding在提供sharding的同时,并没有牺牲掉关系型数据库所带来的优秀特性,比如说关系型数据建模,SQL编程接口,丰富的数据类型,在线的表结构变更,充分利用CPU多核的扩展性,高级安全,压缩,高可用,ACID特征,一致读,所有的Oracle数据库的优势仍然还在那里,但是,额外,提供了sharding的优势。

对于Oracle Sharding的上层来说,使用的是Oracle GDS(Global Data Services)框架来实现自动部署和shading的管理以及拓扑复制。GDS还同时提供了对于整个SDB访问的负载均衡和基于位置的路由功能。在GDS框架中,global service manager负责将应用过来的请求转发到合适的shard上,另外还有一个shard catalog数据库,支持跨shard的查询功能,同时SDB的配置数据也都存在这个catalog数据库中。

对于Oracle Sharding的底层来说,使用的是Oracle长久以来一直存在的分区(partitioning)技术。Oracle Sharding就其本质上来说,实际上就是分布式分区,将以前的分区扩展支持到跨不同的物理数据库上。

因此创建一个sharded table的语法,跟以前创建一张分区表的语法也是非常相像的,以前称为分区键的字段现在被称为“sharding key”。

CREATE SHARDED TABLE customers
( cust_id NUMBER NOT NULL
, name VARCHAR2(50)
, address VARCHAR2(250)
, region VARCHAR2(20)
, class VARCHAR2(3)
, signup DATE
CONSTRAINT cust_pk PRIMARY KEY(cust_id) )     
PARTITION BY CONSISTENT HASH (cust_id) 
TABLESPACE SET ts1
PARTITIONS AUTO
;

在上面的语法中出现了一些新的关键字,比如“CONSISTENT HASH”,“TABLESPACE SET”,后面会详细解释。

Sharded table的每个分区是在表空间这个层面分布到不同的数据库(shard)中的,每个分区都必须在一个单独的表空间中。当使用consistent hash来进行分区以后,表空间会自动分布到不同的shard中,最终提供一个平均分布的数据架构。

除了consistent hash方式之外,Oracle还提供了基于range和list,和两层复合分区(range-consistent hash和list-consistent hash)的方式来进行Sharding,这对于熟悉Oracle分区技术的人来说一点儿也不陌生。

Oracle Sharding整合了Oracle很多成熟的技术,比如复制技术,Oracle Data Guard和Oracle Goldengate;比如高可用技术,Oracle RAC;比如连接池技术,connection pool;当然还有在12.1中新发的GDS框架。

这个章节最后用2张PPT来结束,这是Eygel放出的在昨天旧金山OOW上Oracle介绍的Sharding技术的PPT。

_Oracle Sharding的实现_
640

_Sharding如何实现数据访问路由_
640-1

接下来的章节会继续介绍Oracle Sharding的详细实现和使用方法。

PostgreSQL 9.5 new feature highlight: BRIN indexes

前几天PostgreSQL 9.5 Alpha 1版本刚刚发布,在新版本中吸引我注意的是BRIN index。为什么引人注意?因为这就是活脱脱的Oracle Exadata中的Storage Index和Oracle Database 12.1.0.2中的新功能Zone Maps。

Exadata的Storage Index不说了,因为那并非数据库范畴的解决方案,而Oracle数据库12.1.0.2中的新功能Zone Maps曾让我非常激动,但是最终发现该功能也只能在运行于Exadata上的Oracle中才能启用,略失望。

Zone Maps的解释如下:

Zone maps in an Oracle Database store minimum and maximum values of columns for a range of blocks (known as a zone). In addition to performing I/O pruning based on predicates of clustered fact tables, zone maps prune on predicates of dimension tables provided the fact tables are attribute-clustered by the dimension attributes though outer joins with the dimension tables.

BRIN index的解释如下:

BRIN stands for Block Range INdexes, and store metadata on a range of pages. At the moment this means the minimum and maximum values per block…So if a 10GB table of order contained rows that were generally in order of order date, a BRIN index on the order_date column would allow the majority of the table to be skipped rather than performing a full sequential scan.

同样的思路,在一个类索引结构中存储一定范围的数据块中某个列的最小和最大值,当查询语句中包含该列的过滤条件时,就会自动忽略那些肯定不包含符合条件的列值的数据块,从而减少IO读取量,提升查询速度。

以下借用Pg wiki中的例子解释BRIN indexes的强大。

 -- 创建测试表orders
 CREATE TABLE orders (
     id int,
     order_date timestamptz,
     item text);

 -- 在表中插入大量记录,Pg的函数generate_series非常好用。
 INSERT INTO orders (order_date, item)
 SELECT x, 'dfiojdso' 
 FROM generate_series('2000-01-01 00:00:00'::timestamptz, '2015-03-01 00:00:00'::timestamptz,'2 seconds'::interval) a(x);

 -- 该表目前有13GB大小,算是大表了。
 # \dt+ orders
                    List of relations
  Schema |  Name  | Type  | Owner | Size  | Description 
 --------+--------+-------+-------+-------+-------------
  public | orders | table | thom  | 13 GB | 
 (1 row)

 -- 以全表扫描的方式查询两天内的记录,注意这里预计需要30s,这是一个存储在SSD上Pg数据库,因此速度已经很理想了。
 # EXPLAIN ANALYSE SELECT count(*) FROM orders WHERE order_date BETWEEN '2012-01-04 09:00:00' and '2014-01-04 14:30:00';
                                                                          QUERY PLAN                                                                          
 -------------------------------------------------------------------------------------------------------------------------------------------------------------
  Aggregate  (cost=5425021.80..5425021.81 rows=1 width=0) (actual time=30172.428..30172.429 rows=1 loops=1)
    ->  Seq Scan on orders  (cost=0.00..5347754.00 rows=30907121 width=0) (actual time=6050.015..28552.976 rows=31589101 loops=1)
          Filter: ((order_date >= '2012-01-04 09:00:00+00'::timestamp with time zone) AND (order_date <= '2014-01-04 14:30:00+00'::timestamp with time zone))
          Rows Removed by Filter: 207652500
  Planning time: 0.140 ms
  Execution time: 30172.482 ms 
 (6 rows)

 -- 接下来在order_date列上创建一个BRIN index
 CREATE INDEX idx_order_date_brin
   ON orders
   USING BRIN (order_date);

 -- 查看这个BRIN index占多少物理空间,13GB的表,而BRIN index只有504KB大小,非常精简。
 # \di+ idx_order_date_brin
                               List of relations
  Schema |        Name         | Type  | Owner | Table  |  Size  | Description 
 --------+---------------------+-------+-------+--------+--------+-------------
  public | idx_order_date_brin | index | thom  | orders | 504 kB | 
 (1 row)

 -- 再次执行相同的SQL,看看性能提升多少。速度上升到只需要6秒钟,提升了5倍。如果这是存储在HDD上的Pg库,这个效果还能更明显。
 # EXPLAIN ANALYSE SELECT count(*) FROM orders WHERE order_date BETWEEN '2012-01-04 09:00:00' and '2014-01-04 14:30:00';
                                                                               QUERY PLAN                                                                               
 -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Aggregate  (cost=2616868.60..2616868.61 rows=1 width=0) (actual time=6347.651..6347.651 rows=1 loops=1)
    ->  Bitmap Heap Scan on orders  (cost=316863.99..2539600.80 rows=30907121 width=0) (actual time=36.366..4686.634 rows=31589101 loops=1)
          Recheck Cond: ((order_date >= '2012-01-04 09:00:00+00'::timestamp with time zone) AND (order_date <= '2014-01-04 14:30:00+00'::timestamp with time zone))
          Rows Removed by Index Recheck: 6419
          Heap Blocks: lossy=232320
          ->  Bitmap Index Scan on idx_order_date_brin  (cost=0.00..309137.21 rows=30907121 width=0) (actual time=35.567..35.567 rows=2323200 loops=1)
                Index Cond: ((order_date >= '2012-01-04 09:00:00+00'::timestamp with time zone) AND (order_date <= '2014-01-04 14:30:00+00'::timestamp with time zone))
  Planning time: 0.108 ms
  Execution time: 6347.701 ms
 (9 rows)

 --能够让用户自行设定一个range中可以包含的数据块数,也是很体贴的设计。默认情况下一个range包含128个page,我们可以修改为更小或者更大,包含的page越少则精度越细,相应的BRIN index也就会越大;反之则精度粗,BRIN index小。
 -- 创建一个每个range包含32 pages的索引。
 CREATE INDEX idx_order_date_brin_32
 ON orders
 USING BRIN (order_date) WITH (pages_per_range = 32);
 
 -- 再创建一个每个range包含512 pages的索引。
 CREATE INDEX idx_order_date_brin_512
 ON orders
 USING BRIN (order_date) WITH (pages_per_range = 512);

--比较一下各个索引的大小。
 # \di+ idx_order_date_brin*
                                  List of relations
  Schema |          Name           | Type  | Owner | Table  |  Size   | Description 
 --------+-------------------------+-------+-------+--------+---------+-------------
  public | idx_order_date_brin     | index | thom  | orders | 504 kB  | 
  public | idx_order_date_brin_32  | index | thom  | orders | 1872 kB | 
  public | idx_order_date_brin_512 | index | thom  | orders | 152 kB  | 
 (3 rows)

延展阅读:
Postgres 9.5 feature highlight: BRIN indexes
WAITING FOR 9.5 – BRIN: BLOCK RANGE INDEXES

Compare PostgreSQL Standard Statistics Views with Oracle Dynamic Performance (V$) Views

Oracle数据库的性能视图几乎可以说是最引以为骄傲的功能,在那样细粒度的采样统计强度下,依然保持卓越的性能,基于这些性能数据采样之后形成的AWR,更是Oracle DBA分析数据库性能问题的最重要手段之一。

那么在誉为最接近Oracle的开源数据库PostgreSQL中,如果要诊断性能问题,又有哪些视图可以使用呢?作为Oracle DBA,在学习PostgreSQL的时候,不可避免地会将PostgreSQL和Oracle进行比较。

以下SQL命令,在mydb=#提示符下的均为在PostgreSQL中执行的,在SQL>提示符下的均为在Oracle中执行的。

先看一下在PostgreSQL中存在那些统计信息视图。PostgreSQL中数据字典的命名还是很规范的,所有统计信息基本上都以pg_stat_开头。

mydb=# select relname from pg_class where relname like 'pg_stat_%';
             relname              
----------------------------------
 pg_statistic
 pg_stats
 pg_stat_all_tables
 pg_stat_xact_all_tables
 pg_stat_sys_tables
 pg_stat_xact_sys_tables
 pg_stat_user_tables
 pg_stat_xact_user_tables
 pg_statio_all_tables
 pg_statio_sys_tables
 pg_statio_user_tables
 pg_statio_all_indexes
 pg_statio_sys_indexes
 pg_statio_user_indexes
 pg_statio_all_sequences
 pg_statio_sys_sequences
 pg_statio_user_sequences
 pg_stat_activity
 pg_stat_replication
 pg_stat_database
 pg_stat_database_conflicts
 pg_stat_user_functions
 pg_stat_xact_user_functions
 pg_stat_archiver
 pg_stat_bgwriter
 pg_stat_all_indexes
 pg_stat_sys_indexes
 pg_stat_user_indexes
 pg_statistic_relid_att_inh_index
(29 rows)

pg_stat_activity
该视图显示了连接入一个Cluster下所有数据库的会话的统计信息,每个会话一行记录,类似于Oracle中的V$SESSION视图。
pg_stat_activity.query字段直接显示了该会话正在执行的SQL或者上次执行的SQL语句文本。在Oracle中检查一个会话正在执行的SQL语句文本,则需要通过V$SESSION和V$SQL视图Join才可以。
pg_stat_activity.pid字段直接显示了该会话在操作系统上的进程ID,这样通过top命令看到的繁忙操作系统进程,可以很简单地通过该字段定位,来作进一步的诊断。在Oracle中则需要通过V$SESSION和V$PROCESS视图Join才可以。

pg_stat_archiver
该视图始终只有一条记录,显示了负责一个cluster下所有数据库的重做日志(PostgreSQL中称为WAL file)归档进程的统计信息,记录项比较简单。last_archived_wal和last_archived_time分别显示了最近一次归档的文件名和最近一次归档时间。
类似于Oracle中的V$ARCHIVE_DEST_STATUS。由于PostgreSQL中的归档实现实在是太简单了,所以几乎跟Oracle没有太多可比性。

pg_stat_bgwriter
该视图始终只有一条记录,显示了负责一个cluster下所有数据库的后台写进程的统计信息,也就是在操作系统中看到的postgres: writer process。该进程每隔bgwriter_delay初始化参数定义的间隔(默认200ms)会唤醒,将Buffer Pool中修改过的页写入到磁盘。跟Oracle的后台进程DBWR非常相仿。
在Oracle中没有专门记录DBWR进程的性能视图,V$BGPROCESS视图也同样没有提供类似的信息,但是在V$SYSSTAT却记录了DBWR的统计信息,这部分跟pg_stat_bgwriter中记录的信息相仿。Oracle 11gR2中有超过600项的统计信息记录在V$SYSSTAT视图中。

SQL> select NAME,VALUE from v$sysstat where upper(name) like '%DBWR%';

NAME                                                                  VALUE
---------------------------------------------------------------- ----------
flash cache insert skip: DBWR overloaded                                  0
DBWR checkpoint buffers written                                     1564210
DBWR thread checkpoint buffers written                                    0
DBWR tablespace checkpoint buffers written                             2852
DBWR parallel query checkpoint buffers written                            0
DBWR object drop buffers written                                        324
DBWR transaction table writes                                         81619
DBWR undo block writes                                               485016
DBWR revisited being-written buffer                                       0
DBWR lru scans                                                            0
DBWR checkpoints                                                       4158
DBWR fusion writes                                                        0

12 rows selected.

pg_stat_database
该视图对于每个database显示一行记录,PostgreSQL中的Cluster类似于Oracle的一个Instance,一个Cluster下可以创建多个database。
该视图中记录了每个数据库提交了多少事务,回滚了多少事务,读了多少数据块,查询、插入、更新、删除了多少记录(在PostgreSQL中用Tuple这个奇怪的词表示跟Row相同的概念),产生过多少死锁。总之这是一个数据库级别相对很简单的统计信息。
但是,在Oracle中还真没有与此类似的性能视图,实际上Oracle没有一个视图简单地记录了一个Schema下面总共查询或者DML了多少条记录,但是却有DBA_TAB_MODIFICATIONS这样的视图详细记录每一张表的DML数量。查询了多少数据?可能Oracle认为这个数字是太不重要了,或者说实在是太大了,完全没必要记录。
对于事务级别的统计,同样可以在Oracle的V$SYSSTAT视图中查询包含“ROLLBACK”和“COMMIT”字样的统计值,远比PostgreSQL中记录地要更多样。

pg_stat_all_tables/pg_stat_sys_tables/pg_stat_user_tables
在PostgreSQL的统计信息视图中,all表示一个数据库下所有的表,sys表示所有的系统表,user表示所有用户创建的表,这三个配套的视图我们放在一起看。以下类似的也相同。
该视图对于每张表显示一条记录,显示了一张表上进行过多少全表扫描,多少索引扫描,查询、插入、更新、删除过多少记录,表中现在有多少记录,表的分析时间等。
在Oracle中表的分析信息存储在DBA_TABLES中,而对于每个表上DML的信息如前所述,可以从DBA_TAB_MODIFICATIONS视图中查询,而经历过怎样的IO则又可以从V$SEGSTAT视图中查询。好吧,实际上,在Oracle中根本也不关注一个表上读取过多少记录这样的数字,所以在PostgreSQL中但凡跟Tuple相关的统计值在Oracle中都找不到对应的记录。对于Oracle来说,IO都以Block为单位,所以读取一条记录还是读取一个块,在IO消耗上没有区别。而至于对于返回记录数等的优化,则归结到SQL层面,那则可以通过V$SQLSTAT等一系列视图作更详细的分析。

Oracle在视图层面从Table概念和Segment概念上做了详细的区分,看似复杂,实际清晰而且详尽,而在PostgreSQL中则混为一谈了,当然在PostgreSQL中通过后面会谈到的pg_statio_系列视图又对表和索引上的IO统计信息进行了记录。

pg_stat_xact_all_tables/pg_stat_xact_sys_tables/pg_stat_xact_user_tables
该系列视图与上述相仿,只是增加了xact前缀,xact表示transaction,统计的是当前会话对于表操作的信息,这部分信息通常还没有更新到pg_stat_all_tables视图中。
在Oracle中由于性能数据的抓取粒度是如此之细,所以并未区分当前会话还是已经结束的会话,要知道V$SEGSTAT中的信息几乎是real-time在更新的。所以,在Oracle中无需此类视图。

pg_stat_all_indexes/pg_stat_sys_indexes/pg_stat_user_indexes
该视图对于每个索引显示一条记录,显示的信息如下:

mydb=# select * from pg_stat_all_indexes where relname='t1';   
 relid | indexrelid | schemaname | relname | indexrelname | idx_scan | idx_tup_read | idx_tup_fetch 
-------+------------+------------+---------+--------------+----------+--------------+---------------
 24604 |      24613 | public     | t1      | t1_index     |        3 |        58960 |         47817
(1 row)

可见记录的信息非常简单,就是一个索引上进行过多少次扫描,通过这个索引扫描读取了多少记录,返回了多少记录。
在Oracle中,由于索引是Segment的一种,因此类似的统计信息都可以从V$SEGSTAT中获取。

pg_statio_all_tables/pg_statio_sys_tables/pg_statio_user_tables
pg_statio_all_indexes/pg_statio_sys_indexes/pg_statio_user_indexes

这两部分放在一起描述,具有statio前缀的视图显示的是表或索引在数据块级别的IO统计信息,而stat前缀的视图(如前面看到的)则显示的是表或索引在记录级别的IO统计信息。以pg_statio_all_indexes为例:

mydb=# select * from pg_statio_all_indexes where relname='t1';
 relid | indexrelid | schemaname | relname | indexrelname | idx_blks_read | idx_blks_hit 
-------+------------+------------+---------+--------------+---------------+--------------
 24604 |      24613 | public     | t1      | t1_index     |           150 |          453
(1 row)

显示了读取过多少个数据块,这些读取中有多少数据块是直接命中缓存的。
在Oracle中是我们提到了多次的V$SEGSTAT视图。

pg_statio_all_sequences/pg_statio_sys_sequences/pg_statio_user_sequences
PostgreSQL对sequence上的IO独立给出了一系列视图,PostgreSQL中的sequence跟Oracle中的sequence概念基本一致,为存储序列号等的字段生成序列值。
该视图对于每个序列显示一条记录,显示的信息如下:

mydb=# select * from pg_statio_all_sequences;
 relid | schemaname |   relname    | blks_read | blks_hit 
-------+------------+--------------+-----------+----------
 24614 | public     | users_id_seq |         1 |        3
(1 row)

非常简单,显示了读取过多少个数据块,多少数据块的读取是直接命中缓存的。
在Oracle中,由于序列是系统自身对象的一部分,因此如果要诊断跟序列相关的问题,通常要依赖等待事件,比如“enq: SQ – contention”或者“row cache lock”,另外在V$ROWCACHE视图中存储了与序列相关的整体统计值。

SQL> select PARAMETER,GETS,GETMISSES from v$rowcache where PARAMETER='dc_sequences';

PARAMETER                              GETS  GETMISSES
-------------------------------- ---------- ----------
dc_sequences                           2145         54

pg_stat_user_functions/pg_stat_xact_user_functions
有xact前缀和没有前缀的区别在前面描述pg_stat_xact_all_tables系列视图时已经提过,因此放在一起描述。
该视图对于每个指定要跟踪的用户自定义函数显示一条记录,这通过初始化参数track_functions来控制,默认不开启任何跟踪,视图结构如下:

mydb=# \d pg_stat_user_functions
 View "pg_catalog.pg_stat_user_functions"
   Column   |       Type       | Modifiers 
------------+------------------+-----------
 funcid     | oid              | 
 schemaname | name             | 
 funcname   | name             | 
 calls      | bigint           | 
 total_time | double precision | 
 self_time  | double precision | 

calls字段记录了对于用户函数进行过多少次调用;
total_time字段记录了运行这个函数总共消耗了多长时间(毫秒为单位),包括调用其它函数的时间;
self_time字段记录了运行这个函数本身消耗了多长时间(毫秒为单位),不包括调用其它函数的时间。

Oracle中没有类似的视图,Oracle的关于函数或者存储过程的执行统计信息,都是详细到其中每一条SQL语句的,实际上如果像PostgreSQL这样能有一个函数或者存储过程级别的性能统计值,也是极好的。

pg_stat_replication
在设置了复制的环境中,该视图对于每个WAL sender进程(WAL sender进程负责将本机的重做日志发送到远端复制环境)显示一条记录,显示内容大致如下:

postgres=# select pid,application_name,client_addr,state,sent_location,replay_location from pg_stat_replication;
  pid  | application_name |  client_addr   |   state   | sent_location | replay_location 
-------+------------------+----------------+-----------+---------------+-----------------
 27855 | walreceiver      | 192.168.56.105 | streaming | 0/50188CE8    | 0/50188CE8
(1 row)

每个视图中都能直接显示操作系统进程ID,实在是很方便的事情。在操作系统上可以直接查看pid=27855的进程。

[root@pg1-enmotech-com ~]# ps -ef|grep 2785|grep postgres
postgres 27855  1119  0 00:45 ?        00:00:00 postgres: wal sender process postgres 192.168.56.105(57046) streaming 0/50188CE8

从操作系统的ps命令中看到实际上已经将视图中的这些字段内容更新到了该进程描述中,在进程描述中会更新一些很有用的信息(比如server进程的状态,是等待还是空闲等),这也是PostgreSQL非常方便的一个地方。

在Oracle中与PostgreSQL的复制相类似的功能是Physical Data Guard,在DG中重做日志的传输是通过归档路径来完成的,因此类似的信息可以从V$ARCHIVE_DEST_STATUS和V$MANAGED_STANDBY视图中获取。

pg_stat_database_conflicts
该视图仅对于Standby数据库有效,对于每个数据库显示一条记录,显示内容如下:

postgres=# select * from pg_stat_database_conflicts;
 datid |  datname  | confl_tablespace | confl_lock | confl_snapshot | confl_bufferpin | confl_deadlock 
-------+-----------+------------------+------------+----------------+-----------------+----------------
     1 | template1 |                0 |          0 |              0 |               0 |              0
 13051 | template0 |                0 |          0 |              0 |               0 |              0
 13056 | postgres  |                0 |          0 |              0 |               0 |              0
 16384 | mydb      |                0 |          0 |              0 |               0 |              0
 24587 | mydb_bak  |                0 |          0 |              0 |               0 |              0
(5 rows)

由于PostgreSQL的机制,在备库上的查询会跟一些诸如删除表空间、删除数据库、vacuum cleanup的操作相冲突,为了不让备库的WAL replay操作延时太久,PostgreSQL内建了强制取消当前备库上运行的查询以避免跟应用重做日志这样更重要的动作相冲突的机制。而该视图则是记录由于不同原因取消掉的查询的次数。对于每个数据库显示一条记录。
Oracle中不会出现这样的问题,因此也没有相应的视图。

总结
当然,PostgreSQL中除了这些统计信息视图之外,还有不少类似于pg_tables,pg_users这样与Oracle中的数据字典视图相仿的视图,另外还有比如pg_locks这样用于记录锁信息的诊断视图。但是仅仅用一篇文章的长度就可以将所有的统计信息视图全部介绍完毕,PostgreSQL确实是很简洁的数据库。