Quantcast
Channel: OracleBlog
Viewing all 196 articles
Browse latest View live

在Docker上安装oracle 19c

$
0
0

基于docker的安装非常简单。
其实就两行核心命令:

./buildDockerImage.sh -v 19.2.0 -e
docker run --name oracle19c -p 1521:1521 -p 5500:5500 -v /Users/lovehouse/iDocker/dockervolums/oradata/oracle19c:/opt/oracle/oradata oracle/database:19.2.0-ee

我们假设你已经在Mac上安装好了docker,我们开始安装oracle 19c。在docker上安装数据库或应用,是基于dockerfile的,目前Oracle官方还没发布基于19c的dockerfile,但是我们可以使用别人已经做好的dockerfile(感谢kamus告诉我这个docker file)。

如果你不知道如何在Mac上安装docker,可以参考我这篇《在Mac上安装docker并部署oracle 12.2

我们先来试一下官方在github上的dockerfile:

LoveHousedeiMac:iDocker lovehouse$ pwd
/Users/lovehouse/iDocker/oracle
LoveHousedeiMac:iDocker lovehouse$ git clone https://github.com/oracle/docker-images.git
Cloning into 'docker-images'...
remote: Enumerating objects: 77, done.
remote: Counting objects: 100% (77/77), done.
remote: Compressing objects: 100% (52/52), done.
remote: Total 9878 (delta 25), reused 55 (delta 23), pack-reused 9801
Receiving objects: 100% (9878/9878), 10.20 MiB | 2.47 MiB/s, done.
Resolving deltas: 100% (5686/5686), done.
LoveHousedeiMac:iDocker lovehouse$ 
LoveHousedeiMac:iDocker lovehouse$ 
LoveHousedeiMac:iDocker lovehouse$ ls -l
total 0
drwxr-xr-x  31 lovehouse  staff  1054 Feb 16 17:07 docker-images
LoveHousedeiMac:iDocker lovehouse$ cd docker-images/OracleDatabase/SingleInstance/dockerfiles    
LoveHousedeiMac:dockerfiles lovehouse$ ls -l
total 16
drwxr-xr-x   8 lovehouse  staff   272 Feb 16 17:07 11.2.0.2
drwxr-xr-x  18 lovehouse  staff   612 Feb 16 17:07 12.1.0.2
drwxr-xr-x  16 lovehouse  staff   544 Feb 16 17:07 12.2.0.1
drwxr-xr-x  16 lovehouse  staff   544 Feb 16 17:07 18.3.0
drwxr-xr-x   8 lovehouse  staff   272 Feb 16 17:07 18.4.0
-rwxr-xr-x   1 lovehouse  staff  5088 Feb 16 17:07 buildDockerImage.sh
LoveHousedeiMac:dockerfiles lovehouse$

我们看到只有11.2.0.2,12.1.0.2,12.2.0.1,18.3.0和18.4.0几个版本,还没发布19c。

我们用marcelo-ochoa做好的dockerfile,具体的信息在这里。我们开始安装:
1. 先利用git clone下载marcelo-ochoa做好的dockerfiles:

LoveHousedeiMac:iDocker lovehouse$ mkdir marcelo-ochoa
LoveHousedeiMac:iDocker lovehouse$ cd /Users/lovehouse/iDocker/marcelo-ochoa
LoveHousedeiMac:marcelo-ochoa lovehouse$ 
LoveHousedeiMac:marcelo-ochoa lovehouse$  git clone https://github.com/marcelo-ochoa/docker-images.git
Cloning into 'docker-images'...
remote: Enumerating objects: 24, done.
remote: Counting objects: 100% (24/24), done.
remote: Compressing objects: 100% (20/20), done.
remote: Total 9111 (delta 7), reused 7 (delta 3), pack-reused 9087
Receiving objects: 100% (9111/9111), 10.01 MiB | 1.59 MiB/s, done.
Resolving deltas: 100% (5204/5204), done.
LoveHousedeiMac:marcelo-ochoa lovehouse$

我们看到是存在19.2.0的dockerfile的,同时检查其安装的安装包文件名:

LoveHousedeiMac:dockerfiles lovehouse$ cd /Users/lovehouse/iDocker/marcelo-ochoa/docker-images/OracleDatabase/SingleInstance/dockerfiles
LoveHousedeiMac:dockerfiles lovehouse$ ls -l
total 16
drwxr-xr-x   8 lovehouse  staff   272 Feb 16 17:09 11.2.0.2
drwxr-xr-x  18 lovehouse  staff   612 Feb 16 17:09 12.1.0.2
drwxr-xr-x  16 lovehouse  staff   544 Feb 16 17:09 12.2.0.1
drwxr-xr-x  16 lovehouse  staff   544 Feb 16 17:09 18.3.0
drwxr-xr-x   8 lovehouse  staff   272 Feb 16 17:09 18.4.0
drwxr-xr-x  17 lovehouse  staff   578 Feb 16 17:33 19.2.0
-rwxr-xr-x   1 lovehouse  staff  5145 Feb 16 17:09 buildDockerImage.sh
LoveHousedeiMac:dockerfiles lovehouse$ cd 19.2.0
LoveHousedeiMac:19.2.0 lovehouse$ ls -l
total 136
-rw-r--r--  1 lovehouse  staff    49 Feb 16 17:09 Checksum.ee
-rw-r--r--  1 lovehouse  staff  3405 Feb 16 17:09 Dockerfile
-rwxr-xr-x  1 lovehouse  staff  1148 Feb 16 17:09 checkDBStatus.sh
-rwxr-xr-x  1 lovehouse  staff   905 Feb 16 17:09 checkSpace.sh
-rwxr-xr-x  1 lovehouse  staff  3012 Feb 16 17:09 createDB.sh
-rw-r--r--  1 lovehouse  staff  6878 Feb 16 17:09 db_inst.rsp
-rw-r--r--  1 lovehouse  staff  9204 Feb 16 17:09 dbca.rsp.tmpl
-rwxr-xr-x  1 lovehouse  staff  2526 Feb 16 17:09 installDBBinaries.sh
-rwxr-xr-x  1 lovehouse  staff  6526 Feb 16 17:09 runOracle.sh
-rwxr-xr-x  1 lovehouse  staff  1015 Feb 16 17:09 runUserScripts.sh
-rwxr-xr-x  1 lovehouse  staff   758 Feb 16 17:09 setPassword.sh
-rwxr-xr-x  1 lovehouse  staff   932 Feb 16 17:09 setupLinuxEnv.sh
-rwxr-xr-x  1 lovehouse  staff   678 Feb 16 17:09 startDB.sh
LoveHousedeiMac:19.2.0 lovehouse$ 
LoveHousedeiMac:19.2.0 lovehouse$ 
LoveHousedeiMac:19.2.0 lovehouse$ 
LoveHousedeiMac:19.2.0 lovehouse$ cat Dockerfile 
LoveHousedeiMac:19.2.0 lovehouse$ cat Dockerfile |grep INSTALL_FILE_1
    INSTALL_FILE_1="V981623-01.zip" \
COPY --chown=oracle:dba $INSTALL_FILE_1 $INSTALL_RSP $INSTALL_DB_BINARIES_FILE $INSTALL_DIR/
LoveHousedeiMac:19.2.0 lovehouse$ 
LoveHousedeiMac:dockerfiles lovehouse$

我们可以看到,安装包就是叫V981623-01.zip,这和edelivery.oracle.com上下载的db安装包是同名的,不用改名。

2. 将安装包拷贝到该目录下,运行开始安装:

LoveHousedeiMac:19.2.0 lovehouse$ pwd
/Users/lovehouse/iDocker/marcelo-ochoa/docker-images/OracleDatabase/SingleInstance/dockerfiles/19.2.0
LoveHousedeiMac:19.2.0 lovehouse$ cp /Users/lovehouse/Downloads/V981623-01.zip ./
LoveHousedeiMac:19.2.0 lovehouse$ 
LoveHousedeiMac:19.2.0 lovehouse$ 
LoveHousedeiMac:19.2.0 lovehouse$ ls -l
total 11528424
-rw-r--r--  1 lovehouse  staff          49 Feb 16 17:09 Checksum.ee
-rw-r--r--  1 lovehouse  staff        3405 Feb 16 17:09 Dockerfile
-rw-r--r--@ 1 lovehouse  staff  3032822863 Feb 16 17:33 V981623-01.zip
-rw-r--r--@ 1 lovehouse  staff  2869657581 Feb 16 17:14 V981627-01.zip
-rwxr-xr-x  1 lovehouse  staff        1148 Feb 16 17:09 checkDBStatus.sh
-rwxr-xr-x  1 lovehouse  staff         905 Feb 16 17:09 checkSpace.sh
-rwxr-xr-x  1 lovehouse  staff        3012 Feb 16 17:09 createDB.sh
-rw-r--r--  1 lovehouse  staff        6878 Feb 16 17:09 db_inst.rsp
-rw-r--r--  1 lovehouse  staff        9204 Feb 16 17:09 dbca.rsp.tmpl
-rwxr-xr-x  1 lovehouse  staff        2526 Feb 16 17:09 installDBBinaries.sh
-rwxr-xr-x  1 lovehouse  staff        6526 Feb 16 17:09 runOracle.sh
-rwxr-xr-x  1 lovehouse  staff        1015 Feb 16 17:09 runUserScripts.sh
-rwxr-xr-x  1 lovehouse  staff         758 Feb 16 17:09 setPassword.sh
-rwxr-xr-x  1 lovehouse  staff         932 Feb 16 17:09 setupLinuxEnv.sh
-rwxr-xr-x  1 lovehouse  staff         678 Feb 16 17:09 startDB.sh
LoveHousedeiMac:19.2.0 lovehouse$ 
LoveHousedeiMac:19.2.0 lovehouse$ 
LoveHousedeiMac:19.2.0 lovehouse$

LoveHousedeiMac:19.2.0 lovehouse$ cd ..
LoveHousedeiMac:dockerfiles lovehouse$ ls
11.2.0.2                12.1.0.2                12.2.0.1                18.3.0                  18.4.0                  19.2.0                  buildDockerImage.sh
LoveHousedeiMac:dockerfiles lovehouse$ ./buildDockerImage.sh -v 19.2.0 -e
Ignored MD5 sum, 'md5sum' command not available.
==========================
DOCKER info:
Containers: 0
 Running: 0
 Paused: 0
 Stopped: 0
Images: 0
Server Version: 18.09.2
Storage Driver: overlay2
 Backing Filesystem: extfs
 Supports d_type: true
 Native Overlay Diff: true

......

Step 24/24 : CMD exec $ORACLE_BASE/$RUN_FILE
 ---> Running in f4bddc96e630
Removing intermediate container f4bddc96e630
 ---> 65cdd07a7bc1
Successfully built 65cdd07a7bc1
Successfully tagged oracle/database:19.2.0-ee


  Oracle Database Docker Image for 'ee' version 19.2.0 is ready to be extended: 
    
    --> oracle/database:19.2.0-ee

  Build completed in 574 seconds.
  
LoveHousedeiMac:dockerfiles lovehouse$     
LoveHousedeiMac:dockerfiles lovehouse$

附件是完整的log:build19c.log

我们看到image已经安装好,注意它是附带安装了一个slim版的oracle linux,这个在12.2安装的时候,就是这种模式:

LoveHousedeiMac:dockerfiles lovehouse$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
oracle/database     19.2.0-ee           65cdd07a7bc1        About an hour ago   6.33GB
oraclelinux         7-slim              c3d869388183        4 weeks ago         117MB
LoveHousedeiMac:dockerfiles lovehouse$

3. 我们开始安装数据库实例:
注意oracle 企业版的docker run的命令格式如下(XE版的都有所区别):

docker run --name <container name> \
-p <host port>:1521 -p <host port>:5500 \
-e ORACLE_SID=<your SID> \
-e ORACLE_PDB=<your PDB name> \
-e ORACLE_PWD=<your database passwords> \
-e ORACLE_CHARACTERSET=<your character set> \
-v [<host mount point>:]/opt/oracle/oradata \
oracle/database:18.3.0-ee

Parameters:
   --name:        The name of the container (default: auto generated)
   -p:            The port mapping of the host port to the container port. 
                  Two ports are exposed: 1521 (Oracle Listener), 5500 (OEM Express)
   -e ORACLE_SID: The Oracle Database SID that should be used (default: ORCLCDB)
   -e ORACLE_PDB: The Oracle Database PDB name that should be used (default: ORCLPDB1)
   -e ORACLE_PWD: The Oracle Database SYS, SYSTEM and PDB_ADMIN password (default: auto generated)
   -e ORACLE_CHARACTERSET:
                  The character set to use when creating the database (default: AL32UTF8)
   -v /opt/oracle/oradata
                  The data volume to use for the database.
                  Has to be writable by the Unix "oracle" (uid: 54321) user inside the container!
                  If omitted the database will not be persisted over container recreation.
   -v /opt/oracle/scripts/startup | /docker-entrypoint-initdb.d/startup
                  Optional: A volume with custom scripts to be run after database startup.
                  For further details see the "Running scripts after setup and on startup" section below.
   -v /opt/oracle/scripts/setup | /docker-entrypoint-initdb.d/setup
                  Optional: A volume with custom scripts to be run after database setup.
                  For further details see the "Running scripts after setup and on startup" section below.

我们开始安装实例(注意这里会生成一个sys,system和pdbadmin的密码):

LoveHousedeiMac:dockerfiles lovehouse$ docker run --name oracle19c -p 1521:1521 -p 5500:5500 -v /Users/lovehouse/iDocker/dockervolums/oradata/oracle19c:/opt/oracle/oradata oracle/database:19.2.0-ee
ORACLE PASSWORD FOR SYS, SYSTEM AND PDBADMIN: L40uti33Ojk=1

LSNRCTL for Linux: Version 19.0.0.0.0 - Production on 16-FEB-2019 10:55:17

......

The Oracle base remains unchanged with value /opt/oracle
#########################
DATABASE IS READY TO USE!
#########################
The following output is now a tail of the alert.log:
ORCLPDB1(3):CREATE SMALLFILE TABLESPACE "USERS" LOGGING  DATAFILE  '/opt/oracle/oradata/ORCLCDB/ORCLPDB1/users01.dbf' SIZE 5M REUSE AUTOEXTEND ON NEXT  1280K MAXSIZE UNLIMITED  EXTENT MANAGEMENT LOCAL  SEGMENT SPACE MANAGEMENT  AUTO
ORCLPDB1(3):Completed: CREATE SMALLFILE TABLESPACE "USERS" LOGGING  DATAFILE  '/opt/oracle/oradata/ORCLCDB/ORCLPDB1/users01.dbf' SIZE 5M REUSE AUTOEXTEND ON NEXT  1280K MAXSIZE UNLIMITED  EXTENT MANAGEMENT LOCAL  SEGMENT SPACE MANAGEMENT  AUTO
ORCLPDB1(3):ALTER DATABASE DEFAULT TABLESPACE "USERS"
ORCLPDB1(3):Completed: ALTER DATABASE DEFAULT TABLESPACE "USERS"
2019-02-16T11:06:30.379489+00:00
ALTER SYSTEM SET control_files='/opt/oracle/oradata/ORCLCDB/control01.ctl' SCOPE=SPFILE;
2019-02-16T11:06:30.383959+00:00
ALTER SYSTEM SET local_listener='' SCOPE=BOTH;
   ALTER PLUGGABLE DATABASE ORCLPDB1 SAVE STATE
Completed:    ALTER PLUGGABLE DATABASE ORCLPDB1 SAVE STATE

注,如果“DATABASE IS READY TO USE!”字样已经出现,且后面的log一直停着不动,可以在别的窗口重启container。
附件是完整的log:run19c.log

登陆主机或数据库进行操作:

LoveHousedeiMac:19.2.0 lovehouse$  docker ps -a
CONTAINER ID        IMAGE                       COMMAND                  CREATED             STATUS                    PORTS                                            NAMES
39284f79172b        oracle/database:19.2.0-ee   "/bin/sh -c 'exec $O…"   26 minutes ago      Up 11 minutes (healthy)   0.0.0.0:1521->1521/tcp, 0.0.0.0:5500->5500/tcp   oracle19c
LoveHousedeiMac:19.2.0 lovehouse$ 
LoveHousedeiMac:19.2.0 lovehouse$ 
LoveHousedeiMac:19.2.0 lovehouse$ docker exec -it 39284f79172b /bin/bash
[oracle@39284f79172b ~]$ 
[oracle@39284f79172b ~]$ 
[oracle@39284f79172b admin]$ sqlplus sys/L40uti33Ojk=1@ORCLPDB1 as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Sat Feb 16 11:44:29 2019
Version 19.2.0.0.0

Copyright (c) 1982, 2018, Oracle.  All rights reserved.


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.2.0.0.0

SQL>

LoveHousedeiMac:~ lovehouse$ docker ps   
CONTAINER ID        IMAGE                       COMMAND                  CREATED             STATUS                    PORTS                                            NAMES
39284f79172b        oracle/database:19.2.0-ee   "/bin/sh -c 'exec $O…"   About an hour ago   Up 39 minutes (healthy)   0.0.0.0:1521->1521/tcp, 0.0.0.0:5500->5500/tcp   oracle19c
LoveHousedeiMac:~ lovehouse$ 
LoveHousedeiMac:~ lovehouse$ docker run --rm -ti oracle/database:19.2.0-ee sqlplus pdbadmin/L40uti33Ojk=1@//172.17.0.2:1521/ORCLPDB1

SQL*Plus: Release 19.0.0.0.0 - Production on Sat Feb 16 11:50:21 2019
Version 19.2.0.0.0

Copyright (c) 1982, 2018, Oracle.  All rights reserved.

Last Successful login time: Sat Feb 16 2019 11:50:12 +00:00

Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.2.0.0.0

SQL>

另外注意一下,以主机的方式登陆进去之后,直接sqlplus会报错ORA-12162,是因为docker镜像中没有指定ORACLE_SID,export一下就可以了:

[oracle@39284f79172b admin]$ sqlplus "/ as sysdba"

SQL*Plus: Release 19.0.0.0.0 - Production on Sat Feb 16 12:09:40 2019
Version 19.2.0.0.0

Copyright (c) 1982, 2018, Oracle.  All rights reserved.

ERROR:
ORA-12162: TNS:net service name is incorrectly specified


Enter user-name: 
ERROR:
ORA-12162: TNS:net service name is incorrectly specified


Enter user-name: 
ERROR:
ORA-12162: TNS:net service name is incorrectly specified


SP2-0157: unable to CONNECT to ORACLE after 3 attempts, exiting SQL*Plus
[oracle@39284f79172b admin]$ 
[oracle@39284f79172b admin]$ ps -ef |grep SID
[oracle@39284f79172b admin]$ ps -ef |grep ora_smon |grep -v grep
oracle      65     1  0 11:10 ?        00:00:00 ora_smon_ORCLCDB
[oracle@39284f79172b admin]$ 
[oracle@39284f79172b admin]$ export ORACLE_SID=ORCLCDB
[oracle@39284f79172b admin]$ 
[oracle@39284f79172b admin]$ 
[oracle@39284f79172b admin]$ sqlplus "/ as sysdba"

SQL*Plus: Release 19.0.0.0.0 - Production on Sat Feb 16 12:13:14 2019
Version 19.2.0.0.0

Copyright (c) 1982, 2018, Oracle.  All rights reserved.


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.2.0.0.0

SQL>


小记scn head room

$
0
0

小记一下前段时间看的scn head room的问题。

1. scn的最大值。scn的表示是SCN_WRAP.SCN_BASE,最大值是 ffff.ffffffff,即65535.4294967295,也就是每当scn_base到ffffffff(或者说4294967295)的时候,scn wrap翻一位。因此最大值是:65535*4294967295=281470681677825(281万亿)

2. scn head room的问题,并不是到达了scn的最大值(281万亿),而是接近或者达到了当前最大可用scn。

3. 当前最大可用scn定义,是以1988年1月1号为起点,值是1,然后以每秒递增16384(也就是16k)。得出当前最大可用的scn值。

4. 如果当前的scn(即dbms_flashback.get_system_change_number ),达到了当前最大可用scn,此时就hang了,等待下一秒新的SCN上限。

5. 安全值是当前scn,如果以每秒16k的速度增长,到当前最大可用scn需要超过62天的时间。

2012年之后的补丁或者新版本的数据库,带了3个防止dblink传播高scn的隐含参数:

_external_scn_logging_threshold_seconds-- 默认86400秒,即24小时,表示如果受到外来scn影响跳变时间超过24小时,就在alertlog记录一条告警信息。
_external_scn_rejection_delta_threshold_minutes --外来scn每分钟变化超过xx值,就拒绝。默认值为0,不拒绝
_external_scn_rejection_threshold_hours --默认值24小时,外来scn超过本地最大可用scn24小时,就拒绝。

11.2.0.2之后,默认每秒递增的速率是32k(即_max_reasonable_scn_rate=32768),不再是16k。可以解决上面3的问题。

SCN Compatibility问题:
打过补丁或者12c的数据库,

set serverout on
declare
 EFFECTIVE_AUTO_ROLLOVER_TS date;
 TARGET_COMPAT number;
 IS_ENABLED boolean;
 begin
  dbms_scn.GETSCNAUTOROLLOVERPARAMS(EFFECTIVE_AUTO_ROLLOVER_TS,TARGET_COMPAT,IS_ENABLED);
  dbms_output.put_line('EFFECTIVE_AUTO_ROLLOVER_TS='||to_char(EFFECTIVE_AUTO_ROLLOVER_TS,'yyyy-mm-dd hh24:mi:ss'));
  dbms_output.put_line('TARGET_COMPAT=' || TARGET_COMPAT);
 if(IS_ENABLED)then
  dbms_output.put_line('IS_ENABLED IS TURE'); 
 else 
  dbms_output.put_line('IS_ENABLED IS FALSE'); 
 end if;
 end;
 /
 
EFFECTIVE_AUTO_ROLLOVER_TS=2019-06-23 00:00:00
TARGET_COMPAT=3
IS_ENABLED IS TURE
 
PL/SQL procedure successfully completed
 
SQL>

可以Alter Database Set SCN Compatibility 3;但是降低需要重启。注意,3是96k

参考:
《SCN Head Room 原理全解析》
SCN Compatibility问题汇总-2019年6月23日

隐式转换检查

$
0
0

数据库中是隐式转换往往是性能的杀手,下面2个语句分别可以在sql server和oracle查询到目前在内存中的,使用了隐式转换的SQL:

  • sql server 隐式转换:
  • DECLARE @dbname SYSNAME  
    SET @dbname = QUOTENAME(DB_NAME());  
    WITH XMLNAMESPACES(DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')  
    SELECT stmt.value('(@StatementText)[1]', 'varchar(max)') AS SQL_Text ,  
             t.value('(ScalarOperator/Identifier/ColumnReference/@Schema)[1]', 'varchar(128)') AS SchemaName ,  
             t.value('(ScalarOperator/Identifier/ColumnReference/@Table)[1]', 'varchar(128)') AS TableName ,  
             t.value('(ScalarOperator/Identifier/ColumnReference/@Column)[1]', 'varchar(128)') AS ColumnName ,  
             ic.DATA_TYPE AS ConvertFrom ,  
             ic.CHARACTER_MAXIMUM_LENGTH AS ConvertFromLength ,  
             t.value('(@DataType)[1]', 'varchar(128)') AS ConvertTo ,  
             t.value('(@Length)[1]', 'int') AS ConvertToLength ,  
             query_plan  
    FROM sys.dm_exec_cached_plans AS cp  
    --FROM sys.dm_exec_query_stats qs
    CROSS APPLY sys.dm_exec_query_plan(plan_handle) AS qp  
    CROSS APPLY query_plan.nodes('/ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple')AS batch ( stmt )  
    CROSS APPLY stmt.nodes('.//Convert[@Implicit="1"]') AS n ( t )  
    JOIN INFORMATION_SCHEMA.COLUMNS AS ic ON QUOTENAME(ic.TABLE_SCHEMA) = t.value('(ScalarOperator/Identifier/ColumnReference/@Schema)[1]', 'varchar(128)')  
        AND QUOTENAME(ic.TABLE_NAME) = t.value('(ScalarOperator/Identifier/ColumnReference/@Table)[1]','varchar(128)')  
        AND ic.COLUMN_NAME = t.value('(ScalarOperator/Identifier/ColumnReference/@Column)[1]','varchar(128)')  
    WHERE t.exist('ScalarOperator/Identifier/ColumnReference[@Database=sql:variable("@dbname")][@Schema!="[sys]"]') = 1 
    and ic.DATA_TYPE != t.value('(@DataType)[1]', 'varchar(128)')

  • oracle隐式转换:
  • SELECT sql_id,plan_hash_value FROM v$sql_plan x WHERE  x.FILTER_PREDICATES LIKE '%INTERNAL_FUNCTION%'
    GROUP BY sql_id,plan_hash_value

    数据库序列的监控

    $
    0
    0

    需要监控数据库的序列,在达到最大值前,进行告警。特别是mysql,往往因为字段的定义和auto incremental的定义不同,导致各自的上限不同。

    Oracle:

    SELECT 
    x.*,
    CASE WHEN increment_by<0
     THEN round(last_number/min_value*100,4)
    WHEN increment_by>0
      THEN round(last_number/max_value*100,4)
     ELSE 0
      END 
    AS percent_usage
    from DBA_SEQUENCES x  WHERE cycle_flag='N'
    ORDER BY percent_usage DESC;

    SQL Server:

    SELECT 
    max(
    CASE WHEN increment_by<0
     THEN round(last_number/min_value*100,4)
    WHEN increment_by>0
      THEN round(last_number/max_value*100,4)
     ELSE 0
      END 
    ) AS percent_usage
    from DBA_SEQUENCES x  WHERE cycle_flag='N'
    ORDER BY percent_usage DESC;

    pg:

    --(1)初始化部署:
    --(1.1)数据库内部署表和函数(注,如果一个pg实例中有多个数据库需要监控,需要部署到多个库):
    drop table oracleblog_pg_sequence;
    create table oracleblog_pg_sequence
    (
     record_time timestamp with time zone,
     sequence_name TEXT   , 
     last_value    bigint , 
     start_value   bigint , 
     increment_by  bigint , 
     max_value     bigint , 
     min_value     bigint , 
     cache_value   bigint , 
     log_cnt       bigint , 
     is_cycled     boolean, 
     is_called     boolean,
     sequence_schema TEXT,
     table_name   text,
     column_name TEXT,
     data_type text,
     datatype_maxval bigint,
     datatype_minval bigint,
     cap_value   bigint 
    );
    
    
    DROP function oracleblog_get_seqval();
    CREATE OR REPLACE FUNCTION oracleblog_get_seqval() RETURNS void AS $sequence_values$
    DECLARE
       nsp_name TEXT;
       seq_name TEXT;
    BEGIN
       EXECUTE 'truncate table oracleblog_pg_sequence';
       FOR nsp_name, seq_name IN
           SELECT nspname::text, relname::text
              FROM pg_class 
              JOIN pg_namespace
              ON pg_class.relnamespace = pg_namespace.oid WHERE relkind='S'
       LOOP
           EXECUTE 'INSERT into oracleblog_pg_sequence(sequence_name,last_value,start_value,increment_by,max_value,min_value,cache_value,log_cnt,is_cycled,is_called,sequence_schema) SELECT x.*,'
                   || ''''
                   || nsp_name
                   || ''''
                   ||' from '
                   || nsp_name
                   ||'.'
                   ||seq_name
                   ||' x';
       END LOOP;
       
    update oracleblog_pg_sequence a
    set 
    table_name=(select table_name from INFORMATION_SCHEMA.COLUMNS b where SPLIT_PART(COLUMN_DEFAULT, '''', 2) = SEQUENCE_NAME and sequence_schema=table_schema),
    column_name=(select column_name from INFORMATION_SCHEMA.COLUMNS b where SPLIT_PART(COLUMN_DEFAULT, '''', 2) = SEQUENCE_NAME and sequence_schema=table_schema),
    data_type=(select data_type from INFORMATION_SCHEMA.COLUMNS b where SPLIT_PART(COLUMN_DEFAULT, '''', 2) = SEQUENCE_NAME and sequence_schema=table_schema),
    datatype_maxval=(select
    CASE lower(DATA_TYPE)
             when 'smallint'  then     32767
             when 'integer'   then     2147483647
             when 'serial'    then     2147483647
             when 'bigint'    then     9223372036854775807
             when 'bigserial' then     9223372036854775807
             else  null 
             end from INFORMATION_SCHEMA.COLUMNS b where SPLIT_PART(COLUMN_DEFAULT, '''', 2) = SEQUENCE_NAME and sequence_schema=table_schema),
    datatype_minval=(select
    CASE lower(DATA_TYPE)
             when 'smallint'   then      -32767
             when 'integer'    then      -2147483647
             when 'serial'     then      1
             when 'bigint'     then      -9223372036854775807
             when 'bigserial'  then      1
             else  null 
             end from INFORMATION_SCHEMA.COLUMNS b where SPLIT_PART(COLUMN_DEFAULT, '''', 2) = SEQUENCE_NAME and sequence_schema=table_schema); 
             
    update oracleblog_pg_sequence 
    set cap_value=(
    case when INCREMENT_BY < 0 then 
              (case  when min_value>=datatype_minval then min_value
                     when min_value<=datatype_minval then datatype_minval
               end)      
         when INCREMENT_BY > 0 then 
              (case  when max_value>=datatype_maxval then datatype_maxval
                     when max_value<=datatype_maxval then max_value
               end)         
    end),
    record_time=now()
    ;
              
    END;
    $sequence_values$
    LANGUAGE plpgsql;
    
    --(1.2)在没有安装pgAgent的环境下,用crontab实现定时刷新(注,如果一个pg实例中有多个数据库需要监控,需要部署多个crontab,每个crontab的-d参数后跟不同的库):
    cat /data001/PRD/postgres/9.6.2/home/postgres/crontab_script/fresh_oracleblog_pg_sequence.sh
    #!/bin/bash
    /data/PRD/postgres/base/9.6.2/bin/psql -d dbinfo -c "select oracleblog_get_seqval()"
    chmod +x /data001/PRD/postgres/9.6.2/home/postgres/crontab_script/fresh_oracleblog_pg_sequence.sh
    
    crontab -l
    01 * * * *  /bin/bash /data001/PRD/postgres/9.6.2/home/postgres/crontab_script/fresh_oracleblog_pg_sequence.sh
    
    
    --(2)给zabbix用户授权:
    grant select on oracleblog_pg_sequence to zabbix;
    
    --(3)查询结果:
    --(3.1)用于监控语句:
    select 
    max(round((1-(cap_value-last_value)::numeric/(cap_value-start_value)::numeric)*100,4)) as max_usage_percent
    from oracleblog_pg_sequence where is_cycled='f';
    
    
    --(3.2)平时运维检查:
    select 
    SEQUENCE_NAME,
    last_value,
    start_value,
    increment_by,
    cap_value,
    round((1-(cap_value-last_value)::numeric/(cap_value-start_value)::numeric)*100,4) as usage_percent
    from oracleblog_pg_sequence where  is_cycled='f' order by usage_percent desc;

    MySQL:

    CREATE DEFINER=`root`@`localhost` PROCEDURE `proc_awr_getauto_increment_status`()
    BEGIN
        TRUNCATE TABLE myawr.auto_increment_status;
        INSERT INTO myawr.auto_increment_status (clock, table_schema, table_name,auto_increment_increment,auto_increment_offset,auto_increment_max,auto_increment_used)
        SELECT now() AS clock, b.table_schema, b.table_name
            ,(select VARIABLE_VALUE from performance_schema.global_variables where VARIABLE_NAME = 'auto_increment_increment') as auto_increment_increment
            ,(select VARIABLE_VALUE from performance_schema.global_variables where VARIABLE_NAME = 'auto_increment_offset') as auto_increment_offset
            , CASE 
                WHEN COLUMN_TYPE LIKE 'bigint%' THEN 9223372036854775808
                WHEN COLUMN_TYPE LIKE 'int(%)' THEN 2147483647
                WHEN COLUMN_TYPE LIKE 'int(%) unsigned' THEN 4294967295
                WHEN COLUMN_TYPE LIKE 'MEDIUMINT(%)' THEN 8388607
                WHEN COLUMN_TYPE LIKE 'MEDIUMINT(%) unsigned' THEN 16777215
                WHEN COLUMN_TYPE LIKE 'SMALLINT(%)' THEN 32767
                WHEN COLUMN_TYPE LIKE 'SMALLINT(%) unsigned' THEN 65535
                WHEN COLUMN_TYPE LIKE 'TINYINT(%)' THEN 127
                WHEN COLUMN_TYPE LIKE 'TINYINT(%) unsigned' THEN 255
                ELSE 'other'
            END AS auto_increment_max
            , CASE 
                WHEN COLUMN_TYPE LIKE 'bigint%' THEN format(b.auto_increment / 9223372036854775808 * 100, 0)
                WHEN COLUMN_TYPE LIKE 'int(%)' THEN format(b.auto_increment / 2147483647 * 100, 0)
                WHEN COLUMN_TYPE LIKE 'int(%) unsigned' THEN format(b.auto_increment / 4294967295 * 100, 0)
                WHEN COLUMN_TYPE LIKE 'MEDIUMINT(%)' THEN format(b.auto_increment / 8388607 * 100, 0)
                WHEN COLUMN_TYPE LIKE 'MEDIUMINT(%) unsigned' THEN format(b.auto_increment / 16777215 * 100, 0)
                WHEN COLUMN_TYPE LIKE 'SMALLINT(%)' THEN format(b.auto_increment / 32767 * 100, 0)
                WHEN COLUMN_TYPE LIKE 'SMALLINT(%) unsigned' THEN format(b.auto_increment / 65535 * 100, 0)
                WHEN COLUMN_TYPE LIKE 'TINYINT(%)' THEN format(b.auto_increment / 127 * 100, 0)
                WHEN COLUMN_TYPE LIKE 'TINYINT(%) unsigned' THEN format(b.auto_increment / 255 * 100, 0)
                ELSE '100'
            END AS auto_increment_used
        FROM information_schema.columns a
            JOIN information_schema.tables b
            ON a.table_name = b.table_name
                AND a.table_schema = b.table_schema
        WHERE EXTRA = 'auto_increment'
        ORDER BY auto_increment_used + 0 DESC
        LIMIT 10;
    
    CREATE DEFINER=`root`@`localhost` EVENT `event_awr_getauto_increment_status` ON SCHEDULE EVERY 1 HOUR STARTS '2019-04-17 11:45:34' ON COMPLETION PRESERVE ENABLE DO call myawr.proc_awr_getauto_increment_status()

    MySQL waiting for metadata lock的分析

    $
    0
    0

    处理waiting for metadata lock,需要:

  • 1. 平时打开performance_schema(以下简称PS)的instruments。
  • 2. 查询PS.metadata_locks ,找到状态为PENDING的thread。
  • 3. 查询PS.threads,关联PS.metadata_locks中的thread_id, 找到PROCESSLIST_ID,PROCESSLIST_ID即为show processlist中的metadata lock的holder。
  • 注意,默认情况下,instruments是没有打开的:

    mysql> select * from performance_schema.setup_instruments WHERE NAME = 'wait/lock/metadata/sql/mdl';
    +----------------------------+---------+-------+
    | NAME                       | ENABLED | TIMED |
    +----------------------------+---------+-------+
    | wait/lock/metadata/sql/mdl | NO      | NO    |
    +----------------------------+---------+-------+
    1 row in set (0.00 sec)
    
    mysql>

    如下的方式可以打开instruments:

    mysql> UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME = 'wait/lock/metadata/sql/mdl';
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
    
    mysql>  
    mysql> select * from performance_schema.setup_instruments WHERE NAME = 'wait/lock/metadata/sql/mdl';
    +----------------------------+---------+-------+
    | NAME                       | ENABLED | TIMED |
    +----------------------------+---------+-------+
    | wait/lock/metadata/sql/mdl | YES     | NO    |
    +----------------------------+---------+-------+
    1 row in set (0.00 sec)
    
    mysql>

    我们来测试一下,如何利用PS的instruments找到metadata lock的holder:

    session 1:

    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> delete from orasup_test1;
    Query OK, 131072 rows affected (0.48 sec)
    
    mysql>

    session 3:

    mysql> begin;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> drop table orasup_test1
    --hang

    session 2: (此时,在查innodb_lock_waits等表是看不到信息的,因为innodb_lock_waits是行锁,而metadata lock是表锁。)

    mysql> select * from sys.innodb_lock_waits \G;
    Empty set (0.00 sec)
    
    mysql> 
    mysql> select * from information_schema.INNODB_LOCK_WAITS;
    Empty set (0.00 sec)
    
    mysql> select * from information_schema.INNODB_LOCKS;
    Empty set (0.00 sec)
    
    mysql> 
    mysql>

    session 2:(需要从PS.metadata_locks 进行查询)

    mysql> select * from performance_schema.metadata_locks where OBJECT_NAME='orasup_test1' \G;
    *************************** 1. row ***************************
              OBJECT_TYPE: TABLE
            OBJECT_SCHEMA: myawr
              OBJECT_NAME: orasup_test1
    OBJECT_INSTANCE_BEGIN: 140695273259776
                LOCK_TYPE: EXCLUSIVE
            LOCK_DURATION: TRANSACTION
              LOCK_STATUS: PENDING
                   SOURCE: sql_parse.cc:5776
          OWNER_THREAD_ID: 525483
           OWNER_EVENT_ID: 21
    *************************** 2. row ***************************
              OBJECT_TYPE: TABLE
            OBJECT_SCHEMA: myawr
              OBJECT_NAME: orasup_test1
    OBJECT_INSTANCE_BEGIN: 140694337352464
                LOCK_TYPE: SHARED_WRITE
            LOCK_DURATION: TRANSACTION
              LOCK_STATUS: GRANTED
                   SOURCE: sql_parse.cc:5776
          OWNER_THREAD_ID: 523569
           OWNER_EVENT_ID: 194
    2 rows in set (0.00 sec)
    
    ERROR: 
    No query specified
    
    mysql> 
    mysql> 
    mysql> 
    mysql> 
    mysql> show processlist;
    +--------+-----------------+-------------------+----------+-------------+---------+---------------------------------------------------------------+-------------------------+
    | Id     | User            | Host              | db       | Command     | Time    | State                                                         | Info                    |
    +--------+-----------------+-------------------+----------+-------------+---------+---------------------------------------------------------------+-------------------------+
    |      1 | event_scheduler | localhost         | NULL     | Daemon      |       6 | Waiting for next activation                                   | NULL                    |
    |  28368 | dbsync          | 10.10.1.75:13766  | NULL     | Binlog Dump | 1330136 | Master has sent all binlog to slave; waiting for more updates | NULL                    |
    | 521693 | itsm            | 10.10.2.102:45586 | dji_itsm | Sleep       |     284 |                                                               | NULL                    |
    | 522968 | itsm            | 10.10.1.175:55165 | dji_itsm | Sleep       |     166 |                                                               | NULL                    |
    | 523305 | root            | localhost         | myawr    | Sleep       |    5393 |                                                               | NULL                    |
    | 523542 | root            | localhost         | myawr    | Sleep       |      35 |                                                               | NULL                    |
    | 523950 | itsm            | 10.10.2.102:46490 | dji_itsm | Sleep       |     168 |                                                               | NULL                    |
    | 524308 | itsm            | 10.10.1.175:55619 | dji_itsm | Sleep       |     165 |                                                               | NULL                    |
    | 524531 | itsm            | 10.10.2.102:46685 | dji_itsm | Sleep       |      16 |                                                               | NULL                    |
    | 524718 | itsm            | 10.10.1.175:55755 | dji_itsm | Sleep       |      26 |                                                               | NULL                    |
    | 524858 | itsm            | 10.10.1.175:55797 | dji_itsm | Sleep       |       0 |                                                               | NULL                    |
    | 524873 | itsm            | 10.10.1.175:55806 | dji_itsm | Sleep       |     143 |                                                               | NULL                    |
    | 525070 | itsm            | 10.10.2.102:46883 | dji_itsm | Sleep       |      16 |                                                               | NULL                    |
    | 525309 | itsm            | 10.10.2.102:46984 | dji_itsm | Sleep       |       1 |                                                               | NULL                    |
    | 525456 | root            | localhost         | myawr    | Query       |      21 | Waiting for table metadata lock                               | drop table orasup_test1 |
    | 525496 | itsm            | 10.10.2.102:47050 | dji_itsm | Sleep       |     174 |                                                               | NULL                    |
    | 525497 | itsm            | 10.10.2.102:47051 | dji_itsm | Sleep       |       0 |                                                               | NULL                    |
    | 525498 | itsm            | 10.10.2.102:47052 | dji_itsm | Sleep       |     301 |                                                               | NULL                    |
    | 525522 | itsm            | 10.10.1.175:56005 | dji_itsm | Sleep       |       1 |                                                               | NULL                    |
    | 525545 | root            | localhost         | NULL     | Query       |       0 | starting                                                      | show processlist        |
    +--------+-----------------+-------------------+----------+-------------+---------+---------------------------------------------------------------+-------------------------+
    20 rows in set (0.00 sec)
    
    mysql> 
    mysql> 
    mysql> select * from performance_schema.threads where thread_id='523569'\G;
    *************************** 1. row ***************************
              THREAD_ID: 523569
                   NAME: thread/sql/one_connection
                   TYPE: FOREGROUND
         PROCESSLIST_ID: 523542
       PROCESSLIST_USER: root
       PROCESSLIST_HOST: localhost
         PROCESSLIST_DB: myawr
    PROCESSLIST_COMMAND: Sleep
       PROCESSLIST_TIME: 62
      PROCESSLIST_STATE: NULL
       PROCESSLIST_INFO: NULL
       PARENT_THREAD_ID: NULL
                   ROLE: NULL
           INSTRUMENTED: YES
                HISTORY: YES
        CONNECTION_TYPE: Socket
           THREAD_OS_ID: 17276
    1 row in set (0.00 sec)
    
    ERROR: 
    No query specified
    
    mysql> kill 523542;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql>

    session 3:

    mysql> drop table orasup_test1;
    Query OK, 0 rows affected (1 min 3.79 sec)
    
    mysql>

    阿里云关于MySQL数据库myisam的支持

    $
    0
    0

    最近在做一个阿里云跨账号的数据库迁移,这个库是和论坛相关的,用的是Discuz程序,数据库中大部分表都是innodb引擎,但是有一张表是使用了myisam引擎,用于记录帖子和楼层。

    mysql> show create table abc_ddxid_ppiy_gdsitnpl \G;
    *************************** 1. row ***************************
           Table: abc_ddxid_ppiy_gdsitnpl
    Create Table: CREATE TABLE `abc_ddxid_ppiy_gdsitnpl` (
      `pid` int(10) unsigned NOT NULL,
      `fid` mediumint(8) unsigned NOT NULL DEFAULT '0',
      `tid` mediumint(8) unsigned NOT NULL DEFAULT '0',
      `first` tinyint(1) NOT NULL DEFAULT '0',
      `floorid` int(8) unsigned NOT NULL AUTO_INCREMENT,
      PRIMARY KEY (`tid`,`floorid`),
      UNIQUE KEY `pid` (`pid`),
      KEY `fid` (`fid`),
      KEY `first` (`tid`,`first`)
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED

    这里可以看到tid是帖子号,floorid是楼层,这里用了一个myisam的复合索引第二列作为自增值,到的分组自增值的效果。

    见mysql官方文档:3.6.9 Using AUTO_INCREMENT

    对于分组自增值,在innodb和myisam的不同行为,可以用下面的测试过程演示:

    mysql> show variables like '%engine%';
    +----------------------------+--------+
    | Variable_name              | Value  |
    +----------------------------+--------+
    | default_storage_engine     | InnoDB |
    | default_tmp_storage_engine | InnoDB |
    | storage_engine             | InnoDB |
    +----------------------------+--------+
    3 rows in set (0.00 sec)
    
    mysql> 
    mysql> 
    mysql> CREATE TABLE myisam_animals (
        ->     grp ENUM('fish','mammal','bird') NOT NULL,
        ->     id MEDIUMINT NOT NULL AUTO_INCREMENT,
        ->     name CHAR(30) NOT NULL,
        ->     PRIMARY KEY (grp,id)
        -> ) ENGINE=MyISAM;
    Query OK, 0 rows affected, 2 warnings (0.00 sec)
    
    mysql> 
    mysql> 
    mysql> INSERT INTO myisam_animals (grp,name) VALUES
        ->     ('mammal','dog'),('mammal','cat'),
        ->     ('bird','penguin'),('fish','lax'),('mammal','whale'),
        ->     ('bird','ostrich');
    Query OK, 6 rows affected (0.01 sec)
    Records: 6  Duplicates: 0  Warnings: 0
    
    mysql> 
    mysql> 
    mysql> SELECT * FROM myisam_animals ORDER BY grp,id;
    +--------+----+---------+
    | grp    | id | name    |
    +--------+----+---------+
    | fish   |  1 | lax     |
    | mammal |  1 | dog     |
    | mammal |  2 | cat     |
    | mammal |  3 | whale   |
    | bird   |  1 | penguin |
    | bird   |  2 | ostrich |
    +--------+----+---------+
    6 rows in set (0.01 sec)
    
    mysql> 
    mysql> 
    mysql> 
    mysql> 
    mysql> 
    mysql> 
    mysql> 
    mysql> CREATE TABLE innodb_animals (
        ->     grp ENUM('fish','mammal','bird') NOT NULL,
        ->     id MEDIUMINT NOT NULL AUTO_INCREMENT,
        ->     name CHAR(30) NOT NULL,
        ->     PRIMARY KEY (id,grp)
        -> ) engine=innodb;
    Query OK, 0 rows affected (0.01 sec)
    
    mysql> 
    mysql> INSERT INTO innodb_animals (grp,name) VALUES
        ->     ('mammal','dog'),('mammal','cat'),
        ->     ('bird','penguin'),('fish','lax'),('mammal','whale'),
        ->     ('bird','ostrich');
    Query OK, 6 rows affected (0.01 sec)
    Records: 6  Duplicates: 0  Warnings: 0
    
    mysql> 
    mysql> 
    mysql> SELECT * FROM innodb_animals ORDER BY grp,id;
    +--------+----+---------+
    | grp    | id | name    |
    +--------+----+---------+
    | fish   |  4 | lax     |
    | mammal |  1 | dog     |
    | mammal |  2 | cat     |
    | mammal |  5 | whale   |
    | bird   |  3 | penguin |
    | bird   |  6 | ostrich |
    +--------+----+---------+
    6 rows in set (0.00 sec)
    
    mysql>

    阿里云RDS关于MySQL数据库的myisam引擎的支持,规律如下:

    (1)MySQL 5.6的版本
    a)create table engine=myisam 且用到分组自增序列,那么不会进行转换。
    b)create table engine=myisam 不用自增分组序列,那么内部进行转换,转成innodb

    注,用dts迁移整个数据库,迁移之后,在目标库仍保持原状,innodb的表还是innodb的,利用了分组自增序列的myisam的表还是myisam的,原始表如果创建的时候,手动写myisam但是没有用到分组自增序列,那么在创建的时候,就自动转成innodb,因此dts迁移过去之后,也仍然是innodb。

    (2)MySQL 5.7版本以上
    已经不支持myisam,create table engine=myisam 报错,已经不能创建。
    如果用的是innodb创建的,非分组的自增序列,需要实现分组自增功能,需要改造代码如下:

    mysql> SELECT (@I := CASE
        ->           WHEN @GRP = GRP THEN
        ->            @I + 1
        ->           ELSE
        ->            1
        ->         END) ROWNUM,
        ->        innodb_animals.NAME,
        ->        (@GRP := GRP)
        ->   FROM innodb_animals, (SELECT @I := 0, @GRP := '') AS A
        ->  GROUP BY GRP, ID;
    +--------+---------+---------------+
    | ROWNUM | NAME    | (@GRP := GRP) |
    +--------+---------+---------------+
    |      1 | lax     | fish          |
    |      1 | dog     | mammal        |
    |      2 | cat     | mammal        |
    |      3 | whale   | mammal        |
    |      1 | penguin | bird          |
    |      2 | ostrich | bird          |
    +--------+---------+---------------+
    6 rows in set (0.00 sec)

    读《混沌工程》有感

    $
    0
    0

    十一期间,读了这本《混沌工程:Netflix系统稳定性之道》。

    这本书很小,但是带来的很多理念还是新的。以下,是一些感悟:

    (1)混沌工程更多是面向分布式系统,微服务,云原生的系统。本身就是假定系统是不稳定的,程序是需要面向失败的设计(Design for failure)。

    (2)混沌工程有点像测试,但它不是传统测试,不是故障注入。故障注入的故障是可预知的,可枚举的。而混沌工程是探索性的,他不仅仅可以注入错误,还能注入正常但激增高于平常的流量,从而判断在降级熔断某个系统的时候,是否对其上下游的系统有相关影响,是否会引发雪崩现象。

    (3)混沌工程是复杂系统的改进学科,有2个前提,如果已经很明确某个操作会导致故障,那么这个操作就仅仅是故障注入,而不是混沌工程;如果没有配套的监控系统,那么也无法量化观测。

    (4)康威定律:”设计系统的架构受制于产生这些设计的组织的沟通结构。”—— 即系统设计本质上反映了企业的组织机构。系统各个模块间的接口也反映了企业各个部门之间的信息流动和合作方式。阿里能搞中台,google能搞SRE,Netflix能搞无运维人员的系统运维,都是其内部组织架构的沟通方式,在IT系统中的体现。

    (5)传统架构师负责理解组织中的各个部分如何组成系统以及他们是如何交互,但是大型分布式系统中人类难以胜任这个角色(见下)。微服务牺牲了可理解性和掌控,这是混沌工程发挥作用的地方。

    (6)牛鞭效应:在一条供应链中,消费市场需求的微小变化如何被一级级放大到制造商、首级供应商、次级供应商等。例如计算机市场需求预测轻微增长2%,转化到戴尔(制造商)时可能成了5%,传递到英特尔(首级供应商)时则可能是10%,而到了替英特尔生产制造处理器的设备商(次级供应商)时则可能变为20%。简言之,越是处于供应链的后端,需求变化幅度越大。由于这种需求放大效应的影响,供应方往往维持比需求方更高的库存水平或者生产准备计划。牛鞭效应不仅仅发生在供应链中,在底层硬件的微小波动,都会造成上层操作系统、数据库系统、应用程序的越来越大的波动。比如vmware做vmotion丢1、2个包,导致数据库集群认为主机通信有问题,进而引发集群切换,切换期间数据库不可用,导致业务中断好几分钟。另外,牛鞭效应,Bullwhip effect,不是Bull Penis effect。:-P

    (7)哪些服务有状态:数据库服务(保持配置设置,或者记录生产数据),配置服务(静态配置,或者动态配置如etcd),隐藏的状态(如auto scaling的个数,集群状态,交换机路由器)。混沌工程的工具:FITchaos monkey(破坏节点)Chaos Gorilla(破坏整个az)chaos kong(破坏整个region)chaos lambdaBlockade, ChAPChaosBlade

    (8)和传统的测试不同,混沌工程需要靠近生产环境,需要在生产或靠近生产的环境中实验。不愿意在生产上实验的原因是,看到系统还不具备弹性能力,害怕不能立即终止,害怕爆炸半径过大。

    (9)混沌工程的原则:建立稳定态的假设;用多样性的现实世界事件做验证;在生产环境进行实验;自动化实验以持续运行;最小化爆炸半径。

    (10)混沌工程的成熟程度,Netflix总结了两个维度,一个是复杂度,一个就是接受度(书上叫熟练度和应用度,但是我觉得还是翻译成复杂度和接受度比较信达雅)。
    前者表示的是混沌工程能有多复杂,而后者则表示的是混沌工程被团队的接受程度。
    (10.1)复杂度分成4个等级:
    初级

  • 试验没有在生产中进行
  • 进程被收工管理
  • 结果只反映系统 metric,没有业务的
  • 只有简单的事件进行试验
  • 简单

  • 试验可以在类生产环境中进行
  • 能自动启动执行,但需要人工监控和终止
  • 结果能反应一些聚合的业务 metric
  • 一些扩展的事件譬如网络延迟可以进行试验
  • 结果可以手工汇总和聚合
  • 试验是预先定义好的
  • 有一些工具能进行历史对照
  • 复杂

  • 试验直接在生产环境中进行
  • 启动,执行,结果分析,终止都是自动完成
  • 试验框架集成在持续发布
  • 业务 metrics 会在实验组和控制组进行比较
  • 一些组合错误或者服务级别影响的事件可以进行试验
  • 结果一直可以追踪
  • 有工具可以更好的交互式的对比试验和控制组
  • 高级

  • 试验在每个开发步骤和任意环境都进行
  • 设计,执行和提前终止这些全部都是自动化的
  • 框架跟 A/B 或者其他试验系统整合
  • 一个事件譬如更改使用模式和返回值或者状态变更开始进行试验
  • 试验包括动态作用域和影响,可以找到突变点
  • 通过试验结果能保护资产流失
  • 容量预测可以通过试验分析提前得出
  • 试验结果可以区分不同服务的临界状态
  • (10.2)接受度分成4个等级:
    在暗处

  • 相关项目不被批准
  • 很少系统被覆盖
  • 很少或者没有团队有意识
  • 早期接受者不定期的进行试验
  • 有投入

  • 试验被被官方批准
  • 部分资源被用于实践
  • 多个团队有兴趣并投入
  • 少部分关键服务不定期进行试验
  • 接受

  • 有专门的 team 进行混沌工程
  • 应急响应被集成到框架,从而可以创建回归试验
  • 多数关键系统定期进行混沌试验
  • 一些试验验证会在应急响应或者游戏时间被临时执行
  • 文化

  • 所有关键服务都有频繁的混沌试验
  • 大多数非关键服务定期进行
  • 混沌试验已经是工程师的日常工作
  • 默认所有系统组件都必须参与,如果不想进行,需要有正当的理由

  • (11)混沌工程从2010年演进发展的时间线:

    2010年 Netflix 内部开发了 AWS 云上随机终止 EC2 实例的混沌实验工具: Chaos Monkey
    2011年 Netflix 释出了其猴子军团工具集: Simian Army
    2012年 Netflix 向社区开源由 Java 构建 Simian Army,其中包括 Chaos Monkey V1 版本
    2014年 Netflix 开始正式公开招聘 Chaos Engineer
    2014年 Netflix 提出了故障注入测试(FIT),利用微服务架构的特性,控制混沌实验的爆炸半径
    2015年 Netflix 释出 Chaos Kong ,模拟AWS区域(Region)中断的场景
    2015年 Netflix 和社区正式提出混沌工程的指导思想 – Principles of Chaos Engineering
    2016年 Kolton Andrus(前 Netflix 和 Amazon Chaos Engineer )创立了 Gremlin ,正式将混沌实验工具商用化
    2017年 Netflix 开源 Chaos Monkey 由 Golang 重构的 V2 版本,必须集成 CD 工具 Spinnaker 来使用
    2017年 Netflix 释出 ChAP (混沌实验自动平台),可视为应用故障注入测试(FIT)的加强版
    2017年 由Netflix 前混沌工程师撰写的新书“混沌工程”在网上出版
    2017年 Russell Miles 创立了 ChaosIQ 公司,并开源了 chaostoolkit 混沌实验框架

    (12)Chaos Engineering: Compaines, People, Tools & Practices(原图,带链接,点击此处

    参考:
    混沌工程简介
    AWS云上混沌工程实践之启动篇
    混沌工程实践经验:如何让系统在生产环境中稳定可靠
    阿里巴巴在混沌工程领域的实践和思考

    延伸阅读:
    Intro to Chaos Engineering
    Resiliency through Failure – Netflix’s Approach to Extreme Availability in the Cloud
    Mastering Chaos – A Netflix Guide to Microservices
    Getting started with Chaos Engineering – Paul Stack
    AWS 云上混沌工程实践之对照实验设计和实施 黄帅
    AWS云上混沌工程实践之可行性评估篇
    鲜为人知的混沌工程,到底哪里好?

    .

    dba将死,云架构师即将到来

    $
    0
    0

    最近在招人,在招人的时候,有不少反思。作为一个dba,我们这个行业的趋势如何,我们的出路如何。

    (一)首先看到的一点是,目前越来越多的公司使用了云服务,自建机房的企业越来越少了。上云之后,很多企业对数据库的使用方式,是直接使用了云厂商提供的RDS,数据库服务,而不是在云上自建虚拟机再安装数据库。

    除非是一些很特殊的场景,需要通过自建来实现,比如跨云的postgresql实时同步、多master的MGR(不过这个aws也实现了)。

    上云、使用rds之后,基本解决了几个问题:
    1. 多可用区的架构,实现了数据库的高可用。
    2. 数据库备份,rds服务本身就自带这个服务。而且可以很方便的实现任意时间点的还原。
    3. 简单的数据库监控。基本的cpu、连接数、内存使用率、主从lag这些监控指标都有了(当然,在深入一些的比如等待事件、长事务、没结束的慢查询、最大的表缓存数量、10分钟内产生的wal大小等等,这些是没有的)。
    4. 数据库迁移,云厂商也提供了很多迁移工具,比如阿里云的dts,aws的dms,可以实现迁移、同步、发布/订阅的功能。

    另外,由于rds提供的只是数据库服务,dba无法登录到数据库主机上,因此主机硬件的一些内核参数调整,也基本无缘了。

    (二)其次,我们可以看的,现在越来越多的公司,特别是互联网公司,采用的是框架式开发,如ORM框架:

    ORM:是一种为了解决面向对象与关系型数据库中数据类型不匹配的技术,它通过描述Java对象与数据库表之间的映射关系,自动将java应用程序中的对象持久化到关系型数据库的表中;
    使用ORM框架后,应用程序不再直接访问底层数据库,而是以面向对象的方式来操作持久化对象,而ORM框架则会通过映射关系将这些面向对象的操作转换成底层的SQL操作;

    本来ORM框架,是用来屏蔽底层数据库的差异的。结果用着用着,就变成开发不用管sql,只需要通过代码实现就可以。到了后来,一段坏sql是怎么来的,如何产生的,能否加hint,开发完全不知道。

    上面的两点,是为了说明一个观点:

    dba工作的主要两块业务:
    系统级,这个工作被云吃掉了。
    代码级,这个工作被框架开发吃掉了。

    另外在和一个朋友聊天的过程得知,越来越多的公司上云之后,已经没有招dba的HC了,大部分的数据库运维工作,可以有运维开发人员一起完成,稍有难度的数据库问题,也可以交由云厂商分析解决。这些公司需要的,只是云管理员,或者云架构师。

    从google trends搜索了dba jobs和dba,可以一定的角度反映这个问题。



    可以看的dba jobs和dba在5年内的热度,是不断降低的。

    那么dba的出路何在?

    我个人认为,在未来5年,除了头部企业,也就是云厂商,其他大部分企业将不需要dba这个职业。dba可以有如下选择:

    1. 去云厂商,利用自己本身对操作系统、网络、存储的熟悉,对数据库技术的熟悉,对数据库内核对熟悉,发挥作用;
    2. 在企业内部,熟悉云架构,熟悉整套基于云服务的持续集成持续发布流程,懂得利用云厂商的各种工具,实现数据流转,成为云架构师;
    3. 在企业内部,结合企业业务,进行数据治理、数据整合、数据建模,挖掘数据价值,成为偏业务的数据架构师;

    另外,随着基于云的微服务和分布式架构兴起,或许混沌工程测试工程师,也是个不错的选择。关于混沌工程,不妨可以看我最近写的这篇博文

    另外,韩锋老师的这篇《DBA职业发展之路》,也推荐给大家。里面非常详细的介绍了dba的职业选择路线。

    云计算、人工智能其实并不遥远,只是我们可能还没察觉到未来已来,就已经被时代抛弃了。你怎么看?


    一次MySQL存储空间撑爆的故障处理和分析

    $
    0
    0

    在一次对线上系统的压测过程中,数据库突然变成了只读状态。我们看了一下,是因为空间在短时间内,被撑爆了。云上的rds数据库,如果在空间打爆的情况下,确实会变成只读的情况。
    我们这个业务,做了中美数据拆分,美国的数据库是在aws上,中国的数据库是在阿里云上,跑同样的一套逻辑。
    可以看到,在短时间内:
    aws云:

    阿里云:

    aws的free storage 迅速下降,最低的时候,只有10多G;而阿里云,已经被打爆,阿里云上的空间阈值是500G,故障的时候,已经突发到了600G,其中主要的占据部分,是红色的部分,也就是临时文件的大小。
    因此,综上,可以看的,在十几分钟内,数据库的空间就被占据了200G到400G不等。一方面,说明数据库的IO能力确实还不错。另外一方面,数据库确实存在了异常。我们需要找到是什么原因导致了空间迅速被消耗的原因。

    在顺便这里说一下aws和和阿里云的监控,可以看的阿里云的空间监控,比aws的信息更加详细,因为aws只有一个free storage的值,而阿里云中,可以看的存储空间猛增的原因,是其中红色部分的临时文件的空间猛增。这为我们后续的分析,提供了很重要的信息。如果光是从aws的监控,我们下手会增加一定的难度。

    我们在当时登录到了aws的rds里面,没有看到insert语句,看到有几个select语句,当时是处于creating sort index,从跑的时间看,一些线程已经跑了700多秒,一些线程跑了200多秒。显然,跑的都不快。虽然值得怀疑,当时感觉不应该会消耗那么高的临时空间。但是但是确实没有其他异常的语句,和开发确认后,kill掉了creating sort index的语句。空间立马就恢复回去了。

    看来引发问题的语句是确认了。接下来需要分析,这个语句为什么会消耗这么多临时空间。因为根据经验,一个排序的语句,一般消耗4,5百M的临时段,也是顶天了。为什么这个语句会消耗那么多的临时空间呢?

    在阿里云中有个CloudDBA,我们可以看到更多的一些信息来辅助分析。
    比如这个“性能趋势”

    可以看到精确的消耗临时文件的值,为438298M。

    另外有个“性能洞察”

    可以看到,当时约有16个active session,是消耗在橙色的creating sort index,并且可以看到绑定变量之后的sql语句。

    SELECT `myabccydf_table`.*
    FROM `myabccydf_table`
    	INNER JOIN `ownddeu_mynameac` ON `ownddeu_mynameac`.`id` = `myabccydf_table`.`item_id`
    WHERE `ownddeu_mynameac`.`published_at` <= NOW() + INTERVAL ? DAY
    ORDER BY `myabccydf_table`.`updated_at` DESC
    LIMIT ?, ?

    当时这个sql语句有使用绑定变量,这边显示了?,我们需要更精确的定位到语句。我们可以从多个地方去找到这个具体的sql。一个是通过慢查询记录的语句,因为这些语句基本需要执行几百秒的时间。一个是我们DBA团队部署的myawr,它会定期的把show processlist的结果打快照下来。另外,我们还可以通过登录aws时候抓到的语句来去看,因为两边的逻辑是一样的,是一套程序跑2个云。所以我们最终抓到了语句是:

    SELECT  `myabccydf_table`.* FROM `myabccydf_table` 
    INNER JOIN `ownddeu_mynameac` ON `ownddeu_mynameac`.`id` = `myabccydf_table`.`item_id` 
    WHERE (`ownddeu_mynameac`.`published_at` <= NOW() + INTERVAL 0 DAY)  
    ORDER BY `myabccydf_table`.`updated_at` desc LIMIT 10 OFFSET 0

    这个语句,分析了一下执行计划,如下:

    mysql> explain SELECT  `myabccydf_table`.* FROM `myabccydf_table` 
        -> INNER JOIN `ownddeu_mynameac` ON `ownddeu_mynameac`.`id` = `myabccydf_table`.`item_id` 
        -> WHERE (`ownddeu_mynameac`.`published_at` <= NOW() + INTERVAL 0 DAY)  
        -> ORDER BY `myabccydf_table`.`updated_at` desc LIMIT 10 OFFSET 0;
    +----+-------------+------------------+--------+---------------+---------+---------+----------------------------------------------+----------+-----------------------------+
    | id | select_type | table            | type   | possible_keys | key     | key_len | ref                                          | rows     | Extra                       |
    +----+-------------+------------------+--------+---------------+---------+---------+----------------------------------------------+----------+-----------------------------+
    |  1 | SIMPLE      | myabccydf_table  | ALL    | NULL          | NULL    | NULL    | NULL                                         | 26875439 | Using where; Using filesort |
    |  1 | SIMPLE      | ownddeu_mynameac | eq_ref | PRIMARY       | PRIMARY | 4       | dji_store_production.myabccydf_table.item_id |        1 | Using where                 |
    +----+-------------+------------------+--------+---------------+---------+---------+----------------------------------------------+----------+-----------------------------+
    2 rows in set (0.01 sec)

    检查了一下这个sql语句返回的函数,也就2000多万行:

    mysql> SELECT  count(*) FROM `myabccydf_table` 
        -> INNER JOIN `ownddeu_mynameac` ON `ownddeu_mynameac`.`id` = `myabccydf_table`.`item_id` 
        -> WHERE (`ownddeu_mynameac`.`published_at` <= NOW() + INTERVAL 0 DAY);
    +----------+
    | count(*) |
    +----------+
    | 26385116 |
    +----------+
    1 row in set (30.85 sec)

    感觉千万级的记录有order by排序,虽然排序字段上没有索引,但是也不应该消耗那么多临时空间用来排序。

    看到语句是个select *,直觉上觉得有问题,不符合开发规范,看了一下表结构:

    mysql> show create table myabccydf_table;
    +-----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    | Table           | Create Table                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
    +-----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    | myabccydf_table | CREATE TABLE `myabccydf_table` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `item_id` int(11) DEFAULT NULL,
      `mymum_mydady_sansy_id` int(11) DEFAULT NULL,
      `quantity` int(11) DEFAULT NULL,
      `created_at` datetime DEFAULT NULL,
      `updated_at` datetime DEFAULT NULL,
      `item_type` varchar(255) NOT NULL,
      PRIMARY KEY (`id`),
      KEY `index_myabccydf_table_on_mymum_mydady_sansy_id` (`mymum_mydady_sansy_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2227662255 DEFAULT CHARSET=utf8 |
    +-----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
    1 row in set (0.00 sec)

    尝试把各个字段依次带入,分别运行了一下。发现如果不带最后一个字段,那么20多秒就能跑出来;

    mysql> SELECT  `myabccydf_table`.id,
        -> `myabccydf_table`.item_id,
        -> `myabccydf_table`.mymum_mydady_sansy_id,
        -> `myabccydf_table`.quantity,
        -> `myabccydf_table`.created_at,
        -> `myabccydf_table`.updated_at 
        -> FROM `myabccydf_table` 
        -> INNER JOIN `ownddeu_mynameac` ON `ownddeu_mynameac`.`id` = `myabccydf_table`.`item_id` 
        -> WHERE (`ownddeu_mynameac`.`published_at` <= NOW() + INTERVAL 0 DAY)  
        -> ORDER BY `myabccydf_table`.`updated_at` desc LIMIT 10 OFFSET 0;
    (结果略)
    10 rows in set (24.30 sec)
    
    mysql>

    而带上了最后那个item_type的字段后,需要6,7分钟才能跑出来:

    mysql> SELECT  `myabccydf_table`.* FROM `myabccydf_table` 
        -> INNER JOIN `ownddeu_mynameac` ON `ownddeu_mynameac`.`id` = `myabccydf_table`.`item_id` 
        -> WHERE (`ownddeu_mynameac`.`published_at` <= NOW() + INTERVAL 0 DAY)  
        -> ORDER BY `myabccydf_table`.`updated_at` desc LIMIT 10 OFFSET 0;
    (结果略)
    10 rows in set (6 min 17.34 sec)
    
    mysql>

    并且在阿里云和aws,都测试了一下。发现跑的时候,都会造成约40左右的空间降低,在跑完之后,空间迅速恢复。
    阿里云:
    阿里云的“监控与告警”面板:

    阿里云的CLoudDBA的“性能趋势”面板:

    aws:
    aws起始状态:

    aws跑的过程中的状态:

    都可以看到,单个线程跑都需要消耗40G左右的临时空间。

    那么,当时在阿里云上看到16个active session都跑在create sort index,消耗400多G的临时文件,也就不足为奇了。

    但是,为什么这个item_type的字段,一旦带上去了,就要消耗那么多的临时文件呢?一个varchar 255的字段长度,有那么大的威力吗?

    和知数堂的徐晨亮聊聊一下,说这个可能和mysql的几种排序方式有关,建议我可以具体分析一下。

    要看采用了那种的排序方式,普通的explain已经无法看到,我通过打开设置了optimizer_trace=on,来进一步分析。

    mysql> select @@optimizer_trace;
    +-------------------------+
    | @@optimizer_trace       |
    +-------------------------+
    | enabled=on,one_line=off |
    +-------------------------+
    
    
    mysql> set session optimizer_trace='enabled=on';
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> 
    mysql> SELECT  `myabccydf_table`.* FROM `myabccydf_table` 
        -> INNER JOIN `ownddeu_mynameac` ON `ownddeu_mynameac`.`id` = `myabccydf_table`.`item_id` 
        -> WHERE (`ownddeu_mynameac`.`published_at` <= NOW() + INTERVAL 0 DAY)  
        -> ORDER BY `myabccydf_table`.`updated_at` desc LIMIT 10 OFFSET 0;
    (结果略)
    10 rows in set (6 min 17.34 sec)
    
    mysql>

    mysql> select * from information_schema.optimizer_trace\G;
    *************************** 1. row ***************************
                                QUERY: SELECT  `myabccydf_table`.* FROM `myabccydf_table` 
    INNER JOIN `ownddeu_mynameac` ON `ownddeu_mynameac`.`id` = `myabccydf_table`.`item_id` 
    WHERE (`ownddeu_mynameac`.`published_at` <= NOW() + INTERVAL 0 DAY)  
    ORDER BY `myabccydf_table`.`updated_at` desc LIMIT 10 OFFSET 0
                                TRACE: {
      "steps": [
        {
          "join_preparation": {
            "select#": 1,
            "steps": [
              {
                "expanded_query": "/* select#1 */ select `myabccydf_table`.`id` AS `id`,`myabccydf_table`.`item_id` AS `item_id`,`myabccydf_table`.`mymum_mydady_sansy_id` AS `mymum_mydady_sansy_id`,`myabccydf_table`.`quantity` AS `quantity`,`myabccydf_table`.`created_at` AS `created_at`,`myabccydf_table`.`updated_at` AS `updated_at`,`myabccydf_table`.`item_type` AS `item_type` from (`myabccydf_table` join `ownddeu_mynameac` on((`ownddeu_mynameac`.`id` = `myabccydf_table`.`item_id`))) where (`ownddeu_mynameac`.`published_at` <= (now() + interval 0 day)) order by `myabccydf_table`.`updated_at` desc limit 0,10"
              }
            ]
          }
        },
        {
          "join_optimization": {
            "select#": 1,
            "steps": [
              {
                "transformations_to_nested_joins": {
                  "transformations": [
                    "JOIN_condition_to_WHERE",
                    "parenthesis_removal"
                  ],
                  "expanded_query": "/* select#1 */ select `myabccydf_table`.`id` AS `id`,`myabccydf_table`.`item_id` AS `item_id`,`myabccydf_table`.`mymum_mydady_sansy_id` AS `mymum_mydady_sansy_id`,`myabccydf_table`.`quantity` AS `quantity`,`myabccydf_table`.`created_at` AS `created_at`,`myabccydf_table`.`updated_at` AS `updated_at`,`myabccydf_table`.`item_type` AS `item_type` from `myabccydf_table` join `ownddeu_mynameac` where ((`ownddeu_mynameac`.`published_at` <= (now() + interval 0 day)) and (`ownddeu_mynameac`.`id` = `myabccydf_table`.`item_id`)) order by `myabccydf_table`.`updated_at` desc limit 0,10"
                }
              },
              {
                "condition_processing": {
                  "condition": "WHERE",
                  "original_condition": "((`ownddeu_mynameac`.`published_at` <= (now() + interval 0 day)) and (`ownddeu_mynameac`.`id` = `myabccydf_table`.`item_id`))",
                  "steps": [
                    {
                      "transformation": "equality_propagation",
                      "resulting_condition": "((`ownddeu_mynameac`.`published_at` <= (now() + interval 0 day)) and multiple equal(`ownddeu_mynameac`.`id`, `myabccydf_table`.`item_id`))"
                    },
                    {
                      "transformation": "constant_propagation",
                      "resulting_condition": "((`ownddeu_mynameac`.`published_at` <= (now() + interval 0 day)) and multiple equal(`ownddeu_mynameac`.`id`, `myabccydf_table`.`item_id`))"
                    },
                    {
                      "transformation": "trivial_condition_removal",
                      "resulting_condition": "((`ownddeu_mynameac`.`published_at` <= (now() + interval 0 day)) and multiple equal(`ownddeu_mynameac`.`id`, `myabccydf_table`.`item_id`))"
                    }
                  ]
                }
              },
              {
                "table_dependencies": [
                  {
                    "table": "`myabccydf_table`",
                    "row_may_be_null": false,
                    "map_bit": 0,
                    "depends_on_map_bits": [
                    ]
                  },
                  {
                    "table": "`ownddeu_mynameac`",
                    "row_may_be_null": false,
                    "map_bit": 1,
                    "depends_on_map_bits": [
                    ]
                  }
                ]
              },
              {
                "ref_optimizer_key_uses": [
                  {
                    "table": "`ownddeu_mynameac`",
                    "field": "id",
                    "equals": "`myabccydf_table`.`item_id`",
                    "null_rejecting": true
                  }
                ]
              },
              {
                "rows_estimation": [
                  {
                    "table": "`myabccydf_table`",
                    "table_scan": {
                      "rows": 25033268,
                      "cost": 392812
                    }
                  },
                  {
                    "table": "`ownddeu_mynameac`",
                    "table_scan": {
                      "rows": 2789,
                      "cost": 97
                    }
                  }
                ]
              },
              {
                "considered_execution_plans": [
                  {
                    "plan_prefix": [
                    ],
                    "table": "`myabccydf_table`",
                    "best_access_path": {
                      "considered_access_paths": [
                        {
                          "access_type": "scan",
                          "rows": 2.5e7,
                          "cost": 5.4e6,
                          "chosen": true
                        }
                      ]
                    },
                    "cost_for_plan": 5.4e6,
                    "rows_for_plan": 2.5e7,
                    "rest_of_plan": [
                      {
                        "plan_prefix": [
                          "`myabccydf_table`"
                        ],
                        "table": "`ownddeu_mynameac`",
                        "best_access_path": {
                          "considered_access_paths": [
                            {
                              "access_type": "ref",
                              "index": "PRIMARY",
                              "rows": 1,
                              "cost": 2.5e7,
                              "chosen": true
                            },
                            {
                              "access_type": "scan",
                              "using_join_cache": true,
                              "rows": 2092,
                              "cost": 1e10,
                              "chosen": false
                            }
                          ]
                        },
                        "cost_for_plan": 3.54e7,
                        "rows_for_plan": 2.5e7,
                        "chosen": true
                      }
                    ]
                  },
                  {
                    "plan_prefix": [
                    ],
                    "table": "`ownddeu_mynameac`",
                    "best_access_path": {
                      "considered_access_paths": [
                        {
                          "access_type": "ref",
                          "index": "PRIMARY",
                          "usable": false,
                          "chosen": false
                        },
                        {
                          "access_type": "scan",
                          "rows": 2789,
                          "cost": 654.8,
                          "chosen": true
                        }
                      ]
                    },
                    "cost_for_plan": 654.8,
                    "rows_for_plan": 2789,
                    "rest_of_plan": [
                      {
                        "plan_prefix": [
                          "`ownddeu_mynameac`"
                        ],
                        "table": "`myabccydf_table`",
                        "best_access_path": {
                          "considered_access_paths": [
                            {
                              "access_type": "scan",
                              "using_join_cache": true,
                              "rows": 2.5e7,
                              "cost": 1.4e10,
                              "chosen": true
                            }
                          ]
                        },
                        "cost_for_plan": 1.4e10,
                        "rows_for_plan": 7e10,
                        "pruned_by_cost": true
                      }
                    ]
                  }
                ]
              },
              {
                "attaching_conditions_to_tables": {
                  "original_condition": "((`ownddeu_mynameac`.`id` = `myabccydf_table`.`item_id`) and (`ownddeu_mynameac`.`published_at` <= (now() + interval 0 day)))",
                  "attached_conditions_computation": [
                  ],
                  "attached_conditions_summary": [
                    {
                      "table": "`myabccydf_table`",
                      "attached": "(`myabccydf_table`.`item_id` is not null)"
                    },
                    {
                      "table": "`ownddeu_mynameac`",
                      "attached": "(`ownddeu_mynameac`.`published_at` <= (now() + interval 0 day))"
                    }
                  ]
                }
              },
              {
                "clause_processing": {
                  "clause": "ORDER BY",
                  "original_clause": "`myabccydf_table`.`updated_at` desc",
                  "items": [
                    {
                      "item": "`myabccydf_table`.`updated_at`"
                    }
                  ],
                  "resulting_clause_is_simple": true,
                  "resulting_clause": "`myabccydf_table`.`updated_at` desc"
                }
              },
              {
                "refine_plan": [
                  {
                    "table": "`myabccydf_table`",
                    "access_type": "table_scan"
                  },
                  {
                    "table": "`ownddeu_mynameac`"
                  }
                ]
              }
            ]
          }
        },
        {
          "join_execution": {
            "select#": 1,
            "steps": [
              {
                "filesort_information": [
                  {
                    "direction": "desc",
                    "table": "`myabccydf_table`",
                    "field": "updated_at"
                  }
                ],
                "filesort_priority_queue_optimization": {
                  "usable": false,
                  "cause": "not applicable (no LIMIT)"
                },
                "filesort_execution": [
                ],
                "filesort_summary": {
                  "rows": 26497152,
                  "examined_rows": 26497152,
                  "number_of_tmp_files": 10287,
                  "sort_buffer_size": 2096864,
                  "sort_mode": "<sort_key, additional_fields>"
                }
              }
            ]
          }
        }
      ]
    }
    MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 0
              INSUFFICIENT_PRIVILEGES: 0
    1 row in set (0.00 sec)
    
    ERROR: 
    No query specified
    
    mysql> 
    mysql> 
    mysql> 
    mysql> set session optimizer_trace='enabled=off';
    Query OK, 0 rows affected (0.00 sec)
    
    mysql>

    我们重点看这一部分:

    "filesort_summary": {
                  "rows": 26497152,
                  "examined_rows": 26497152,
                  "number_of_tmp_files": 10287,
                  "sort_buffer_size": 2096864,
                  "sort_mode": "<sort_key, additional_fields>"
                }

    这里的sort_key, additional_fields,说明这个语句,是采用mysql的第二种排序方式。

    mysql的排序方式有3种:

    • < sort_key, rowid >对应的是MySQL 4.1之前的“原始排序模式”
    • < sort_key, additional_fields >对应的是MySQL 4.1以后引入的“修改后排序模式”
    • < sort_key, packed_additional_fields >是MySQL 5.7.3以后引入的进一步优化的“打包数据排序模式”

     

    1. 原始排序模式:
    根据索引或者全表扫描,按照过滤条件获得需要查询的排序字段值和row ID;
    将要排序字段值和row ID组成键值对,存入sort buffer中;
    如果sort buffer内存大于这些键值对的内存,就不需要创建临时文件了。否则,每次sort buffer填满以后,需要直接用qsort(快速排序算法)在内存中排好序,并写到临时文件中;
    重复上述步骤,直到所有的行数据都正常读取了完成;
    用到了临时文件的,需要利用磁盘外部排序,将row id写入到结果文件中;
    根据结果文件中的row ID按序读取用户需要返回的数据。由于row ID不是顺序的,导致回表时是随机IO,为了进一步优化性能(变成顺序IO),MySQL会读一批row ID,并将读到的数据按排序字段顺序插入缓存区中(内存大小read_rnd_buffer_size)。

    2. 修改后排序模式:
    根据索引或者全表扫描,按照过滤条件获得需要查询的数据;
    将要排序的列值和用户需要返回的字段组成键值对,存入sort buffer中;
    如果sort buffer内存大于这些键值对的内存,就不需要创建临时文件了。否则,每次sort buffer填满以后,需要直接用qsort(快速排序算法)在内存中排好序,并写到临时文件中;
    重复上述步骤,直到所有的行数据都正常读取了完成;
    用到了临时文件的,需要利用磁盘外部排序,将排序后的数据写入到结果文件中;
    直接从结果文件中返回用户需要的字段数据,而不是根据row ID再次回表查询。

    3. 打包数据排序模式:
    第三种排序模式的改进仅仅在于将char和varchar字段存到sort buffer中时,更加紧缩。
    在之前的两种模式中,存储了“yes”3个字符的定义为VARCHAR(255)的列会在内存中申请255个字符内存空间,但是5.7.3改进后,只需要存储2个字节的字段长度和3个字符内存空间(用于保存”yes”这三个字符)就够了,内存空间整整压缩了50多倍,可以让更多的键值对保存在sort buffer中。

    我们这个数据库的版本是5.6,所以无法用到第三种排序方式,那么它是怎么选择第二种还是第一种的排序方式呢?

    MySQL给用户提供了一个max_length_for_sort_data的参数。当“排序的键值对大小” >max_length_for_sort_data时,MySQL认为磁盘外部排序的IO效率不如回表的效率,会选择第一种排序模式;反之,会选择第二种不回表的模式。

    我们当前数据库的max_length_for_sort_data大小为:

    mysql> show variables like '%max_length_for_sort_data%';
    +--------------------------+-------+
    | Variable_name            | Value |
    +--------------------------+-------+
    | max_length_for_sort_data | 1024  |
    +--------------------------+-------+
    1 row in set (0.00 sec)
    
    mysql>

    排序键值对是返回字段+排序字段,注意这里是返回字段,我们是select *,因此包含了myabccydf_table表的所有字段。

    `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `item_id` int(11) DEFAULT NULL,
      `mymum_mydady_sansy_id` int(11) DEFAULT NULL,
      `quantity` int(11) DEFAULT NULL,
      `created_at` datetime DEFAULT NULL,
      `updated_at` datetime DEFAULT NULL,
      `item_type` varchar(255) NOT NULL,
    
     `updated_at` datetime DEFAULT NULL,

    我们的库是utf8:
    根据mysql的文档:
    1. int和bigint:

    2. datetime:

    3. varchar:

    因此需要占据排序区的大小为:

    mysql> select version();
    +------------+
    | version()  |
    +------------+
    | 5.6.16-log |
    +------------+
    1 row in set (0.00 sec)
    
    mysql> 
    
    mysql>  select (8+4+4+4+5+5+255*3+5);
    +-----------------------+
    | (8+4+4+4+5+5+255*3+5) |
    +-----------------------+
    |                   800 |
    +-----------------------+
    1 row in set (0.00 sec)
    
    mysql> 
    1 row in set (0.00 sec)

    因此,排序的键值对大小,800,小于max_length_for_sort_data的值1024,mysql会认为磁盘外部的IO效率高于通过rowid回表的效率,因此采用了第二种排序方式。

    那么第二种的排序方式,消耗多大的临时空间呢,我们知道,需要排序的内容是“排序列+返回列”,也就是(单个排序列+返回列的大小)*返回的行数,即:

    mysql> select (8+4+4+4+5+5+255*3+5)*26385116;
    +--------------------------------+
    | (8+4+4+4+5+5+255*3+5)*26385116 |
    +--------------------------------+
    |                    21108092800 |
    +--------------------------------+
    1 row in set (0.00 sec)
    
    mysql> 
    大约是20G左右的空间。
    
    那么这20G左右的数据,怎么样在数据库主机上,消耗40G左右空间呢?为什么排序操作完了之后,40G的临时空间又马上释放了呢?
    
    首先说说mysql的外部排序方式,是现在内存中,以sort_buffer_size的大小进行排序,如果能在内存中完成排序,那就不用使用临时文件,如果内存中排不下,那么就需要使用临时文件了。
    
    仍然在filesort_summary部分,我们可以看到:
    
                "filesort_summary": {
                  "rows": 26497152,
                  "examined_rows": 26497152,
                  "number_of_tmp_files": 10287,
                  "sort_buffer_size": 2096864,
                  "sort_mode": "<sort_key, additional_fields>"
                }

    这里可以看到,sort_buffer_size是2096864,这个是mysql里面的设置,number_of_tmp_files是10287,也就是说,在排序的时候,在内存里面排不下,需要用到外部文件,外部文件按照sort_buffer_size的大小,切成了10287个文件,每个大小是2096864。我们把每个临时文件,叫做一个chunk。那么这些chunk的大小,就是需要排序的数据大小,也就是20G,或者按照sort_buffer_size*number_of_tmp_files计算,也是20G左右的大小。

    那么这些chunk,每个chunk完成了排序,需要合并成一个大的排序文件,也就是说,需要将10287个文件,总共20G的临时文件,合并成一个大的最终排序结果文件。那么这个文件也是20G,所以零碎的排序文件+合并结果的排序文件,总共大小是40G。

    那么这些临时文件,为什么在排序完成的时候,就释放了呢?
    临时文件它是在发起filesort的时候,先有mkstemp函数生成一个文件,但是生成之后马上调用unlink删除文件。但是删掉又不close文件,还是保留了文件系统句柄,因此后续的写临时文件的操作,都是基于句柄。

    这类似我们打开一个文件,但是另外的进程把这个文件删掉了,但是之前的那个进程,还是操作着这个文件的句柄。可以仍然对这个文件读写,只是我们ls已经看不到这个文件了,但是df -h还是看的空间没释放。只有等到第一个进程关闭了文件句柄,才释放掉了这个文件占据的空间。

    所以mysql的用户线程开始操作,那么就一直握着文件句柄,然后在临时文件内完成内存与文件的sort-merge排序操作,完成了操作之后,用户进程可以close掉这个文件句柄,但是又没退出和mysql服务器的连接。
    所以就能看到我们看到的现象:就是阿里云上的临时文件的占用,在开始的时候上升,一旦排序结束,就下降了。

    好了,到这里,应该就解释了,为什么一个小小的2000多万行的排序,消耗了40多G的临时文件,并且在排序操作完成之后,就立即释放了空间。那么,对应的解决方法,我们有可以随着而来。

    改进方式1:
    为排序字段添加索引。

    改进方式2:
    既然是采用了additional_fields的方式进行排序,而这种排序方式由于select了所有字段,导致排序体积过大,那么我不采用additional_fields 的方式,改用rowid的方式:
    可以session级别修改max_length_for_sort_data,使得这个值小于800。

    mysql> select @@max_length_for_sort_data;
    +----------------------------+
    | @@max_length_for_sort_data |
    +----------------------------+
    |                       1024 |
    +----------------------------+
    1 row in set (0.00 sec)
    
    mysql> 
    mysql> 
    mysql> set session max_length_for_sort_data=100;
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> select @@max_length_for_sort_data;
    +----------------------------+
    | @@max_length_for_sort_data |
    +----------------------------+
    |                        100 |
    +----------------------------+
    1 row in set (0.00 sec)
    
    mysql> 
    mysql> SELECT  `myabccydf_table`.* FROM `myabccydf_table` 
        -> INNER JOIN `ownddeu_mynameac` ON `ownddeu_mynameac`.`id` = `myabccydf_table`.`item_id` 
        -> WHERE (`ownddeu_mynameac`.`published_at` <= NOW() + INTERVAL 0 DAY)  
        -> ORDER BY `myabccydf_table`.`updated_at` desc LIMIT 10 OFFSET 0;
    (结果略)
    10 rows in set (12.90 sec)
    
    mysql>

    可以看到,我们没有修改任何语句,12秒也可以出结果了。

    改进方式3:
    采用延时关联的方法,这个方法,我们在分页查询的时候,经常用到。需要注意的事,驱动表和关联字段是一个表。

    SELECT  `x`.*  FROM `myabccydf_table` x join
    (
    select `myabccydf_table`.id from `myabccydf_table`
    INNER JOIN `ownddeu_mynameac` ON `ownddeu_mynameac`.`id` = `myabccydf_table`.`item_id` 
    WHERE (`ownddeu_mynameac`.`published_at` <= NOW() + INTERVAL 0 DAY)  
    ORDER BY `myabccydf_table`.`updated_at` desc LIMIT 10
    ) t
    using(id);

    mysql> SELECT  `x`.*  FROM `myabccydf_table` x join
        -> (
        -> select `myabccydf_table`.id from `myabccydf_table`
        -> INNER JOIN `ownddeu_mynameac` ON `ownddeu_mynameac`.`id` = `myabccydf_table`.`item_id` 
        -> WHERE (`ownddeu_mynameac`.`published_at` <= NOW() + INTERVAL 0 DAY)  
        -> ORDER BY `myabccydf_table`.`updated_at` desc LIMIT 10
        -> ) t
        -> using(id);
    (结果略)
    10 rows in set (15.00 sec)
    
    mysql>

    总结几条军规,就是:
    1. mysql的字段选择要严谨,如字段类型定义长varchar 255,虽然在存放数据的时候,只是放14个字符,但是一旦这个字段被查询到用于排序的时候,就会按照255个字符申请内存。
    2. 带order by的查询应该禁止select *操作,而select具体的必要字段。
    3. 排序字段和返回字段,建议形成覆盖索引。

    本来,到这里,花了一天的时间,已经把问题搞清楚,并且在团队内部进行了分享。但是我并不打算到此结束,接下来我打算做个有趣的实验,同一个问题在aws和阿里云都开了个case,一方面看看哪家服务更专业;另一方面我也想看看云厂商能专业到什么程度,是否能取代专业的DBA,未来企业内部的DBA是否还有生存空间。

    于是,我两家云厂商都开了case,咨询为什么一个小小的查询需要消耗40G的临时文件。看看两家对rds的技术问题的处理风格。

    需要说明有3点:
    1. 这个case没有主动让TAM和研发介入,只是看看一线后台人员的技术水平,或者看看如果仅仅通过开case(这也是大家最简单、最常见的寻求支持的途径),能处理到什么地步。
    2. 我后续的评论和感受,仅仅针对于这个case,不代表整体水平。
    3. 给了两家厂商一个星期的时间,一般来说,跟case跟一个星期,如果还没结论,那就基本分析不出来了。

    我们来看看同一时间线下,两家的不同反应:

    首先说说感受:
    1. 阿里云的case的反应非常迅速,回复的很快,也非常的激进。基本没有pending在他手上过夜的。
    2. aws的TAM也非常负责任,看到了rds的工单,有主动打电话联系我,询问是否需要帮助。他也主动去追工程师。
    3. 阿里云的工程师,一开始是推脱,需要这个需要那个,需要开审计功能,说我没有审计,无法帮我。
    4. aws的工程师比较务实,一开始就问我要了表结构和explain的信息。这是一种负责任的态度,是用心在问客户要数据,分析数据的态度。
    5. 阿里云的工程师给人的感觉是并不分析用户的数据,不主动向用户要细节,只是给用户一些空对空的指示和建议。其实在请教阿里云之前,我也做过网上的搜索,我发现他告诉我的那些话,我都在网上看到过。
    6. 阿里云的工程师,那种风格给人感觉是先拿着锤子到处砸,运气好的话砸到了故障根因,运气不好的话,看能否砸晕客户,因为他告诉我的一些结论,不太经得起推敲。如果我是一个很着急需要得到一个结果去写报告的人,往往他这种风格可以满足到我。可惜我这次不是这样,我去细细推敲了他的分析,我发现他的一些计算是错误的。他试图绕晕我,如果我不坚定,很容易在过程中接收了他的结论。
    7. aws的工程师比较务实的地方,不仅仅体现在他一开始问我要了表结构和explain,还很坦诚的打电话告诉我,这个问题是他能力范围之外的,但是由于我们是企业用户,他会用best effort来帮助我们。而且,在从20G的临时文件,为什么会到40G的推理上,他在电话中也比较接近真相。他提到了有临时的小文件,和会有一个汇总的大文件。只是还没想到两路外部排序算法和多路外部排序算法。
    8. aws的工程师,对于能力之外的问题,反应比较慢,远远没有阿里云的工程师更新case来的积极主动,我往往很久都看不到他在case上的更新。所以我也不知道他的分析进度和状态如何。这一点是让我担心的。
    9. 最终的结果,在一周之内,两家都没分析出根因。阿里云更接近一点,他根据字段长度去算了,但是算的方法不对,结论是错误的。utf8,一个字符占3个字节,只是针对字符型的,所以算的时候,应该是(8+4+4+4+5+5+255*3+5),而不是(8+4+4+4+5+5+256+4+5)*3,不应该把其他类型也乘以3;另外排序的第二种算法应该是排序列+返回列,阿里云是把两个表的所有出现过的列计算了。

    阿里云只是数字上比较接近。
    10. 如果要让我选一家,我可能会选aws,因为他的诚恳和务实。我买的是服务,不想被忽悠。

    然后说说结论:
    目前云厂商的专线技术水平,还不能达到专业DBA的水平,就云厂商的一线工程师而言,可能有能力处理一般的rds的功能问题,但是进一步的根因查找,就无能为力了。因此企业内部的DBA还是有一定的市场。我之前写过一个比较悲观的dba将死,云架构师即将到来,但是现在来看,可能没死的那么快,至少在5年内,还不会迅速消失。

    关键还是看公司对数据库的重视程度。是否有足够的人力,是否需要精细化运维,是否每次故障都需要分析根因?还是过去了就没事?下次再掉同样坑里面,大不了重启解决99%的IT问题。




    参考:
    MySQL排序内部原理探秘
    MySQL · 引擎特性 · 临时表那些事儿
    Data Type Storage Requirements
    Unicode Support
    MySQL 5.6 Reference Manual – ORDER BY Optimization
    MySQL 5.1 Reference – Chapter 7. Optimization

    华硕AC86U路由器不定期连不上公网的解决办法

    $
    0
    0

    华硕的路由器,不知什么原因,总是会不定期连不上公网,在网络地图的互联网状态,会显示连接失败的状态。

    测试了一下发现,ACU86U在上级的光猫路由器比如重启,比如间歇断网之后,哪怕上级路由器恢复,ac86u还是无法重连外网。我测试了断开上级的光猫路由的电源插头再插上,发现AC86U不会自动重拨,AC86U就一直连不上外网了。直到重启。

    为了解决这个问题,我写了个check_wan.sh脚本,定期检查是否能正常访问国内百度网页,如果不能说明访问外网有问题,就重启路由器。脚本如下:

    #!/bin/sh
    
    LOGTIME=$(date "+%Y-%m-%d %H:%M:%S")
    wget -4 --spider --quiet --tries=5 --timeout=3 www.baidu.com
    if [ "$?" == "0" ]; then
            echo '['$LOGTIME'] Access ChinaNet OK.'
    else
            echo '['$LOGTIME'] Problem decteted, restarting Router after 10 seconds......'
            sleep 10
            /sbin/reboot
    
    fi

    脚本需要放到crontab中进行,但是需要处理一下。不然的话,重启路由器之后,crontab就消失了。参考《[固件讨论] 求助大佬们 如何用cru命令定时运行一个脚本》
    新建/jffs/scripts/init-start (如果不存在的话),内容如下:

    #!/bin/sh
    sleep 180
    cru a ScheduledCheckWan "* * * * * /jffs/scripts/check_wan.sh >> /tmp/mnt/FLASH4GKING/check_wan.log 2>&1"
    cru a ScheduledCleanCheckWanLog "01 00 1 * * echo "" > /tmp/mnt/FLASH4GKING/check_wan.log"

    注意这里的log是放在路由器的外接优盘上的。/tmp/mnt/FLASH4GKING/check_wan.log。这个外接优盘是用来用路由器的虚拟内存的。见《[虚拟内存] 【进阶类教程】为路由器梅林固件增加虚拟内存 2018/7/14》
    log放虚拟内存的所在路径的好处是,不占据路由器系统的其他空间,且也不会重启就删除掉。你可以定期的去检查一下,路由器因为连不上外网导致重启的情况。
    注意这里有个sleep 180秒,是延后写入crontab,这样的话,因为即使是每分钟检查一次,也不会因为刚刚启动,就检查连不上公网,就重启,再启动后检查还是连不上公网,反复重启。因为在上级光猫坏掉的情况下,你还是可以在180内进路由器去修改脚本的。

    路由器启动3分钟后,在crontab中看到的结果是这样的:

    admin@LoveHouse-RT:/jffs/scripts# crontab -l
    * * * * * /jffs/scripts/check_wan.sh >> /tmp/mnt/FLASH4GKING/check_wan.log 2>&1 #ScheduledCheckWan#
    01 00 1 * * echo  > /tmp/mnt/FLASH4GKING/check_wan.log #ScheduledCleanCheckWanLog#
    admin@LoveHouse-RT:/jffs/scripts#

    某系统物理机本地存储和虚拟机SAN存储性能对比测试

    $
    0
    0

    本文用fio测试了某系统的物理机本地存储和虚拟机使用SAN存储的性能对比。

     

     

     

     

     

     

     

     

    FIO测试原始数据

    说明:
    虚拟机在小块文件的IO处理能力上,要比物理机处理的好。而物理机在大块文件的处理能力上比虚拟机好。
    从理论上说,物理机的IO延时应该小,因为IO链路比虚拟机的要短,物理机的直接到本地硬盘。虚拟机要经过SAN网络,通过光纤交换机。
    虚拟机在最大IO延时上要好于物理机,可能是物理上有生产业务在跑,虚拟机用的是共享存储,IO资源池化了,因此IO压力不集中。
    IO直方图中,物理机的IO延时比虚拟机的好,但是之前第三点最大IO延时是虚拟机好,是因为物理机的负载,不属于平稳型,属于有尖峰的类型,因此最大IO延时高,但是直方图中的最大比例IO延时低。

    小记MySQL的锁和事务

    $
    0
    0

    (一)先说明一下定义:

    1. 读现象(Read phenomena):
    SQL 92标准规定了3种不同的读现象。脏读、不可重复读和幻读。分别解释一下。

    1.1 脏读:
    A dirty read (aka uncommitted dependency) occurs when a transaction is allowed to read data from a row that has been modified by another running transaction and not yet committed.
    也就是说,脏读发生在当一个事务,允许从一行中读取数据,而这行的数据,是在另外一个事务中被修改,且还没有被commit的数据。

    此时transaction 1可以看的transaction 2修改过且没有提交的数据,这时就是脏读。

    1.2 不可重复读:
    A non-repeatable read occurs, when during the course of a transaction, a row is retrieved twice and the values within the row differ between reads.
    也就是说,在一个事务中,当两次读取一行的值的时候,得到的值是不同的。这一行被另外一个事务修改且commit了。

    在lock-based的并发控制模式下,当执行SELECT时未获取read lock时,或者在执行SELECT操作时释放对应hang上的read lock时,可能会发生不可重复读。或者是在mvcc的并发冲突控制模式下,当受到提交冲突影响的事务必须回滚的要求被放宽时,也可能会发生不可重复读。

    1.3 幻读:
    A phantom read occurs when, in the course of a transaction, new rows are added or removed by another transaction to the records being read.
    也就是说,在一个事务中,你查询的数据,得到的结果,在另外一个事务中被插入或者删除了,并且提交了,而你是可以看的这个变化的。

    这里有几个要点:
    脏读是还没提交就被读到了;
    不可重复读和幻读都是提交之后被读到了;区别是,不可重复读是当前事务是等值查询,而另一事务对该值进行了更新操作;幻读是当前事务是范围查询,而另一事务对该范围进行了插入或删除操作。
    解决不可重复读的2个方式,lock-based下加锁并且串行化,事务二必须等待事务一提交,或者mvcc下读snapshot。
    解决幻读的方式,也有2个方式,一个是串行化,二是对范围加锁,防止数据插入或者删除。注意在REPEATABLE READ模式(RR模式)下,这个范围其实是没有加锁的,但是mysql的RR模式下引入了gap锁,就解决了这个问题。
    解决不可重复的方法,比如锁定等值查询的行,但是还是无法避免幻读的。因为幻读在没有范围锁的情况下,可以插入记录。

    2. 事务隔离级别(ANSI/ISO SQL):
    ANSI/ISO SQL标准,也定义4个事务隔离级别:

    2.1 串行(Serializable,SE):
    要求事务是串行的,后一个事务必须等待前一个事务完成才能进行。
    整个事务操作过程中,都加锁。

    2.2 可重复读(Repeatable reads,RR):
    要求在一个事务中,第一次读到的内容,和第二次读到的内容一致。
    事务在进行过程中,持续持有读锁和写锁,直到事务完成。同时,没有范围锁,所以该事务隔离级别下还是会出现幻读。但是mysql引入了gap锁,所以mysql在RR隔离级别下,没有幻读。

    2.3 提交读(Read committed,RC)
    在一个事务中,前一次读到的记录和后一次读到的记录,不一定一致。
    事务在进行过程总,持有写锁,读锁不长期持有,读锁只是在读的过程中持有,读完之后就释放了。同时,也没有范围锁。

    2.4 未提交读(Read uncommitted,RU)
    可以读到未提交的记录。也就是可以读到脏数据。

    因此,我们可以画出4种事务隔离级别和3种读现象的兼容图:

  • RU隔离级别下,由于可以读到内存中的未提交,未落盘的数据,那么是可以读到由另外的session发起的另外的事务的脏数据,也是脏读。另外,如果另外的session修改了数据并且提交,此时读到的数据和第一次不一样,那就是不可重复读;此时如果有数据被删除并且提交,那么也会出现幻读。
  • 在RC级别下,由于需要读到提交读数据,那么脏读是不可能;另外,由于是读取提交之后的数据,两次读到的数据肯定不一样,那么是不可重复读;同样,幻读也可能出现。
  • 在RR级别下,脏读也是不可能的,因为RR需要读提交之后读数据;另外,由于RR的定于是2次读取一致,所以必然不会不可重复读;另外,RR下,由于传统的定义,并没有定义范围锁,所以RR有可能出现幻读。
  • SE级别下所有的事务都是串行,所以脏读、不可重复读、幻读都不可能出现。

  • (二)好了,讲完了标准的ANSI/ISO SQL定义下的事务隔离级别,我们再来讲讲mysql的事务隔离级别和锁。

    3. 事务隔离级别(MySQL):
    mysql参考ANSI/ISO SQL,也将自身的隔离级别分成了4种:

    3.1 可重复读(RR):
    这是mysql的默认事务隔离级别,在这个隔离级别下,你在一个事务内读到的数据,总是一致的。这叫一致性读。
    一致性读分为一致性非锁定读和一致性锁定读。
    一致性读因为你在第一次读的时候,就生成了对应的视图快照。通常的select语句,就是这种方式。这种一致性读没有锁,因此叫做一致性非锁定读。
    对应于一致性非锁定读,那么就是一致性锁定读了。这里就引入了锁的概念(锁lock的概念,后续再介绍)。这常常发生在select for update,delete、update、insert时。
    在一致性锁定读的情况下,如果where条件是unique index走unique search,那么此时就只锁定对应索引的行;如果where条件是范围检索,那么此时就会有gap lock和next-key lock。(lock的相关概念,后续再介绍)。

    3.2 提交读(RC):
    每个事务的一致性读,都会创建自己的视图快照。
    对于select for update,update,delete语句,一致性锁定读,只锁定索引行,并不会锁定范围,(范围锁定的gap锁只是在检查外键冲突,或者duplicate key的时候,才会引入。此时类似oracle的mode=4的TX锁)。默认情况下,RC级别是没有gap锁的。
    由于RC没有gap锁,所以RC必然有幻读。
    另外需要注意的是,主从同步,如果是RC的隔离级别,必须使用row-based binlog(即BINLOG_FORMAT = ROW,如果用MIXED,则server自动改成ROW ),不能使用statement base的binlog(原因:binlog的记录顺序是按照事务commit顺序为序。先提交的先记录在binlog,参考《STATEMENT格式的binlog为何不支持Read-Committed和Read-Uncommitted隔离级别》)。

    对于RC,有2个额外的效果需要注意:

  • update或者delete语句,在执行的过程中会锁住对应的行,不匹配where条件中的行,行上的锁将会释放。注意,此时虽然释放了不匹配where条件的行,但是之前还是会有锁的过程。
  • update语句,在执行时,如果已经有行被锁定,那么innodb会执行“半一致性读”,也就是mysql会返回最近commit的行记录,看这些行记录是否符合where条件,如果符合where条件,那么就申请锁或者等待锁。
  • 注,gap lock在RR下存在,可以通过innodb_locks_unsafe_for_binlog =1(默认为0,参数已过期,在将来版本会取消)来取消gap lock。
    但是,即使是设置了innodb_locks_unsafe_for_binlog =1,在RC级别下的外键检查冲突或者duplicated key检测的时候,还是会引入gap lock。
    innodb_locks_unsafe_for_binlog=1可以约等于设置事务隔离级别为RC,但是设置事务隔离级别,可以session级设置,另外修改innodb_locks_unsafe_for_binlog 需要重启数据库。

    3.3 不提交读(RU):
    此隔离级别,读没有锁。所以该事务隔离级别下,不存在读一致性。

    3.4 串行(SE):
    当autocommit禁用时,innnodb隐式的将select改写是select for share。
    当autocommit启用时(默认值),select语句有它自己的事务。

    4. MySQL的锁的类型:
    MySQL的官方文档中,将MySQL的锁分成了8种类型:

    4.1 共享锁和排他锁(Shared and Exclusive Locks)。
    innodb的共享锁和排他锁,是一种行锁,类型有两种:

  • 共享锁 S:持有该锁,可以读取行。
  • 排他锁 X:持有该锁可以delete或者update行。
  • 4.2 意向锁(Intention Locks)
    innodb的意向锁,是一种表锁,类型有两种:

  • 意向共享锁IS:表示事务将要对表上的行r加S锁,那么在表上,就会加上IS锁。
  • 意向独占锁IX:表示事务将要对表上的行r加X锁,那么在表上,就会加上IX锁。
  • innodb支持多粒度锁,也就是说,可以在一个行上有S锁或者X锁,在表上可以持有意向锁IS或IX。
    当事务T1,持有了表上某个行r的S锁,那么此时另外的一个事务T2,是可以申请S锁在该行r上。在行r上可以同时有T1的S锁和T2的S锁。但是如果事务T2想修改行记录,申请r行的X锁,那么此时需要等待T1释放X锁才可以。
    当事务T1,持有了表上某个行r的X锁,那么此时另外一个事务T2,申请该行r上的S锁或者X锁,都会等待。需要等待T1的X锁释放。

    意向锁协议遵循如下规则:
    在一个事务在表的行上申请S锁之前,它必须在表上获得IS锁或者IX锁。
    在一个事务在表的行上申请X锁之前,它必须在表上获得IX锁。

    结合上面的T1和T2。
    当事务T1,持有了表上的行r的S锁的时候,其实它也是持有表的IS锁。此时,如果事务T2也想申请表上行r的S锁,那么它先要获得表的IS锁,而事务T1的IS锁是兼容事务T2的IS锁,所以不必等待,T2直接获得表的IS锁,然后获得行r的S锁。
    当事务T1,持有了表上的行r的S锁的时候,其实它也是持有表的IS锁。此时,如果事务T2想修改记录,申请表上行r的X锁,那么它先要获得表的IX锁,而事务T1的IS锁兼容事务T2的IX锁,所以T2可以先获得表的IX锁,然后T2再申请表的X锁,但是X锁和S锁不兼容,所以需要等T1释放行r的S锁。
    当事务T1,持有了表上的行r的X锁的时候,其实它也是持有表的IX锁。此时,如果事务T2想读取记录,申请表上行r的S锁,那么它先要获得表的IS锁,而事务T1的IX锁兼容事务T2的IS锁,所以不必等待,T2直接获得表的IS锁,然后申请行r的S锁,和T1的X锁不兼容,需要等T1释放行r的X锁。(但是我们在实际执行的时候,T2不是用lock base机制,用的是mvcc机制,所以T2是可以看到记录r的snapshot的,不需要等待。)
    当事务T1,持有了表上的行r的X锁的时候,其实它也是持有表的IX锁。此时,如果事务T2想修改记录,申请表上行r的X锁,那么它先要获得表的IX锁,而事务T1的IX锁兼容事务T2的IX锁,所以T2可以先获得表的IX锁,然后申请行r的X锁的时候,和T1的X锁不兼容,需要等T1释放行r的X锁。

    锁的兼容性表格如下:

    可以看出:
    排它锁(X):与任何锁都不兼容
    共享锁(S):只兼容共享锁和意向共享锁
    意向锁(IS,IX): 互相兼容,行级别的锁只兼容共享锁

    4.3 记录锁(Record Locks):
    记录锁,锁定的是索引记录。如果表上没有索引,那么用的是隐藏的聚簇索引。
    当一条 SQL 没有走任何索引时,那么将会在每一条聚集索引后面加 X 锁,这个类似于表锁,但原理上和表锁是不同的。

    4.4 间隙锁(Gap Locks)
    gap锁是为了解决幻读问题而生的,RR隔离级别中,防止insert记录产生幻读,而对记录的gap进行锁定,不让别的事务在间隙插入值。RC级别通常没有gap锁(除了外键检测和duplicate key检测之外)。
    update操作不会申请gap锁。
    gap锁可以跨越单个索引值,多个索引值,甚至是空值。
    在通过唯一索引进行唯一查找时,不会用到gap锁。但是有情况例外,就是唯一索引是多列组合索引。
    注意,gap的X锁和另外一个事务在同一个gap上的S锁,是兼容的。多冲突类型gap锁之所以能存在是因为:如果某个记录从索引中删除时,这条记录上的gap锁(多个事务持有的)一定会被合并。
    gap锁在InnoDB中是专一功能(purely inhibitive),这意味着它们只能防止其他事务在这个间隙中插入数据,而无法阻止不同的事务在同样的间隙上获取间隙锁。所以就间隙锁来说,S锁和X锁效果一样。

    gap锁可以通过设置事务隔离级别为RC来禁用。

    4.5 Next-Key锁(Next-Key Locks)
    Next-Key锁其实是记录锁+间隙锁。它是发生在查询过程中,锁定索引记录以及该索引记录前面的间隙。另外如果没有主键,则会对辅助索引下一个键值加上gap lock。
    有shard或exclusive两种模式。

    next-key lock还会加在“supremum pseudo-record”上,什么是supremum pseudo-record呢?它是索引中的伪记录(pseudo-record),代表此索引中可能存在的最大值。也就是会锁上当前索引到最大字到正无穷大。
    比如:
    select* from mytab where col_a>=10 for update;
    那么就锁定了10到正无穷到所有值。 此时如果insert into mytable(col_a) values (11);是insert不进去,需要等待前者到select是否锁才可以。

    这里需要注意到有2点:
    (1)当查询的索引含有唯一属性时,InnoDB会对Next-Key Lock进行优化,将其降级为Record Lock,即仅锁住索引本身,而不是范围。
    (2)当一个session当transaction T1中,在行对查询中无论使用了X或者S对gap锁,那么都将阻止另外一个transaction T2对gap进行insert。
    (3)InnoDB存储引擎还会对辅助索引下一个键值加上gap lock。

    4.6 插入意向锁(Insert Intention Locks)
    插入意向锁是一种特殊对gap锁,它是在多个transaction对同一个gap进行插入的时候,且插入是在gap内的不同地方,那么各个transaction不需要彼此等待对方释放锁。目的是为了提高insert的并发。
    通常的说,面对即将到来的并发insert,第一个insert会申请插入意向锁,在之前含有gap锁的事务完成之后,释放了gap锁,此时这个insert语句就持有了gap锁,而这个gap锁是特殊的gap锁,即插入意向锁。是允许同一个gap内,如果值不同是允许并发插入的。

    4.7 自增锁(AUTO-INC Locks)
    自增锁是一种特殊的表锁,是在事务进行insert带有AUTO_INCREMENT字段时获取的。最简单的一个例子是,一个事务在insert表时,另外一个事务需要等待。
    自增锁的行为收到参数innodb_autoinc_lock_mode控制。这里根据insert的数据是否可以推测的,分成以下几类:

    4.7.1 “Simple inserts”
    这种方式的insert是可以预估多少行数的,它是单行或者多行的INSERT 或 REPLACE 语句(注,不包含嵌套子查询),另外, INSERT … ON DUPLICATE KEY UPDATE也是不包含这种insert类型内的。

    4.7.2 “Bulk inserts”
    这种方式的insert行数是不可预估的。它通常是INSERT … SELECT, REPLACE … SELECT, 和 LOAD DATA,innodb假定每次处理每行数据,AUTO_INCREMENT都是一个新的值。

    4.7.3 Mixed-mode inserts”
    是simple-insert,但是部分auto increment值给定或者不给定。如在c1是自增值的情况下,INSERT INTO t1 (c1,c2) VALUES (1,’a’), (NULL,’b’), (5,’c’), (NULL,’d’); 如INSERT … ON DUPLICATE KEY UPDATE,

    4.7.4 innodb_autoinc_lock_mode可以有值0,1,2:

  • 0 (“traditional” lock mode),表示上述所有类型的insert,都基于传统的lock模式,即一个session插入,另外一个session等待。
  • 1 (“consecutive” lock mode),(a)当发生Simple inserts,则使用的是一种轻量级锁,只要获取了相应的auto increment就释放锁,并不会等到语句结束;(b)当发生bulk inserts的时候,会产生一个特殊的AUTO-INC table-level lock直到语句结束(注,这里是语句结束就释放锁,而不是事务结束才释放);
  • 2 (“interleaved” lock mode),进行bulk insert的时候,不会产生table级别的自增锁,因为它是允许其他insert插入的。


  • 参考文章:
    wikipedia – Isolation (database systems)
    MySQL 8.0 Reference Manual – 15.7.2.1 Transaction Isolation Levels

    检查pg中所有分区表

    $
    0
    0

    pg 10由于没有hash分区,而pg_pathman一直都是支持多种分区的。所以如果某些pg 11以前的系统,可能会混合部署pg原生分区和pg_pathman。
    要检查这种混合部署环境中的分区情况,可以用下面的sql:

    select b.parent::text as part_table,
         'native partition' as part_tool,
    CASE WHEN a.partstrat='r' THEN 'range'
         WHEN a.partstrat='l' THEN 'list'
         WHEN a.partstrat='h' THEN 'hash'
         ELSE 'other'
         END as part_type,
         b.cnt as part_cnt
    from
    pg_partitioned_table a,
    (SELECT
        parent.oid,
        parent.relname          AS parent,
        COUNT(*) as cnt
    FROM pg_inherits
        JOIN pg_class parent                 ON pg_inherits.inhparent = parent.oid
        JOIN pg_class child                  ON pg_inherits.inhrelid   = child.oid
        JOIN pg_namespace nmsp_parent        ON nmsp_parent.oid  = parent.relnamespace
        JOIN pg_namespace nmsp_child         ON nmsp_child.oid   = child.relnamespace
    GROUP BY  oid,parent) b
    where a.partrelid=b.oid
    union all
    select
         parent::text,
         'pg_pathman',
    CASE WHEN parttype=1 THEN 'hash'
         WHEN parttype=2 THEN 'range'
         ELSE 'other'
    END ,
    count(*)
    from pathman_partition_list
    group by parent,parttype

    显示结果如下:

    dyats=> select version();
                                                     version                                                
    ---------------------------------------------------------------------------------------------------------
     PostgreSQL 10.5 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-16), 64-bit
    (1 row)
     
          part_table       |    part_tool     | part_type | part_cnt
    -----------------------+------------------+-----------+----------
     mytabaa1_kkj_dbddak   | native partition | range     |        7
     kkj_dbddak            | native partition | range     |       59
     drop_sgs_djaygsrf_bak | native partition | range     |        9
     sgs_djaygsrf          | pg_pathman       | hash      |     1024
     mumybaad_sgs_djaygsrf | pg_pathman       | hash      |      128
    (5 rows)
     
    dyats=>
    dyats=>
    dyats=>
    
    dyats=>

    可以看到有5个partition table,其中3个是native partition,2个是用来pg_pathman,并且还可以看到分区的类型,和分区数量。

    pg报错current transaction is aborted

    $
    0
    0

    在一个事务中,pg报错了current transaction is aborted:

    mumy_corehrdban_psdb=> begin;
    BEGIN
    mumy_corehrdban_psdb=> select * from orasup_test1 ;
     a 
    ---
     1
     2
     3
    (3 rows)
    
    mumy_corehrdban_psdb=> insert into orasup_test1 values(111);
    INSERT 0 1
    mumy_corehrdban_psdb=> select * from not_exist;
    ERROR:  relation "not_exist" does not exist
    LINE 1: select * from not_exist;
                          ^
    mumy_corehrdban_psdb=> insert into orasup_test1 values(222);
    ERROR:  current transaction is aborted, commands ignored until end of transaction block
    
    mumy_corehrdban_psdb=> select * from orasup_test1;
    ERROR:  current transaction is aborted, commands ignored until end of transaction block
    mumy_corehrdban_psdb=> 
    mumy_corehrdban_psdb=> \d
    ERROR:  current transaction is aborted, commands ignored until end of transaction block
    mumy_corehrdban_psdb=> 
    mumy_corehrdban_psdb=> 
    mumy_corehrdban_psdb=> rollback;
    ROLLBACK
    mumy_corehrdban_psdb=> select * from orasup_test1;
     a 
    ---
     1
     2
     3
    (3 rows)
    
    mumy_corehrdban_psdb=>

    原因是在一个事务中,pg如果遇到的Error的报错,会忽略后续的命令,后续所有命令都会报错:current transaction is aborted, commands ignored until end of transaction block。 直到手工确认该事务结束(需要commit或者rollback)

    这个问题,可以在psql中设置ON_ERROR_ROLLBACK true来绕过:

    mumy_corehrdban_psdb=> 
    mumy_corehrdban_psdb=> \set ON_ERROR_ROLLBACK true
    mumy_corehrdban_psdb=> begin;
    BEGIN
    mumy_corehrdban_psdb=> select * from orasup_test1;
     a 
    ---
     1
    (1 row)
    
    mumy_corehrdban_psdb=> insert into orasup_test1 values(2);
    INSERT 0 1
    mumy_corehrdban_psdb=> select * from not_exist;
    ERROR:  relation "not_exist" does not exist
    LINE 1: select * from not_exist;
                          ^
    mumy_corehrdban_psdb=> insert into orasup_test1 values(3);
    INSERT 0 1
    mumy_corehrdban_psdb=> select * from orasup_test1;
     a 
    ---
     1
     2
     3
    (3 rows)
    
    mumy_corehrdban_psdb=>

    我们来看一下ON_ERROR_ROLLBACK和另外一个类似的ON_ERROR_STOP。
    这2个参数,可以\set观看当前设置,默认值都是off:

    mumy_corehrdban_psdb=> \set
    AUTOCOMMIT = 'on'
    COMP_KEYWORD_CASE = 'preserve-upper'
    DBNAME = 'mumy_corehrdban_psdb'
    ECHO = 'none'
    ECHO_HIDDEN = 'off'
    ENCODING = 'UTF8'
    FETCH_COUNT = '0'
    HISTCONTROL = 'none'
    HISTSIZE = '500'
    HOST = '/tmp'
    IGNOREEOF = '0'
    LASTOID = '0'
    ON_ERROR_ROLLBACK = 'off'
    ON_ERROR_STOP = 'off'
    PORT = '5432'
    PROMPT1 = '%/%R%# '
    PROMPT2 = '%/%R%# '
    PROMPT3 = '>> '
    QUIET = 'off'
    SERVER_VERSION_NAME = '10.5'
    SERVER_VERSION_NUM = '100005'
    SHOW_CONTEXT = 'errors'
    SINGLELINE = 'off'
    SINGLESTEP = 'off'
    USER = 'app_rw'
    VERBOSITY = 'default'
    VERSION = 'PostgreSQL 10.5 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5
    20150623 (Red Hat 4.8.5-16), 64-bit'
    VERSION_NAME = '10.5'
    VERSION_NUM = '100005'
    mumy_corehrdban_psdb=>

    这2个参数,一般用在数据导入到时候,

    -bash-4.2$ cat test.sql
    select * from orasup_test1;
    insert into orasup_test1 values(111);
    insert into orasup_test1 values(222);
    select * from not_exist;
    insert into orasup_test1 values(333);
    insert into orasup_test1 values(444);
    select * from orasup_test1;
    
    mumy_corehrdban_psdb=> \set ON_ERROR_STOP true
    mumy_corehrdban_psdb=> \set
    AUTOCOMMIT = 'on'
    COMP_KEYWORD_CASE = 'preserve-upper'
    DBNAME = 'mumy_corehrdban_psdb'
    ECHO = 'none'
    ECHO_HIDDEN = 'off'
    ENCODING = 'UTF8'
    FETCH_COUNT = '0'
    HISTCONTROL = 'none'
    HISTSIZE = '500'
    HOST = '/tmp'
    IGNOREEOF = '0'
    LASTOID = '0'
    ON_ERROR_ROLLBACK = 'off'
    ON_ERROR_STOP = 'true'
    PORT = '5432'
    PROMPT1 = '%/%R%# '
    PROMPT2 = '%/%R%# '
    PROMPT3 = '>> '
    QUIET = 'off'
    SERVER_VERSION_NAME = '10.5'
    SERVER_VERSION_NUM = '100005'
    SHOW_CONTEXT = 'errors'
    SINGLELINE = 'off'
    SINGLESTEP = 'off'
    USER = 'app_rw'
    VERBOSITY = 'default'
    VERSION = 'PostgreSQL 10.5 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5
    20150623 (Red Hat 4.8.5-16), 64-bit'
    VERSION_NAME = '10.5'
    VERSION_NUM = '100005'
    mumy_corehrdban_psdb=> 
    mumy_corehrdban_psdb=> 
    mumy_corehrdban_psdb=> \i test.sql
     a 
    ---
     1
     2
     3
    (3 rows)
    
    INSERT 0 1
    INSERT 0 1
    psql:test.sql:4: ERROR:  relation "not_exist" does not exist
    LINE 1: select * from not_exist;
                          ^
    mumy_corehrdban_psdb=> 
    mumy_corehrdban_psdb=> select * from orasup_test1 ;
      a  
    -----
       1
       2
       3
     111
     222
    (5 rows)
    
    mumy_corehrdban_psdb=>

    可以看到,设置ON_ERROR_STOP true的时候,导入数据时,一旦报错,就停止了,不再导入后面的数据。

    mumy_corehrdban_psdb=> \set ON_ERROR_ROLLBACK true
    mumy_corehrdban_psdb=> \set
    AUTOCOMMIT = 'on'
    COMP_KEYWORD_CASE = 'preserve-upper'
    DBNAME = 'mumy_corehrdban_psdb'
    ECHO = 'none'
    ECHO_HIDDEN = 'off'
    ENCODING = 'UTF8'
    FETCH_COUNT = '0'
    HISTCONTROL = 'none'
    HISTSIZE = '500'
    HOST = '/tmp'
    IGNOREEOF = '0'
    LASTOID = '0'
    ON_ERROR_ROLLBACK = 'true'
    ON_ERROR_STOP = 'off'
    PORT = '5432'
    PROMPT1 = '%/%R%# '
    PROMPT2 = '%/%R%# '
    PROMPT3 = '>> '
    QUIET = 'off'
    SERVER_VERSION_NAME = '10.5'
    SERVER_VERSION_NUM = '100005'
    SHOW_CONTEXT = 'errors'
    SINGLELINE = 'off'
    SINGLESTEP = 'off'
    USER = 'app_rw'
    VERBOSITY = 'default'
    VERSION = 'PostgreSQL 10.5 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5
    20150623 (Red Hat 4.8.5-16), 64-bit'
    VERSION_NAME = '10.5'
    VERSION_NUM = '100005'
    mumy_corehrdban_psdb=> 
    mumy_corehrdban_psdb=> select * from orasup_test1 ;
     a 
    ---
     1
     2
     3
    (3 rows)
    
    mumy_corehrdban_psdb=> 
    mumy_corehrdban_psdb=> 
    mumy_corehrdban_psdb=> \i test.sql 
     a 
    ---
     1
     2
     3
    (3 rows)
    
    INSERT 0 1
    INSERT 0 1
    psql:test.sql:4: ERROR:  relation "not_exist" does not exist
    LINE 1: select * from not_exist;
                          ^
    INSERT 0 1
    INSERT 0 1
      a  
    -----
       1
       2
       3
     111
     222
     333
     444
    (7 rows)
    
    mumy_corehrdban_psdb=> 
    mumy_corehrdban_psdb=> 
    mumy_corehrdban_psdb=>  
    mumy_corehrdban_psdb=> select * from orasup_test1 ;
    -----
       1
       2
       3
     111
     222
     333
     444
    (7 rows)
    
    mumy_corehrdban_psdb=>

    可以看到,设置ON_ERROR_ROLLBACK true的时候,导入数据时,报错的命令是回滚了,但是后续的命令可以继续。

    数据库应急杀进程脚本

    $
    0
    0

    Oracle:
    (1)数据库内操作

    --单实例:
    select 'alter system kill session '''||s.sid||','||s.SERIAL#||''' immediate;' from v$session s
    where s.status='INACTIVE'  --状态为非活跃
    and s.USERNAME= 'ZZZ'  --用户为ZZZZ
    s.type<>'BACKGROUND' --不为oracle后台进程
    and program not like '%(J0%' --不为oracle的JOB进程
    order by s.LOGON_TIME asc,sql_exec_start asc;

    (2)操作系统中操作(要求登录到数据库主机)

    ## kill掉所有local=no的非本地连接进程
    ps -ef|grep -v grep|grep LOCAL=NO|awk '{print $2}'|xargs kill -9

    SQL Server:

    --kill 被阻塞会话
    select  'kill '+cast(spid as varchar) FROM sys.sysprocesses sp
    where sp.blocked !=0 and sp.spid != sp.blocked and loginame='XXX';

    PG:

    (1)数据库内操作:
    方案一,较保守、风险低,但是针对高并发的系统效果不好。因为kill的速度慢,跟不上再次上来的会话。
    SELECT 'select pg_terminate_backend('||pid||');' FROM pg_stat_activity
    WHERE pid <> pg_backend_pid() -- 不kill掉自己的进程
    and datname='ZZZ' --涉及到的数据库名
    and usename='ZZZ' --涉及到的用户名
    and query like '%ZZZ%' – 涉及到的语句
    order by (now()-query_start) desc – 根据执行时间长短排序,先kill执行时间长的
    ;
     
     
    方案二,可以针对高并发系统,会话上来很快,且kong端虽然限流但是还是没有限制住(可能是开发没找对接口,报给运维限制了kong,但是还是有大量的会话进来)
    1. 先确认pid对应的sql是需要kill的sql,没有别的类似相似的sql干扰:
    SELECT pid,query FROM pg_stat_activity
    WHERE pid <> pg_backend_pid()
    and datname='XXX'
    and usename='YYY' and state='active'
    and query like '%ZZZZZZZZ%'
    order by (now()-query_start) desc;
    如:
    SELECT pid,query FROM pg_stat_activity
    WHERE pid <> pg_backend_pid()
    and datname='flysafe_websdite'
    and usename='app_rw' and state='active'
    and query like '%SELECT * FROM "quiz_info"  WHERE "quiz_info"."deleted_at" IS NULL AND (("questionnaire_id" IN ($1)) AND (enc_sn = $2))%'
    order by (now()-query_start) desc;
     
     
    2.然后批量循环kill session
    select pg_terminate_backend(pid) from  (SELECT pid FROM pg_stat_activity
    WHERE pid <> pg_backend_pid()
    and datname='XXX'
    and usename='YYY' and state='active'
    and query like '%ZZZ%'
    ) a \watch 5;
    如:
    select pg_terminate_backend(pid) from  (SELECT pid FROM pg_stat_activity
    WHERE pid <> pg_backend_pid()
    and datname='flysafe_websdite'
    and usename='app_rw' and state='active'
    and query like '%SELECT * FROM "quiz_info"  WHERE "quiz_info"."deleted_at" IS NULL AND (("questionnaire_id" IN ($1)) AND (enc_sn = $2))%'
    ) a \watch 5;

    (2)操作系统中操作(要求登录到数据库主机)

    有kill -9的语句可以用,但是不建议,会导致正在update的语句被kill之后,数据库进入recovery状态。

    MySQL:

    (1) aws :   
    select concat('call mysql.rds_kill(',id,');') from information_schema.processlist
    where user='ZZZ' 
    and info like '%ZZZ%'   -- 当前消耗高的SQL语句
    and command = '' -- 按照SQL语句的状态
    order by time desc;
     
    --- 在SQL命令行得到的kill命令不能直接粘贴复制,可通过shell命令快速得到kill id的脚本
    mysql -uroot -p -h xxxx < kill_query.sh > kill_id.txt
    
    
    (2) 阿里云 :   
    select concat('KILL ',id,';') from information_schema.processlist
    where user='ZZZ'  -- 操作的数据库用户
    and info like '%ZZZ%'   -- 当前消耗高的SQL语句
    and command = '' -- 按照SQL语句的状态
    order by time desc;  -- 根据操作时间排序,先kill执行时间长的;
     
     
    --- 在SQL命令行得到的kill命令不能直接粘贴复制,可通过shell命令快速得到kill id的脚本
    mysql -uroot -p -h xxxx < kill_query.sh > kill_id.txt
    
    
    (3)内网
    (3.1)数据库内操作:
    select time,concat('KILL ',id,';') from information_schema.processlist
    where user='ZZZ'  --操作的数据库用户
    and info like '%ZZZ%'   –当前消耗高的SQL语句
    order by time desc; --根据操作时间排序,先kill执行时间长的;
    
    
    (3.2)操作系统中操作:(要求登录到数据库主机)
    ## 假定kill掉所有ZZZ用户的线程
    mysqladmin -uroot -p processlist|awk -F "|" '{if($3 == "ZZZ")print $2}'|xargs -n 1 mysqladmin -uroot -p kill


    pg常用大小查询

    $
    0
    0

    查出所有数据库大小:

    select pg_database.datname, pg_database_size(pg_database.datname) AS size from pg_database order by size desc;

    查出所有表按大小排序并分离data与index:

    SELECT
        table_name,
        pg_size_pretty(table_size) AS table_size,
        pg_size_pretty(indexes_size) AS indexes_size,
        pg_size_pretty(total_size) AS total_size
    FROM (
        SELECT
            table_name,
            pg_table_size(table_name) AS table_size,
            pg_indexes_size(table_name) AS indexes_size,
            pg_total_relation_size(table_name) AS total_size
        FROM (
            SELECT ('"' || table_schema || '"."' || table_name || '"') AS table_name
            FROM information_schema.tables
        ) AS all_tables
        ORDER BY total_size DESC
    ) AS pretty_sizes;

    查看索引大小:

    select relname as table_name,pg_size_pretty(pg_relation_size(relid)) as table_size, 
    indexrelname as index_name,
    pg_size_pretty(pg_relation_size(indexrelid)) as index_size from pg_stat_user_indexes 
    order by pg_relation_size(indexrelid) desc;

    How to upgrade aws rds postgresql

    $
    0
    0

    ## 0. Read the summary of upgrade pg
    https://docs.aws.amazon.com/zh_cn/AmazonRDS/latest/UserGuide/USER_UpgradeDBInstance.PostgreSQL.html

    ## 1. Prepare Action
    1.1. Choose Preferred Upgrade Targets from :
    https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_UpgradeDBInstance.PostgreSQL.html

    1.2 Set the env variables.

    export REGION=ap-southeast-1                          
    export AWS_PROF=akProfile_mmp_global_vg
    export UPGRADE_INSTANCE_ID=pg-for-coolap-vg-restore-testupgrade
    export DB_CURRENT_VERSION=`aws --profile ${AWS_PROF}  --region ${REGION} rds describe-db-instances --db-instance-identifier=${UPGRADE_INSTANCE_ID} |grep EngineVersion | awk '{print $2}' |awk -F "[\"\"]" '{print $2}'`
    export ENDPOINT=https://rds.${REGION}.amazonaws.com
    export READ_REPLICA_INSTANCE_ID=`aws --profile ${AWS_PROF}  --region ${REGION} rds describe-db-instances --db-instance-identifier=${UPGRADE_INSTANCE_ID} --output text |grep READREPLICADBINSTANCEIDENTIFIERS |awk '{print $2}'`
    export DB_NEW_VERSION=<Version_From_Step_1.1>
    
    echo "REGION: ${REGION}"
    echo "AWS_PROF: ${AWS_PROF}"
    echo "UPGRADE_INSTANCE_ID: ${UPGRADE_INSTANCE_ID}"
    echo "DB_CURRENT_VERSION: ${DB_CURRENT_VERSION}"
    echo "DB_NEW_VERSION: ${DB_NEW_VERSION}"
    echo "ENDPOINT: ${ENDPOINT}"
    echo "READ_REPLICA_INSTANCE_ID: ${READ_REPLICA_INSTANCE_ID}"

    1.3. Check the current instance infomation,check if non-default option and parameter group

    aws  --profile ${AWS_PROF}  --region ${REGION} rds describe-db-instances --db-instance-identifier=${UPGRADE_INSTANCE_ID} \
         --query 'DBInstances[*].{ID:DBInstanceIdentifier,Name:DBName,EngineName:Engine,EngineVersion:EngineVersion,Public:PubliclyAccessible,Type:DBInstanceClass,OptionGroup:OptionGroupMemberships[*].OptionGroupName|[0],ParameterGroup:DBParameterGroups|[0].DBParameterGroupName, VpcId:DBSubnetGroup.VpcId, ReadReplica:ReadReplicaDBInstanceIdentifiers|[0]}'  \
        --output table

    1.4. Check any vailable target major version

    aws  --profile ${AWS_PROF} rds describe-db-engine-versions --engine postgres --region ${REGION} --endpoint $ENDPOINT --output table --query 'DBEngineVersions[*].ValidUpgradeTarget[?IsMajorVersionUpgrade==`true`].{EngineVersion:EngineVersion}' --engine-version ${DB_CURRENT_VERSION}

    Double check with the version which you choose in step 1.1


    ## 2. Upgrade Action

    2.1. List all database in the instance

    DB=> SELECT d.datname FROM pg_catalog.pg_database d WHERE d.datallowconn = true;

    2.2. Check if any REG data type, these data type should be handle(remove or change),before upgrade

    DB=> SELECT count(*) FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n, pg_catalog.pg_attribute a 
          WHERE c.oid = a.attrelid 
           AND NOT a.attisdropped 
           AND a.atttypid IN ('pg_catalog.regproc'::pg_catalog.regtype, 
                             'pg_catalog.regprocedure'::pg_catalog.regtype, 
                             'pg_catalog.regoper'::pg_catalog.regtype, 
                             'pg_catalog.regoperator'::pg_catalog.regtype, 
                             'pg_catalog.regconfig'::pg_catalog.regtype, 
                             'pg_catalog.regdictionary'::pg_catalog.regtype) 
          AND c.relnamespace = n.oid 
          AND n.nspname NOT IN ('pg_catalog', 'information_schema');


    2.3 If upgrade from pg 9.3, check if any exist LINE data type,these data type should be handle(remove or change),before upgrade

    DB=> SELECT count(*) FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n, pg_catalog.pg_attribute a 
          WHERE c.oid = a.attrelid 
          AND NOT a.attisdropped
          AND a.atttypid = 'pg_catalog.line'::pg_catalog.regtype 
          AND c.relnamespace = n.oid 
          AND n.nspname !~ '^pg_temp_' 
          AND n.nspname !~ '^pg_toast_temp_' 
          AND n.nspname NOT IN ('pg_catalog', 'information_schema');

    2.4. check the extension support

    DB=> \c <dbname> <username>
    DB=> \dx

    https://docs.aws.amazon.com/zh_cn/AmazonRDS/latest/UserGuide/CHAP_PostgreSQL.html#PostgreSQL.Concepts.General.FeatureSupport.Extensions

    2.5. check if any prepare transaction, if any, please kill the session and wait for no prepared traction.

    DB=> SELECT count(*) FROM pg_catalog.pg_prepared_xacts;

    2.6. If any Read Replica instance from , promote the read replica before upgrade.

    aws --profile ${AWS_PROF} --region ${REGION} rds promote-read-replica \
        --db-instance-identifier ${READ_REPLICA_INSTANCE_ID}

    2.7. Begin upgrade postgresql

    aws --profile ${AWS_PROF} --region ${REGION} rds modify-db-instance \
        --db-instance-identifier ${UPGRADE_INSTANCE_ID} \
        --engine-version ${DB_NEW_VERSION} \
        --no-allow-major-version-upgrade \
        --apply-immediately


    2.8. monitor the instance upgrade status:

    while true
    do
    echo "-----`date`------"
    aws  --profile ${AWS_PROF}  --region ${REGION} rds describe-db-instances --db-instance-identifier=${UPGRADE_INSTANCE_ID} |grep DBInstanceStatus
    sleep 5
    done

    ## 3. Post Action
    3.1. Update the pg extension to new version, new version could be found in step 3.5

    DB=> ALTER EXTENSION PostgreSQL-extension UPDATE TO 'new-version';
    For example:
    DB=> ALTER EXTENSION pg_trgm update to '1.4';

    3.2. Delete the read replica after you confirm it will not be used.

    aws --profile ${AWS_PROF}  --region ${REGION} rds delete-db-instance \
        --db-instance-identifier ${READ_REPLICA_INSTANCE_ID} \
        --final-db-snapshot-identifier ${READ_REPLICA_INSTANCE_ID}-FinalSnapshot-`date +%Y%m%d%H%M%S%N`

    3.3. Create new read replica for upgrade instance if necessary.

    aws --profile ${AWS_PROF} --region ${REGION} rds create-db-instance-read-replica \
        --db-instance-identifier ${READ_REPLICA_INSTANCE_ID} --no-multi-az \
        --no-auto-minor-version-upgrade --no-deletion-protection --copy-tags-to-snapshot \
        --no-publicly-accessible --storage-type gp2 \
        --source-db-instance-identifier  ${UPGRADE_INSTANCE_ID}

    数据库内查询pg的表结构定义

    $
    0
    0

    需要利用到plperlu和自己写一个system函数。

    -bash-4.2$ psql
    psql (9.6.2)
    Type "help" for help.
    
    postgres=# create extension plperlu;
    CREATE EXTENSION
    postgres=# \dx
                          List of installed extensions
      Name   | Version |   Schema   |              Description               
    ---------+---------+------------+----------------------------------------
     plperlu | 1.0     | pg_catalog | PL/PerlU untrusted procedural language
     plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language
    (2 rows)

    postgres=# CREATE OR REPLACE FUNCTION system(text) RETURNS text
    postgres-# AS 'my $cmd=shift; return `cd /tmp;$cmd`;' LANGUAGE plperlu;
    CREATE FUNCTION
    postgres=# 
    postgres=#

    postgres=# select system('pg_dump -s -t orasup_test1 dbinfo2 |egrep -v "^--|^$"');
                           system                        
    -----------------------------------------------------
     SET statement_timeout = 0;                         +
     SET lock_timeout = 0;                              +
     SET idle_in_transaction_session_timeout = 0;       +
     SET client_encoding = 'UTF8';                      +
     SET standard_conforming_strings = on;              +
     SET check_function_bodies = false;                 +
     SET client_min_messages = warning;                 +
     SET row_security = off;                            +
     SET search_path = public, pg_catalog;              +
     SET default_tablespace = '';                       +
     SET default_with_oids = false;                     +
     CREATE TABLE orasup_test1 (                        +
         a integer,                                     +
         b character varying(200)                       +
     );                                                 +
     ALTER TABLE orasup_test1 OWNER TO djidba_rw;       +
     CREATE INDEX idx_b ON orasup_test1 USING btree (b);+
     
    (1 row)
    
    postgres=# 
    postgres=#

    SQL Server数据文件结构

    $
    0
    0

    Data file 结构:

    第一个extent:

    page 0 : File Header,注m_type = 15,Metadata: ObjectId = 99
    
    page 1 : PFS( page free space),注m_type = 11,Metadata: ObjectId = 99
    
    page 2 : GAM ( global allocation map),注m_type = 8,Metadata: ObjectId = 99,每个GAM管理4GB的页面,所以每隔4GB大小的页面,会有一个GAM。
    
    page 3 : SGAM ( shared global allocation map),注m_type = 9,Metadata: ObjectId = 99
    
    page 4 : 空的,注m_type = 0,Metadata: ObjectId = 0
    
    page 5 : 空的,注m_type = 0,Metadata: ObjectId = 0
    
    page 6 : DCM(differential change map),注m_type = 16,Metadata: ObjectId = 99
    
    page 7 :BCM(bulk change map),注m_type = 17,Metadata: ObjectId = 99

    注意,如果大temp文件(一个超过4G)的page latch,往往是在temp数据文件的第一个extent的第2个page上,其争用可以用下面的sql监控:

    select count(*) as cnt from sys.sysprocesses where  DateDiff(ss,last_batch,getDate())>=5 and lastwaittype Like 'PAGE%LATCH_%' And waitresource Like '2:%'

    注,争用是指在多个进程都要使用temp数据文件的时候,需要找temp文件的free space,而free space的元数据,是记录在GAM中的。在GAM中容易出现争用。解决方案是使用多个temp数据文件,

    第二个extent:

    page 8 : DATA,注m_type = 1,Metadata: ObjectId = 90 ,类似sysqnames 这样的system base table,基表。
    
    page 9 : Boot Page,包含启动数据库的信息,注m_type = 13,Metadata: ObjectId = 99 ,通常情况,第一个数据文件的第9个page是boot page。
    
    page 10 : IAM,注m_type = 10,Metadata: ObjectId = 44 ,类似sysnsobjs的基表。
    
    page 11 : index page,注m_type = 2,Metadata: ObjectId = xx,基表
    
    page 12 : IAM,注m_type = 10,Metadata: ObjectId = xx,基表
    
    page 13 : IAM,注m_type = 10,Metadata: ObjectId = xx,基表
    
    page 14 : DATA,注m_type = 1,Metadata: ObjectId = xx,基表
    
    page 15 : IAM,注m_type = 10,Metadata: ObjectId = xx,基表

    第三个extent:

    page 16 : index page,注m_type = 2,Metadata: ObjectId = xx,基表
    
    page 17 : DATA,注m_type = 1,Metadata: ObjectId = xx,基表
    
    page 18 :  IAM,注m_type = 10,Metadata: ObjectId = xx,基表
    
    page 19 :  DATA,注m_type = 1,Metadata: ObjectId = xx,基表
    
    page 20 :  DATA,注m_type = 1,Metadata: ObjectId = xx,基表
    
    page 21 :  IAM,注m_type = 10,Metadata: ObjectId = xx,基表
    
    page 22 :  IAM,注m_type = 10,Metadata: ObjectId = xx,基表
    
    page 23 :  DATA,注m_type = 1,Metadata: ObjectId = xx,基表

    参考文档:
    《https://docs.microsoft.com/en-us/sql/relational-databases/pages-and-extents-architecture-guide?view=sql-server-ver15》

    《人人都是 DBA(VIII)SQL Server 页存储结构》
    http://www.cnblogs.com/gaochundong/p/everyone_is_a_dba_sqlserver_page_storage_structure.html

    《Sql server page & log file architecture by Sunil Kumar Anna》
    https://www.slideshare.net/sunilannakumar/sql-server-page-log-file-architecture

    mysql awr脚本部署

    $
    0
    0

    这个脚本是用来在mysql数据库中创建一个myawr数据库,记录数据库中记录active session。

    cat /root/user/myawr.sql

    create database myawr DEFAULT CHARACTER SET utf8mb4;
    
    use myawr;
    
    CREATE TABLE myawr.`processliststatus` (
      `ID` bigint(21) unsigned NOT NULL DEFAULT '0',
      `USER` varchar(32) NOT NULL DEFAULT '',
      `HOST` varchar(64) NOT NULL DEFAULT '',
      `DB` varchar(64) DEFAULT NULL,
      `COMMAND` varchar(16) NOT NULL DEFAULT '',
      `TIME` int(7) NOT NULL DEFAULT '0',
      `STATE` varchar(64) DEFAULT NULL,
      `INFO` longtext,
      `clock` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
      `snapid` bigint(20) DEFAULT NULL,
      KEY `idx_clock` (`clock`),
      key idx_snapid(snapid)
    );
    
    
    CREATE TABLE myawr.`mysqldumplog` (
      `ID` bigint unsigned NOT NULL DEFAULT '0',
      `USER` varchar(32) NOT NULL DEFAULT '',
      `HOST` varchar(64) NOT NULL DEFAULT '',
      `DB` varchar(64) DEFAULT NULL,
      `COMMAND` varchar(16) NOT NULL DEFAULT '',
      `TIME` int NOT NULL DEFAULT '0',
      `STATE` varchar(64) DEFAULT NULL,
      `INFO` longtext,
      `clock` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
      KEY `idx_clock` (`clock`)
    );
    
    
    CREATE TABLE myawr.`lockstatus` (
      `clock` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
      `wating_trx_state` varchar(255) DEFAULT NULL,
      `waiting_trx_id` bigint DEFAULT NULL,
      `waiting_thread` bigint DEFAULT NULL,
      `waiting_query` varchar(2000) DEFAULT NULL,
      `blocking_trx_state` varchar(255) DEFAULT NULL,
      `blocking_trx_id` bigint DEFAULT NULL,
      `blocking_thread` bigint DEFAULT NULL,
      `blocking_query` varchar(2000) DEFAULT NULL,
      `snapid` bigint(20) DEFAULT NULL,
      KEY `idx_clock` (`clock`),
      key idx_snapid(snapid)
    );
    
    
    DELIMITER $$
    CREATE PROCEDURE myawr.`partition_verify`(SCHEMANAME VARCHAR(64), TABLENAME VARCHAR(64), HOURLYINTERVAL INT(11))
    BEGIN
            DECLARE PARTITION_NAME VARCHAR(16);
            DECLARE RETROWS INT(11);
            DECLARE FUTURE_TIMESTAMP TIMESTAMP;
            /*
             * Check if any partitions exist for the given SCHEMANAME.TABLENAME.
             */
            SELECT COUNT(1) INTO RETROWS
            FROM information_schema.partitions
            WHERE table_schema = SCHEMANAME AND table_name = TABLENAME AND partition_name IS NULL;
            /*
             * If partitions do not exist, go ahead and partition the table
             */
            IF RETROWS = 1 THEN
                    /*
                     * Take the current date at 00:00:00 and add HOURLYINTERVAL to it.  This is the timestamp below which we will store values.
                     * We begin partitioning based on the beginning of a day.  This is because we don't want to generate a random partition
                     * that won't necessarily fall in line with the desired partition naming (ie: if the hour interval is 24 hours, we could
                     * end up creating a partition now named "p201403270600" when all other partitions will be like "p201403280000").
                     */
                    SET FUTURE_TIMESTAMP = TIMESTAMPADD(HOUR, HOURLYINTERVAL, CONCAT(CURDATE(), " ", '00:00:00'));
                    SET PARTITION_NAME = DATE_FORMAT(CURDATE(), 'p%Y%m%d%H00');
                    -- Create the partitioning query
                    SET @__PARTITION_SQL = CONCAT("ALTER TABLE ", SCHEMANAME, ".", TABLENAME, " PARTITION BY RANGE(UNIX_TIMESTAMP(`clock`))");
                    SET @__PARTITION_SQL = CONCAT(@__PARTITION_SQL, "(PARTITION ", PARTITION_NAME, " VALUES LESS THAN (", UNIX_TIMESTAMP(FUTURE_TIMESTAMP), "));");
                    -- Run the partitioning query
                    PREPARE STMT FROM @__PARTITION_SQL;
                    EXECUTE STMT;
                    DEALLOCATE PREPARE STMT;
            END IF;
    END $$
    
    
    
    CREATE PROCEDURE myawr.`partition_create` ( SCHEMANAME VARCHAR ( 64 ), TABLENAME VARCHAR ( 64 ), PARTITIONNAME VARCHAR ( 64 ), CLOCK INT ) BEGIN
    /*
    SCHEMANAME = The DB schema in which to make changes
    TABLENAME = The table with partitions to potentially delete
    PARTITIONNAME = The name of the partition to create
    */
    /*
    Verify that the partition does not already exist
    */
    DECLARE
    RETROWS INT;
    SELECT
    COUNT( 1 ) INTO RETROWS 
    FROM
    information_schema.PARTITIONS 
    WHERE
    table_schema = SCHEMANAME 
    AND table_name = TABLENAME 
    AND partition_description >= CLOCK;
    IF
    RETROWS = 0 THEN
    /*
    1. Print a message indicating that a partition was created.
    2. Create the SQL to create the partition.
    3. Execute the SQL from #2.
    */
    SELECT
    CONCAT( "partition_create(", SCHEMANAME, ",", TABLENAME, ",", PARTITIONNAME, ",", CLOCK, ")" ) AS msg;
    SET @SQL = CONCAT( 'ALTER TABLE ', SCHEMANAME, '.', TABLENAME, ' ADD PARTITION (PARTITION ', PARTITIONNAME, ' VALUES LESS THAN (', CLOCK, '));' );
    PREPARE STMT 
    FROM
    @SQL;
    EXECUTE STMT;
    DEALLOCATE PREPARE STMT;
    END IF;
    END $$
    
    
    
    CREATE PROCEDURE myawr.`partition_drop` ( SCHEMANAME VARCHAR ( 64 ), TABLENAME VARCHAR ( 64 ), DELETE_BELOW_PARTITION_DATE BIGINT ) BEGIN
    /*
    SCHEMANAME = The DB schema in which to make changes
    TABLENAME = The table with partitions to potentially delete
    DELETE_BELOW_PARTITION_DATE = Delete any partitions with names that are dates older than this one (yyyy-mm-dd)
    */
    DECLARE
    done INT DEFAULT FALSE;
    DECLARE
    drop_part_name VARCHAR ( 16 );
    /*
    Get a list of all the partitions that are older than the date
    in DELETE_BELOW_PARTITION_DATE.  All partitions are prefixed with
    a "p", so use SUBSTRING TO get rid of that character.
    */
    DECLARE
    myCursor CURSOR FOR SELECT
    partition_name 
    FROM
    information_schema.PARTITIONS 
    WHERE
    table_schema = SCHEMANAME 
    AND table_name = TABLENAME 
    AND CAST( SUBSTRING( partition_name FROM 2 ) AS UNSIGNED ) < DELETE_BELOW_PARTITION_DATE;
    DECLARE
    CONTINUE HANDLER FOR NOT FOUND 
    SET done = TRUE;
    /*
    Create the basics for when we need to drop the partition.  Also, create
    @drop_partitions to hold a comma-delimited list of all partitions that
    should be deleted.
    */
    SET @alter_header = CONCAT( "ALTER TABLE ", SCHEMANAME, ".", TABLENAME, " DROP PARTITION " );
    SET @drop_partitions = "";
    /*
    Start looping through all the partitions that are too old.
    */
    OPEN myCursor;
    read_loop :
    LOOP
    FETCH myCursor INTO drop_part_name;
    IF
    done THEN
    LEAVE read_loop;
    END IF;
    SET @drop_partitions =
    IF
    ( @drop_partitions = "", drop_part_name, CONCAT( @drop_partitions, ",", drop_part_name ) );
    END LOOP;
    IF
    @drop_partitions != "" THEN
    /*
                       1. Build the SQL to drop all the necessary partitions.
                       2. Run the SQL to drop the partitions.
                       3. Print out the table partitions that were deleted.
                    */
    SET @full_sql = CONCAT( @alter_header, @drop_partitions, ";" );
    PREPARE STMT 
    FROM
    @full_sql;
    EXECUTE STMT;
    DEALLOCATE PREPARE STMT;
    SELECT
    CONCAT( SCHEMANAME, ".", TABLENAME ) AS `table`,
    @drop_partitions AS `partitions_deleted`;
    ELSE /*
                       No partitions are being deleted, so print out "N/A" (Not applicable) to indicate
                       that no changes were made.
                    */
    SELECT
    CONCAT( SCHEMANAME, ".", TABLENAME ) AS `table`,
    "N/A" AS `partitions_deleted`;
    END IF;
    END $$
    
    
    
    CREATE PROCEDURE myawr.`partition_maintenance`(SCHEMA_NAME VARCHAR(32), TABLE_NAME VARCHAR(32), KEEP_DATA_DAYS INT, HOURLY_INTERVAL INT, CREATE_NEXT_INTERVALS INT)
    BEGIN
            DECLARE OLDER_THAN_PARTITION_DATE VARCHAR(16);
            DECLARE PARTITION_NAME VARCHAR(16);
            DECLARE OLD_PARTITION_NAME VARCHAR(16);
            DECLARE LESS_THAN_TIMESTAMP INT;
            DECLARE CUR_TIME INT;
            CALL partition_verify(SCHEMA_NAME, TABLE_NAME, HOURLY_INTERVAL);
            SET CUR_TIME = UNIX_TIMESTAMP(DATE_FORMAT(NOW(), '%Y-%m-%d 00:00:00'));
            SET @__interval = 1;
            create_loop: LOOP
                    IF @__interval > CREATE_NEXT_INTERVALS THEN
                            LEAVE create_loop;
                    END IF;
                    SET LESS_THAN_TIMESTAMP = CUR_TIME + (HOURLY_INTERVAL * @__interval * 3600);
                    SET PARTITION_NAME = FROM_UNIXTIME(CUR_TIME + HOURLY_INTERVAL * (@__interval - 1) * 3600, 'p%Y%m%d%H00');
                    IF(PARTITION_NAME != OLD_PARTITION_NAME) THEN
                            CALL partition_create(SCHEMA_NAME, TABLE_NAME, PARTITION_NAME, LESS_THAN_TIMESTAMP);
                    END IF;
                    SET @__interval=@__interval+1;
                    SET OLD_PARTITION_NAME = PARTITION_NAME;
            END LOOP;
            SET OLDER_THAN_PARTITION_DATE=DATE_FORMAT(DATE_SUB(NOW(), INTERVAL KEEP_DATA_DAYS DAY), '%Y%m%d0000');
            CALL partition_drop(SCHEMA_NAME, TABLE_NAME, OLDER_THAN_PARTITION_DATE);
    END$$
    
    
    
    CREATE PROCEDURE myawr.proc_awr_killdump( )
    BEGIN
    declare dump_id INT default false;
    DECLARE done INT DEFAULT FALSE;
    DECLARE myCursor CURSOR FOR SELECT id FROM information_schema.PROCESSLIST WHERE INFO like 'SELECT /*!40001 SQL_NO_CACHE */ * FROM%';
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    SET @dump_id ='';
    OPEN myCursor;
    read_loop :
    LOOP
    FETCH myCursor INTO dump_id;
    IF
    done THEN
    LEAVE read_loop;
    END IF;
    insert into myawr.mysqldumplog(ID,USER,HOST,DB,COMMAND,TIME,STATE,INFO) select ID,USER,HOST,DB,COMMAND,TIME,STATE,INFO from information_schema.processlist where id=dump_id;
    call mysql.rds_kill(dump_id);
    END LOOP;
    END
    $$
    
    
    
    
    CREATE procedure myawr.proc_awr_getstatus()
    begin
    declare insertSessionCount INT default 0; -- 声明insert会话的计数器  
    set @ha=unix_timestamp(now());
    -- 查看是否有insert 会话
    select count(*) into insertSessionCount  from  information_schema.processlist where INFO like 'insert into myawr.processliststatus%';
    -- 如果之前存在未结束的insert会话,则本次不insert
    IF  insertSessionCount <1   THEN  
    -- 排除非活跃会话和系统会话,并做截断操作
    insert into myawr.processliststatus(ID,USER,HOST,DB,COMMAND,TIME,STATE,INFO,snapid) select ID,USER,HOST,DB,COMMAND,TIME,STATE,substring(INFO,1,3000), @ha from information_schema.processlist  where COMMAND not in ('Sleep','Daemon','Binlog Dump GTID') and INFO not like 'insert into myawr.processliststatus%';   
    END IF;  
     
    insert into  myawr.lockstatus(wating_trx_state,waiting_trx_id,waiting_thread,waiting_query,blocking_trx_state,blocking_trx_id,blocking_thread,blocking_query,snapid) SELECT r.trx_state wating_trx_state,r.trx_id waiting_trx_id,r.trx_mysql_thread_Id waiting_thread,r.trx_query waiting_query,b.trx_state blocking_trx_state,b.trx_id blocking_trx_id,b.trx_mysql_thread_id blocking_thread,b.trx_query blocking_query,@ha FROM information_schema.innodb_lock_waits w INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id;
    end
    $$
    
    
    create procedure myawr.proc_awr_enable(proc_name varchar(64))
    begin
    /*declare proc_cursor varchar(64) default false;
    DECLARE done INT DEFAULT FALSE;
    DECLARE myCursor CURSOR FOR select name from mysql.event where db='myawr' and name like 'event\_awr\_%' and name not in ('event_awr_resetpartition');
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;*/
    if proc_name = 'all' then
    /*SET @proc_cursor ='';
    OPEN myCursor;
    read_loop :
    LOOP
    FETCH myCursor INTO proc_cursor;
    IF
    done THEN
    LEAVE read_loop;
    END IF;
    alter event proc_cursor enable;
    END LOOP;*/
    select "抱歉,该参数现在还没能实现,敬请期待" as msg;
    elseif proc_name = 'killdump' then
    alter event event_awr_killdump enable;
    select "enable event_awr_killdump succeed ~" as msg;
    elseif proc_name = 'getmysqlstatus' then
    alter event event_awr_getMysqlStatus enable;
    select "enable event_awr_getMysqlStatus succeed ~" as msg;
    else
    select "参量输入有误抑或没有布置awr脚本!" as msg;
    end if;
    end
    $$
    
    
    
    create procedure myawr.proc_awr_disable(proc_name varchar(64))
    begin
    /*declare proc_cursor varchar(64) default false;
    DECLARE done INT DEFAULT FALSE;
    DECLARE myCursor CURSOR FOR select name from mysql.event where db='myawr' and name like 'event\_awr\_%' and name not in ('event_awr_resetpartition');
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;*/
    if proc_name = 'all' then
    /*SET @proc_cursor ='';
    OPEN myCursor;
    read_loop :
    LOOP
    FETCH myCursor INTO proc_cursor;
    IF
    done THEN
    LEAVE read_loop;
    END IF;
    alter event proc_cursor enable;
    END LOOP;*/
    select "抱歉,该参数现在还没能实现,敬请期待" as msg;
    elseif proc_name = 'killdump' then
    alter event event_awr_killdump disable;
    select "disable event_awr_killdump succeed ~" as msg;
    elseif proc_name = 'getmysqlstatus' then
    alter event event_awr_getMysqlStatus disable;
    select "disable event event_awr_getMysqlStatus succeed ~" as msg;
    else
    select "参量输入有误抑或没有布置awr脚本!" as msg;
    end if;
    end
    $$
    
    CREATE PROCEDURE myawr.myawr_help()
    BEGIN
    select  "
    1、实现功能:
    1.1、默认每10s会收集一次mysql的processlist状态和锁等待情况。
    1.2、默认每9s检查是否存在dump操作,如果存在将会kill线程,并且将此记录在mysqldump_log表中。
    2、更改参数:
    2.1、修改脚本收集或检查的频率
     最小时间不应该小于7s,否则可能会对数据库性能产生影响。
    mysql> call proc_awr_changeint('killdump',5);
    2.2、启用或停止脚本部分功能
    mysql> call proc_awr_enable('all');
    mysql> call proc_awr_enable('killdump');
    mysql> call proc_awr_enable('getmysqlstatus');
    mysql> call proc_awr_disable('all');
    mysql> call proc_awr_disable('killdump');
    mysql> call proc_awr_disable('getmysqlstatus');
    3、dump操作:
    3.1、停止非法dump监控事件
    mysql> call proc_awr_disable('killdump');
    4.2、进行dump操作
    mysqldump --single-transaction ***
    3.3、恢复监控事件
    mysql> call proc_awr_enable('killdump');"  AS help_message;
    end
    $$
    
    
    create event myawr.event_awr_getMysqlStatus
    ON SCHEDULE
    -- 每隔10秒运行
    every 10 second
    on completion preserve
    do call proc_awr_getstatus();
    $$
    
    
    
    CREATE EVENT myawr.event_awr_killdump
    ON SCHEDULE 
    -- 每隔9秒运行
    EVERY 9 SECOND 
    ON COMPLETION PRESERVE
    DO CALL proc_awr_killdump ();
    $$
    
    
    
    
    create event myawr.event_awr_resetpartition
    ON SCHEDULE
    every 1 day
    on completion preserve
    do
    begin
    call partition_maintenance('myawr','processliststatus',7,24,30);
    call partition_maintenance('myawr','lockstatus',7,24,30);
    end
    $$

    Viewing all 196 articles
    Browse latest View live