Oracle ACE,腾讯云TVP,墨天轮MVP,Oracle 10g/11g OCP,11g OCM,Exin DevOps Master,OCMU成员,Oracle YEP成员,微信公众号:bisal的个人杂货铺
索引是一种奇特的对象,他就像一把双刃剑,用好了可以提高性能,用不好就可能会影响性能,但如何才能用好索引? 可能我们日常工作中,同事、朋友,甚至我自己会问这种问题, 我们创建了索引,为什么这条SQL未用这索引? 创建的索引越多,应用是不是就会越快? 只要SQL运行慢,一定是索引有问题? 应用是否用索引,是谁来决定的? 是否有了索引,应用就一定不会有性能问题? ... 其实这些问题,都蕴含着丰富的信息,就像学习Oracle一样,之所以说Oracle入门不难(例如写一些SQL语句),但要精通掌握就很难,原因就在于Oracle体系结构的庞大(从Oracle发布的官方文档数量,就能体现出来),而且还是在不断发展着,这次OOW大会,Larry就提出了Oracle 18c这个版本,会是一款自治(Autonomous)数据库,啥玩意儿啊这是,11g还没搞明白,这就18c了啊? 非常抱歉,有些扯远了,其实我要表达的,就是为了用好数据库索引,我们就需要首先了解索引,了解索引的一些基本知识,以及一些原理,做到知其然,更要知其所以然,这样才能更好地驾驭索引。 注:实验主要基于Oracle,一些知识点,其他数据库,可能通用。 为了这一个目的,我会总结一下,关注一些索引易混淆的知识,以及一些案例,做到查漏补缺,整理下自己的知识体系。我也是在不断的学习中,理解上可能会有偏差,可能会有出入,也欢迎朋友们及时指出来,共同学习,共同进步。 这篇文章,我们关注的是,索引的属性,有什么属性?作用是什么?什么场景使用? 我们先看下官方文档,对于索引属性的描述, UsabilityIndexes are usable (default) or unusable. An unusable index is not maintained by DML operations and is ignored by the optimizer. An unusable index can improve the performance of bulk loads. Instead of dropping an index and later re-creating it, you can make the index unusable and then rebuild it. Unusable indexes and index partitions do not consume space. When you make a usable index unusable, the database drops its index segment. 索引可以设置为usable(默认属性)或者unusable。unusable的索引做DML操作的时候,不会被维护,而且会被优化器忽略。unusable索引可以提升批量导入性能,且不会消耗空间。 VisibilityIndexes are visible (default) or invisible. An invisible index is maintained by DML operations and is not used by default by the optimizer. Making an index invisible is an alternative to making it unusable or dropping it. Invisible indexes are especially useful for testing the removal of an index before dropping it or using indexes temporarily without affecting the overall application. 索引可以设置为visible(默认属性)或者invisible。invisible的索引做DDL操作的时候,会被维护,但默认不会被优化器使用。在删除一个索引之前,或者临时使用一个索引时,用这种invisible的索引,特别有用,因为他不会影响应用性能。 创建测试表、数据和索引, SQL> create table test (id number, name varchar2(1));Table created. SQL> begin for i in 1 .. 10000 loop insert into test values(i, dbms_random.string('a',1)); end loop; commit; end;/PL/SQL procedure successfully completed. SQL> create index idx_test_01 on test(id);Index created. 实验一:usable和unusable 可以看出,上面提及的可用性(usable)和可见性(visible),是两个字段,值为valid和visible, SQL> select table_name, index_name, status, visibility from user_indexes;TABLE_NAME INDEX_NAME STATUS VISIBILITY---------- ----------- ---------- ----------TEST IDX_TEST_01 VALID VISIBLE 表占用空间196608, SQL> select segment_name, segment_type, bytes from user_segments where segment_name='TEST';SEGMENT_NAM SEGMENT_TYP BYTES----------- ----------- ----------TEST TABLE 196608 索引占用空间196608, SQL> select segment_name, segment_type, bytes from user_segments where segment_name='IDX_TEST_01';SEGMENT_NAM SEGMENT_TYP BYTES----------- ----------- ----------IDX_TEST_01 INDEX 196608 此时执行select * from test where id = 1,应该可以用索引, 将索引设置为unusable, SQL> alter index idx_test_01 unusable;Index altered. 此时索引status就变为了unusable, SQL> select table_name, index_name, status, visibility from user_indexes;TABLE_NAME INDEX_NAME STATUS VISIBILITY---------- ----------- ---------- ----------TEST IDX_TEST_01 UNUSABLE VISIBLE 而且之前的索引段空间,被删除了, SQL> select segment_name, segment_type, bytes from user_segments where segment_name='IDX_TEST_01';no rows selected 此时执行select * from test where id = 1,用的就是TABLE ACCESS FULL, 此时向表中插入数据, SQL> begin for i in 10001 .. 20000 loop insert into test values(i, dbms_random.string('a',1)); end loop; commit; end;/ PL/SQL procedure successfully completed. SQL> select count(*) from test; COUNT(*)---------- 20000 此时索引段,还是删除状态, SQL> select segment_name, segment_type, bytes from user_segments where segment_name='IDX_TEST_01';no rows selected 我们需要恢复索引状态,方法一是可以删除重建,方法二是使用rebuild, SQL> alter index idx_test_01 rebuild;Index altered. 现在索引的状态,status=valid, SQL> select table_name, index_name, status, visibility from user_indexes;TABLE_NAME INDEX_NAME STATUS VISIBILITY---------- ----------- ---------- ----------TEST IDX_TEST_01 VALID VISIBLE 此时索引段,已经重建,表和索引的空间,符合现在20000条数据的容量, SQL> select segment_name, segment_type, bytes from user_segments where segment_name='TEST';SEGMENT_NAM SEGMENT_TYP BYTES----------- ----------- ----------TEST TABLE 327680SQL> select segment_name, segment_type, bytes from user_segments where segment_name='IDX_TEST_01';SEGMENT_NAM SEGMENT_TYP BYTES----------- ----------- ----------IDX_TEST_01 INDEX 393216 以上实验,可以说明, 1. 索引设置为unusable,此时会删除索引段。 2. 索引处于unusable期间,对表数据做DML操作,此时不维护索引。 3. 索引处于unusable期间,优化器会忽略此索引。 4. 索引处于unusable期间,由于不需要维护索引,因此可以提升批量导入性能。 5. 索引unusable变为usable,有两种方法,一种是删除-重建索引,一种是使用alter index ... rebuild,两种方法,都相当于重新构建了索引。 实验二:visible和invisible 首先我们恢复,测试表包含10000条数据的状态, SQL> select count(*) from test; COUNT(*)---------- 10000 SQL> select table_name, index_name, status, visibility from user_indexes;TABLE_NAME INDEX_NAME STATUS VISIBILITY---------- ----------- ---------- ----------TEST IDX_TEST_01 VALID VISIBLE SQL> select segment_name, segment_type, bytes from user_segments where segment_name='TEST';SEGMENT_NAM SEGMENT_TYP BYTES----------- ----------- ----------TEST TABLE 196608 SQL> select segment_name, segment_type, bytes from user_segments where segment_name='IDX_TEST_01';SEGMENT_NAM SEGMENT_TYP BYTES----------- ----------- ----------IDX_TEST_01 INDEX 196608 设置索引状态为invisible, SQL> alter index idx_test_01 invisible;Index altered. SQL> select table_name, index_name, status, visibility from user_indexes;TABLE_NAME INDEX_NAME STATUS VISIBILITY---------- ----------- ---------- ----------TEST IDX_TEST_01 VALID INVISIBLE 执行select * from test where id = 1;,从执行计划看,未用索引, 但和之前unusable,不同的是,索引段未被删除, SQL> select segment_name, segment_type, bytes from user_segments where segment_name='IDX_TEST_01';SEGMENT_NAM SEGMENT_TYP BYTES----------- ----------- ----------IDX_TEST_01 INDEX 196608 此时向表中插入数据, SQL> begin for i in 10001 .. 20000 loop insert into test values(i, dbms_random.string('a',1)); end loop; commit; end;/ PL/SQL procedure successfully completed. SQL> select count(*) from test; COUNT(*)---------- 20000 可以看出,表和索引空间,均和数据量吻合, SQL> select segment_name, segment_type, bytes from user_segments where segment_name='TEST';SEGMENT_NAM SEGMENT_TYP BYTES----------- ----------- ----------TEST TABLE 327680SQL> select segment_name, segment_type, bytes from user_segments where segment_name='IDX_TEST_01';SEGMENT_NAM SEGMENT_TYP BYTES----------- ----------- ----------IDX_TEST_01 INDEX 393216 Oracle提供了一个参数,optimizer_use_invisible_indexes,可以控制优化器是否使用属性状态为invisible的这些索引,默认值是false, SQL> show parameter optimizer_use_invisible_indexesNAME TYPE VALUE------------------------------------ ---------- ------------------------------optimizer_use_invisible_indexes boolean FALSE 可以设置session级别optimizer_use_invisible_indexes值, SQL> alter session set optimizer_use_invisible_indexes=true;Session altered. 再次执行select * from test where id = 1;,此时执行计划用到了索引, 让索引设置为visible,直接使用alter index ... visible, SQL> alter index idx_test_01 visible;Index altered. SQL> select table_name, index_name, status, visibility from user_indexes;TABLE_NAME INDEX_NAME STATUS VISIBILITY---------- ----------- ---------- ----------TEST IDX_TEST_01 VALID VISIBLE 以上实验,可以说明, 1. 索引设置为invisible,不会删除索引段。 2. 索引处于invisible期间,对表数据做DML操作,此时会维护索引。 3. 索引处于invisible期间,优化器会忽略此索引,但可以使用optimizer_use_invisible_indexes控制。 4. 索引invisible变为visible,直接使用alter index ... visible。 实验三:同时设置unusable和invisible 首先我们恢复,测试表包含10000条数据的状态, SQL> select count(*) from test; COUNT(*)---------- 10000 SQL> select table_name, index_name, status, visibility from user_indexes;TABLE_NAME INDEX_NAME STATUS VISIBILITY---------- ----------- ---------- ----------TEST IDX_TEST_01 VALID VISIBLE SQL> select segment_name, segment_type, bytes from user_segments where segment_name='TEST';SEGMENT_NAM SEGMENT_TYP BYTES----------- ----------- ----------TEST TABLE 196608 SQL> select segment_name, segment_type, bytes from user_segments where segment_name='IDX_TEST_01';SEGMENT_NAM SEGMENT_TYP BYTES----------- ----------- ----------IDX_TEST_01 INDEX 196608 同时设置unusable和invisible, SQL> alter index idx_test_01 unusable;Index altered.SQL> alter index idx_test_01 invisible;Index altered.SQL> select table_name, index_name, status, visibility from user_indexes;TABLE_NAME INDEX_NAME STATUS VISIBILITY---------- ----------- ---------- ----------TEST IDX_TEST_01 UNUSABLE INVISIBLE 执行select * from test where id = 1;,从执行计划看,未用索引,但不能明确,这是因为unusable还是invisible, 可以看出,索引段已被删除, SQL> select segment_name, segment_type, bytes from user_segments where segment_name='IDX_TEST_01';no rows selected 以上实验,可以说明,unusable比invisible优先级要高,同时设置,起作用的是unusable。 实验四:disable和enable 索引还有一种状态disable和enable,但并不是通用的,例如对之前创建的索引,执行disable会报错, SQL> alter index idx_test_01 disable;alter index idx_test_01 disable*ERROR at line 1:ORA-02243: invalid ALTER INDEX or ALTER MATERIALIZED VIEW option 这是因为disable和enable只对函数索引有效,创建函数索引, SQL> create index idx_test_02 on test(upper(name)); Index created. 设置函数索引disable, SQL> alter index idx_test_02 disable;Index altered. 函数索引段未被删除, SQL> select segment_name, segment_type, bytes from user_segments where segment_name='IDX_TEST_02';SEGMENT_NAM SEGMENT_TYP BYTES----------- ----------- ----------IDX_TEST_02 INDEX 196608 user_indexes视图FUNCIDX_STATUS字段,表示的是函数索引的状态,有三个值, NULL - Index is not a function-based indexENABLED - Function-based index is enabledDISABLED - Function-based index is disabled 可以看出,此时函数索引,FUNCIDX_STATUS值为DISABLE, SQL> select table_name, index_name, status, visibility, funcidx_status from user_indexes;TABLE_NAME INDEX_NAME STATUS VISIBILITY FUNCIDX_STATUS---------- ----------- ---------- ---------- ------------------------TEST IDX_TEST_01 VALID VISIBLETEST IDX_TEST_02 VALID VISIBLE DISABLED 此时执行select * from test where name = upper('a');,不会用索引, 向表中插入数据,就会报错,禁止插入数据,因为函数索引DISABLED了,数据DML操作会维护索引,索引不能维护,进而不让插数据, SQL> begin for i in 10001 .. 20000 loop insert into test values(i, dbms_random.string('a',1)); end loop; commit; end;/ begin*ERROR at line 1:ORA-30554: function-based index BISAL.IDX_TEST_02 is disabledORA-06512: at line 3 所有需要维护索引的操作,都会报这个错, SQL> update test set name='b' where id=1;update test set name='b' where id=1*ERROR at line 1:ORA-30554: function-based index BISAL.IDX_TEST_02 is disabledSQL> delete from test where id=1;delete from test where id=1*ERROR at line 1:ORA-30554: function-based index BISAL.IDX_TEST_02 is disabled 当然,根据上面的结论,只要不维护索引,就应该可以操作, SQL> update test set id=1 where id=1;1 row updated. alter index ... enable,可以让函数索引enable, SQL> alter index idx_test_02 enable;Index altered.SQL> select table_name, index_name, status, visibility, funcidx_status from user_indexes;TABLE_NAME INDEX_NAME STATUS VISIBILITY FUNCIDX_STATUS---------- ----------- ---------- ---------- ------------------------TEST IDX_TEST_02 VALID VISIBLE ENABLED 以上实验,可以说明,函数索引disable,则所有涉及,这个函数索引维护的操作,会被禁止,且执行计划,不会用这索引。 总结: > 索引设置为unusable,会有以下特点, 1. 索引设置为unusable,此时会删除索引段。 2. 索引处于unusable期间,对表数据做DML操作,此时不维护索引。 3. 索引处于unusable期间,优化器会忽略此索引。 4. 索引处于unusable期间,由于不需要维护索引,因此可以提升批量导入性能。 5. 索引unusable变为usable,有两种方法,一种是删除-重建索引,一种是使用 alter index ... rebuild,两种方法,都相当于重新构建了索引。 > 索引设置为invisible,会有以下特点, 1. 索引设置为invisible,不会删除索引段。 2. 索引处于invisible期间,对表数据做DML操作,此时会维护索引。 3. 索引处于invisible期间,优化器会忽略此索引。 4. 索引invisible变为visible,直接使用alter index ... visible。> unusable比invisible优先级要高,同时设置,起作用的是unusable。 > 只有函数索引可以设置disable和enable,涉及函数索引维护的操作,会被禁止,且执行计划,不会用这索引。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
无论是应用运维,还是数据库运维,均可以分为“人肉”-“自动化”-“智能化”阶段,其中自动化阶段,主要是将一些人做的操作,尤其是一些重复性操作,封装为程序,一方面避免重复性操作,另一方面提高执行效率。自动化实现的过程中,经常使用的,可能就是shell脚本了,前段时间,从一个微信公众号,学习了赵班长写的一篇小文,循序渐进的方式,介绍了shell运维脚本的编写,小脚本有大智慧,几十行代码,夹杂着系统设计、代码规范等细节,值得学习。 以下脚本参考的原文:《如何不耍流氓的做运维之-SHELL脚本》,用shell脚本模拟mysql备份过程,循序渐进的三个脚本。 脚本一:记录日志的shell脚本,shell_template_1.sh #!/bin/bashSHELL_NAME="shell_template_1.sh"SHELL_DIR="/home/oracle/scripts/shell"SHELL_LOG="${SHELL_DIR}/${SHELL_NAME}.log"shell_log() { LOG_INFO=$1 echo "$(date "+%Y-%m-%d") $(date "+%H-%M-%S") : ${SHELL_NAME} : ${LOG_INFO}" >> ${SHELL_LOG}}shell_log "shell beginning, write log test"shell_log "sehll success, write log test" 负责打印日志的函数,shell_log(),接受第一个参数,作为需要打印的日志内容,shell_log()中定义了日志输出的格式(年-月-日 时-分-秒 : 脚本名称 : 日志内容),日志文件路径,则由$SHELL_LOG变量定义。 执行shell_template_1.sh,生成日志文件shell_template_1.sh.log, vi shell_template_1.sh.log 2017-10-11 11-34-39 : shell_template_1.sh : shell beginning, write log test2017-10-11 11-34-39 : shell_template_1.sh : sehll success, write log test 脚本二:直接执行的脚本很危险,要提示用户如何使用脚本,shell_template_2.sh #!/bin/bashSHELL_NAME="shell_template_2.sh"SHELL_DIR="/home/oracle/scripts/shell"SHELL_LOG="${SHELL_DIR}/${SHELL_NAME}.log"shell_log() { LOG_INFO=$1 echo "$(date "+%Y-%m-%d") $(date "+%H-%M-%S") : ${SHELL_NAME} : ${LOG_INFO}" >> ${SHELL_LOG}}shell_usage() { echo $"USAGE: $0 {backup}"}mysql_backup() { shell_log "mysql backup start" shell_log "mysql backup stop"}main() { case $1 in backup) mysql_backup ;; *) shell_usage; esac}main $1 shell_template_2.sh相比shell_template_1.sh,增加了如下, 1. shell_usage()函数,用于说明脚本使用方法,即“shell_template_2.sh 跟着backup参数”。 2. 主函数中则判断,若参数是backup,则执行mysql_backup(),否则执行shell_usage()函数,提示用户正确的使用方法。 错误的执行, sh shell_template_2.shUSAGE: shell_template_2.sh {backup} 正确的执行, sh shell_template_2.sh backup 日志显示, vi shell_template_2.sh.log 2017-10-11 11-35-37 : shell_template_2.sh : mysql backup start2017-10-11 11-35-37 : shell_template_2.sh : mysql backup stop 脚本三:避免多人同时执行脚本,需要增加锁机制,shell_template_3.sh !/bin/bashSHELL_NAME="shell_template_3.sh"SHELL_DIR="/home/oracle/scripts/shell"SHELL_LOG="${SHELL_DIR}/${SHELL_NAME}.log"LOCK_FILE="/home/oracle/scripts/shell/${SHELL_NAME}.lock"shell_log() { LOG_INFO=$1 echo "$(date "+%Y-%m-%d") $(date "+%H-%M-%S") : ${SHELL_NAME} : ${LOG_INFO}" >> ${SHELL_LOG}}shell_usage() { echo $"USAGE: $0 {backup}"}shell_lock() { touch ${LOCK_FILE} } shell_unlock() { rm -f ${LOCK_FILE}}mysql_backup() { if [ -f "${LOCK_FILE}" ]; then shell_log "${SHELL_NAME} is running" echo "${SHELL_NAME}" is running && exit fi shell_log "mysql backup start" shell_lock sleep 10 shell_log "mysql backup stop" shell_unlock}main() { case $1 in backup) mysql_backup ;; *) shell_usage; esac} main $1 shell_template_3.sh相比shell_template_2.sh,增加了如下, 1. 增加shell_lock()函数,用于touch一个文件,作为文件锁。 2. 增加shell_unlock()函数,用于删除1创建的锁文件。 3. mysql_backup()函数需要判断是否存在LOCK_FILE,若存在说明有用户正执行,此时提示信息,并退出程序,若不存在文件LOCK_FILE,则执行shell_lock创建锁文件,sleep 10秒,模拟执行,结束前执行shell_unlock,删除锁文件完成,此时其他用户可以执行。 用户A执行脚本, sh shell_template_3.sh backup sleep 10秒 用户B在10秒内,执行脚本,提示报错信息,并退出执行, sh shell_template_3.sh backup;shell_template_3.sh is running 日志显示, vi shell_template_3.sh.log 2017-10-11 11-42-03 : shell_template_3.sh : mysql backup start --用户A记录 2017-10-11 11-42-08 : shell_template_3.sh : shell_template.sh is running --用户B记录 2017-10-11 11-42-13 : shell_template_3.sh : mysql backup stop --用户A记录 如果再做规范些,可以加上注释, ####################################################### $Name: shell_template.sh# $Version: v1.0# $Function: Backup MySQL Databaes Template Script# $Author: Bisal# $Create Date: 2017-10-11# $Description: shell###################################################### 总结: 通过以上三个脚本,经历了“日志记录输出” -> “增加脚本执行的方法说明,并通过传递参数,避免直接执行脚本的风险” -> “利用文件锁,增加了锁机制,避免多人同时执行脚本,带来的可能风险”,这三个阶段,再加上注释,逐步完善,我们可以从中,汲取一些经验, 1. 编写shell经常是面向过程的,但用函数封装,可以让脚本清晰可读。 2. 通过文件锁机制,可以控制并发,其实这还有优化空间,有些场景应该允许用户并发,例如可以通过临时文件写入、合并操作的方法,实现用户并发。 3. 标准常量定义、清晰的注释、函数和变量大小写用法,细节中可以看出严谨,即使只有几行,也能体现出,一名优秀工程师的素质。 虽然离这些目标还有距离,但通过这些优秀的脚本,吸取经验,增长见识,我们会朝着目标前进,兄弟们加油! 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
有一个库,由于设置了PASSWORD_LIFE_TIME,且到期未重置密码,账户被锁了,手工解锁后,登录发现报错ORA-28002,明明解锁了,为何还会报错? ORA-28002是一个很简单的错误号, oerr ora 2800228002, 00000, "the password will expire within %s days"// *Cause: The user's account is about to about to expire and the password needs to be changed// *Action: change the password or contact the DBA 可以分为两个场景说明,首先创建profile和用户t_pro_user,其中PASSWORD_LIFE_TIME设置为1,表示密码只有1天有效期,然后将其赋予t_pro_user用户, SQL> create profile t_profile limit PASSWORD_LIFE_TIME 1; Profile created. SQL> create user t_pro_user identified by 123; User created. SQL> alter user t_pro_user profile t_profile; User altered. grant resource,connect to t_pro_user; Grant succeeded. 场景一:超过PASSWORD_LIFE_TIME,未更改此值前就登录一次,会报ORA-28002 1天之后,登录就会提示,ORA-28002错误,警告密码7天内就会过期, sqlplus t_pro_user/123 SQL*Plus: Release 11.2.0.4.0 Production on Mon Sep 18 12:03:16 2017 Copyright (c) 1982, 2013, Oracle. All rights reserved. ERROR: ORA-28002: the password will expire within 7 days Connected to: Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production With the Partitioning, OLAP, Data Mining and Real Application Testing options SQL> 注意:这里提示了7天,是由profile中的PASSWORD_GRACE_TIME参数控制的,默认值是7,表示密码过期之后还有多少天可以使用原密码。 此时将PASSWORD_LIFE_TIME设置为unlimited, SQL> alter profile t_profile limit PASSWORD_LIFE_TIME unlimited; Profile altered. 再次登录,提示相同的错误, sqlplus t_pro_user/123 SQL*Plus: Release 11.2.0.4.0 Production on Mon Sep 18 12:03:16 2017 Copyright (c) 1982, 2013, Oracle. All rights reserved. ERROR: ORA-28002: the password will expire within 7 days Connected to: Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production With the Partitioning, OLAP, Data Mining and Real Application Testing options SQL> 手工修改一次新密码, SQL> alter user t_pro_user identified by 123; User altered. 再次登录,就不会报错了, sqlplus t_pro_user/123 SQL*Plus: Release 11.2.0.4.0 Production on Mon Sep 18 12:04:01 2017 Copyright (c) 1982, 2013, Oracle. All rights reserved. Connected to: Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production With the Partitioning, OLAP, Data Mining and Real Application Testing options 场景二:超过PASSWORD_LIFE_TIME,但不登录,直接改此参数,再次登录,不会报ORA-28002 1天之后,不登录直接修改PASSWORD_LIFE_TIME, SQL> alter profile t_profile limit PASSWORD_LIFE_TIME unlimited; Profile altered. 再次登录,此时未有提示ORA-28002, sqlplus t_pro_user/123SQL*Plus: Release 11.2.0.4.0 Production on Mon Oct 2 17:38:37 2017Copyright (c) 1982, 2013, Oracle. All rights reserved.Connected to:Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit ProductionWith the Partitioning, OLAP, Data Mining and Real Application Testing options 其实,这篇MOS文章《ORA-28002 Even If Default Profile Has Limits Set To 'UNLIMITED' (文档 ID 292093.1)》有相关介绍, if we reset the default profile to have all the limits unlimited and set PASSWORD_VERIFY_FUNCTION to NULL, the restriction are still there because of the previous settings. Resetting the profile parameters is not enough. 如果PASSWORD_VERIFY_FUNCTION to NULL参数设置为NULL,不能仅仅依靠重新设置PASSWORD_LIFE_TIME参数。 If we change the password of the user(s) having default profile now (with PASSWORD_VERIFY_FUNCTION set to NULL), then the error ORA-28002 will not come. This is as expected because of the error ORA-28002. We can change the password of the user(s) to the same existing one. PASSWORD_VERIFY_FUNCTION to NULL参数设置为NULL,修改了用户的密码,就不会报错ORA-28002,这也是ORA-28002错误期望的结果,当然允许设置重复的密码,重要的不是密码是什么,而是重新设置了一次。 For example:ALTER PROFILE DEFAULT LIMIT PASSWORD_VERIFY_FUNCTION null;alter user scott identified by tiger;( alter user <username> identified by <same password>; ) 总结: 1. 如果用户密码超过了PASSWORD_LIFE_TIME的值,未被提示ORA-28002之前对PASSWORD_LIFE_TIME进行了修改,再次登录,不会提示ORA-28002。 2. 如果用户密码超过了PASSWORD_LIFE_TIME的值,曾经登陆过并被提示ORA-28002,此时PASSWORD_LIFE_TIME进行了修改,再次登录,仍会提示ORA-28002,必须手工再改一次密码,才能消除此错。重新设置密码,这是ORA-28002错误的初衷。 对于ORA-28000错误,应该和上述两个结论一致,有兴趣可以试试。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
看见微信群有位朋友问: truncate表,会将统计信息清除么? 有些朋友回复, 数据字典信息都没有了,统计信息就清除了,所以是没有统计信息的。 做个实验,跟踪一下truncate,应该比较清楚。 我做了10g的测试,发现那个表的last_analyzed还是有记录的。 truncate完统计信息还是在的,跟你10g还是11g没有关系,关键在你之前有没有收集统计信息,你之前都没有收集统计信息,last analyzed本来就是空的。 之前有记录,last_analyzed是不为空的,truncate表后,这个变成了空。 第二位朋友说的很对,究竟会不会删除统计信息,做一下实验,就可以了解了。 创建测试表, SQL> create table test (id number, name varchar2(1));Table created.SQL> begin for i in 1 .. 10000 loop insert into test values(i, dbms_random.string('a',1)); end loop; commit; end; /PL/SQL procedure successfully completed.SQL> create index idx_test on test(id);Index created.SQL> select count(*) from test; COUNT(*)---------- 10000 此时检索表的统计信息,记录是空的,检索索引的统计信息,是有记录的, SQL> select num_rows, to_char(last_analyzed,'yyyy-mm-dd hh24:mi:ss') last_analyzed from user_tables where table_name='TEST'; NUM_ROWS LAST_ANALYZED --------------- ----------------------------------------- SQL> select num_rows, sample_size, to_char(last_analyzed,'yyyy-mm-dd hh24:mi:ss') last_analyzed from user_indexes where table_name='TEST'; NUM_ROWS SAMPLE_SIZE LAST_ANALYZED---------- ----------- ----------------------------- 10000 10000 2017-10-08 15:55:42 手工以cascade=false收集统计信息, SQL> exec dbms_stats.gather_table_stats('BISAL','TEST',cascade=>false);PL/SQL procedure successfully completed. 可以看出,表的统计信息已近更新了, SQL> select num_rows, to_char(last_analyzed,'yyyy-mm-dd hh24:mi:ss') last_analyzed from user_tables where table_name='TEST'; NUM_ROWS LAST_ANALYZED---------- -------------------- 10000 2017-10-08 16:04:16 但是由于cascade=false,因此不会自动采集索引, SQL> select num_rows, sample_size, to_char(last_analyzed,'yyyy-mm-dd hh24:mi:ss') last_analyzed from user_indexes where table_name='TEST'; NUM_ROWS SAMPLE_SIZE LAST_ANALYZED---------- ----------- ----------------------------- 10000 10000 2017-10-08 15:55:42 以cascade=true采集统计信息,表和索引的统计信息更新了, SQL> exec dbms_stats.gather_table_stats('BISAL','TEST',cascade=>true);PL/SQL procedure successfully completed. SQL> select num_rows, to_char(last_analyzed,'yyyy-mm-dd hh24:mi:ss') last_analyzed from user_tables where table_name='TEST'; NUM_ROWS LAST_ANALYZED---------- -------------------- 10000 2017-10-08 16:07:18SQL> select num_rows, sample_size, to_char(last_analyzed,'yyyy-mm-dd hh24:mi:ss') last_analyzed from user_indexes where table_name='TEST'; NUM_ROWS SAMPLE_SIZE LAST_ANALYZED---------- ----------- --------------- 10000 10000 2017-10-08 16:07:18 此时执行truncate,清空表数据, SQL> truncate table test;Table truncated.SQL> select count(*) from test; COUNT(*)---------- 0 可以看出表和索引统计信息,没有被删除, SQL> select num_rows, to_char(last_analyzed,'yyyy-mm-dd hh24:mi:ss') last_analyzed from user_tables where table_name='TEST'; NUM_ROWS LAST_ANALYZED---------- -------------------- 10000 2017-10-08 16:07:18SQL> select num_rows, sample_size, to_char(last_analyzed,'yyyy-mm-dd hh24:mi:ss') last_analyzed from user_indexes where table_name='TEST'; NUM_ROWS SAMPLE_SIZE LAST_ANALYZED---------- ----------- --------------- 10000 10000 2017-10-08 16:07:18 执行一次统计信息采集,此时表和索引的统计信息,已经是最新了, SQL> exec dbms_stats.gather_table_stats('BISAL','TEST',cascade=>true);PL/SQL procedure successfully completed.SQL> select num_rows, to_char(last_analyzed,'yyyy-mm-dd hh24:mi:ss') last_analyzed from user_tables where table_name='TEST';NUM_ROWS LAST_ANALYZED--------- -------------------- 0 2017-10-08 16:25:06SQL> select num_rows, sample_size, to_char(last_analyzed,'yyyy-mm-dd hh24:mi:ss') last_analyzed from user_indexes where table_name='TEST'; NUM_ROWS SAMPLE_SIZE LAST_ANALYZED---------- ----------- -------------------- 0 0 2017-10-08 16:25:06 说明执行truncate,表的统计信息不会被删除,除非执行了统计信息采集,truncate table和表和索引的统计信息,没有任何关联。 另一方面,truncate会影响表是否可以被自动采集统计信息的任务触发,mon_mods_all$会记录自上次自动统计信息收集作业完成之后,对所有目标表的insert、delete和update操作所影响的记录数,即DML操作次数,以及目标表是否执行过truncate操作,主要用于每日统计信息采集作业判断是否需要采集此张表,对于这张视图mon_mods_all$的介绍,可以参考eygle的文章, http://www.eygle.com/archives/2009/09/mon_mods_is_use.html 比如如下表,记录数为10000,mon_mods_all$记录了一条信息,其中插入insert是10000,其他的字段,为空, SQL> select count(*) from test; COUNT(*)---------- 10000 SQL> exec dbms_stats.flush_database_monitoring_info();PL/SQL procedure successfully completed.SQL> select obj#, inserts, updates, deletes, flags from sys.mon_mods_all$ where obj#=18021; OBJ# INSERTS UPDATES DELETES FLAGS------ --------- --------- ---------- ------- 18021 10000 0 0 0 此时执行truncate,mon_mods_all$记录未变, SQL> truncate table test;Table truncated.SQL> select obj#, inserts, updates, deletes, flags from sys.mon_mods_all$ where obj#=18021; OBJ# INSERTS UPDATES DELETES FLAGS------- ---------- ---------- --------- -------- 18021 10000 0 0 0 此时执行一次dbms_stats.flush_database_monitoring_info(),显示FLAGS是1,表示执行过了truncate table, SQL> exec dbms_stats.flush_database_monitoring_info();PL/SQL procedure successfully completed.SQL> select obj#, inserts, updates, deletes, flags from sys.mon_mods_all$ where obj#=18021; OBJ# INSERTS UPDATES DELETES FLAGS------- ---------- ---------- --------- -------- 18021 10000 0 10000 1 再次执行统计信息采集,此时mon_mods_all$的记录就会被清空, SQL> exec dbms_stats.gather_table_stats('BISAL','TEST',cascade=>true);PL/SQL procedure successfully completed.SQL> select obj#, inserts, updates, deletes, flags from sys.mon_mods_all$ where obj#=18021;no rows selected 总结: 1. 执行truncate,表的统计信息不会被删除,除非执行了统计信息采集,truncate table和表和索引的统计信息,没有任何关联,对象是否有统计信息记录,取决于是否采集过统计信息,包括手工和自动两种方法。 2. 执行truncate,会将mon_mods_all$视图的FLAGS字段置位,变为1,自动采集统计信息作业,可以据此判断,是否需要采集这张表,当重新采集统计信息,就会删除mon_mods_all$保存的记录。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
前两天有位朋友,微信公众号提了一个问题,原文描述如下, 1. 我的需求是在tag库中执行一个处理,使得tag中所有用户seq的nextval与src库中一致。 2. 我在tag库的user1中创建了一个存储过程,代码逻辑为通过dblink(指向src库的user1,user1有读取dba视图的权限)查询源库的dba_sequence与tag库的对比,找出两库间nextval相差1000以上的,并在tag中获取create seq的语句,然后用src库中的nextval值替换,并在src库中按src库的nextval重建seq。 3. 问题出在,我没有sys用户或者dba权限,使用的是一个user1用户,过程建在user1中,但程序需要处理所有用户的seq,我写的过程是给dba用的,他能用sys执行。在用sys执行过程时,执行到dbms_metadata.get_ddl('SEQUENCE','SEQ1','USER2')时,会报错用户USER2中没有这个序列号。但如果不通过user1的这个存储过程,而是直接在sys用户中执行语句dbms_metadata.get_ddl。。。就可以正常获取create语句,我不明白,执行者是sys,执行的是user1的过程,权限要按照user1的吗?但我尝试给user1授权其他用户序列号的使用权限也不行。 刚又进行了个实验,在user1中create or replace procedure user1.p_seq_test as LV_SQL VARCHAR2(1024);begin execute immediate 'create table user2.t_dataread_test1(col1 number)';END;/在sys中begin user1.p_seq_test;end;执行报错没有权限。但是我用sys进行grant create any table to user1后就可以了。我之前以为,虽然procedure在user1下,但是我用sys执行,权限应该是按照sys的权限走,但实际实验看即使sys执行存储过程,权限也是按照存储过程的属主用户走的。只不过是我前面说过的问题中,我始终没有找到能让USER1成功执行dbms_metadata.get_ddl('SEQUENCE','SEQ_TEST','USER2')所需要的权限,也就是user1能操作user2的sequence的权限。 按照理解,总结一下问题, 1. 用户user1定义的存储过程,即使用sys用户执行,需要参考user1权限? 2. 用户user1中创建一个序列,sys用户可以执行dbms_metadata.get_ddl('SEQUENCE','SEQ','USER1')得到序列创建语句,但user1用户看不了属于user2的序列定义? 问题1:用户user1定义的存储过程,即使用sys用户执行,需要参考user1权限? 我们先看问题1,创建测试用户user1和user2, SQL> create user user1 identified by 123;User created.SQL> create user user2 identified by 123;User created. SQL> grant connect, resource to user1;Grant succeeded. SQL> grant connect, resource to user2;Grant succeeded. sys用户创建属于user1的存储过程, SQL> create or replace procedure user1.p_seq_test as LV_SQL VARCHAR2(1024); begin execute immediate 'create table user2.t_dataread_test1(col1 number)'; END; /Procedure created. sys用户执行这个存储过程, 提示权限错误, SQL> begin user1.p_seq_test; end; /begin*ERROR at line 1:ORA-01031: insufficient privilegesORA-06512: at "USER1.P_SEQ_TEST", line 4ORA-06512: at line 2 授予user1用户create any table权限, SQL> grant create any table to user1;Grant succeeded.SQL> begin user1.p_seq_test; end; /PL/SQL procedure successfully completed. 从现象来看,即使使用sys执行user1的存储过程,权限参考的是user1,不是sys,因此由于user1没有create any table的权限,报错ORA-01031: insufficient privileges,注意编译过程未报错,而是执行过程中报错了。 杨长老有篇文章,其实提及了类似的问题,http://m.blog.itpub.net/4227/viewspace-69047, SQL> CREATE OR REPLACE PROCEDURE P_TEST AS BEGIN FOR I IN (SELECT DBMS_METADATA.GET_DDL('TABLE', 'DUAL', 'SYS') DEFINE FROM DUAL) LOOP DBMS_OUTPUT.PUT_LINE(SUBSTR(I.DEFINE, 1, 255)); END LOOP; END; / 一直就认为是角色导致的问题,而没有继续深究。而这次仔细看了Tom对定义者权限和调用者权限存储过程的描述才真正彻底清楚了导致上述现象的原因。 一个调用者权限的存储过程,如果在定义者权限存储过程中被调用,则它的行为表现将像一个定义者权限的过程。这时由于定义者权限过程中,CURRENT_SCHEMA和所拥有的权限都是固定的,调用者权限过程中所有可能发生变化的东西都被固定了下来。 而如果直接调用或者通过调用者权限过程来调用,那么这个调用者权限过程的全部特性得以保留。而这就是上面碰到的那个问题的真正答案。 Tom的书《Expert one-on-one Oracle》中单独有一章节,介绍的就是,调用者和定义者, 定义者(Definer)-指的是编译存储对象的拥有者,包括包、存储过程、函数、触发器和视图。 调用者(Invoker)-指当前会话中生效的schema,不一定就是当前登录的用户。 Oracle 8i之前,所有编译存储对象的执行,都是以定义者权限为准,因此编译阶段就会发现错误,不会像上面,等待运行阶段才报错。 从Oracle 8i开始,引入了invoker rights-调用者,允许包、存储过程、函数、触发器和视图这些对象的权限,以运行时的调用者为准。 引用Tom的实验,首先user1用户,创建如下两个存储过程,分为定义者权限,和调用者权限,并将这两个存储过程,执行权限授予user2, create or replace procedure definer_procasbeginfor x in( select sys_context('userenv', 'current_user') current_user, sys_context('userenv', 'session_user') session_user,sys_context('userenv', 'current_schema') current_schemafrom dual )loop dbms_output.put_line('Current User: ' || x.current_user ); dbms_output.put_line('Session User: ' || x.session_user ); dbms_output.put_line('Current Schema: ' || x.current_schema );end loop;end;/ Procedure created. create or replace procedure invoker_proc AUTHID CURRENT_USERasbeginfor x in( select sys_context('userenv', 'current_user') current_user, sys_context('userenv', 'session_user') session_user,sys_context('userenv', 'current_schema') current_schemafrom dual )loop dbms_output.put_line('Current User: ' || x.current_user ); dbms_output.put_line('Session User: ' || x.session_user ); dbms_output.put_line('Current Schema: ' || x.current_schema );end loop;end;/ SQL> grant execute on definer_proc to user2; Grant succeeded.SQL> grant execute on invoker_proc to user2;Grant succeeded. 接着使用user2,分别执行, SQL> exec user1.definer_proc;Current User: USER1Session User: USER2Current Schema: USER1PL/SQL procedure successfully completed.SQL> exec user1.invoker_procCurrent User: USER2Session User: USER2Current Schema: USER2PL/SQL procedure successfully completed. 可以看出,使用定义者权限,Current User和Current Schema均为user1,因为存储过程属于user1,但调用者权限,由于调用者是user2,因此Current User和Current Schema均为user2。 尝试设置current_schema,动态改变用户所用的schema,可以看出,第一个存储过程为定义者,没有任何变化,第二个存储过程为调用者,Current User是user2,Current Schema则变为了之前设置的system,说明定义者权限,是相对静态的,而调用者权限,则相对动态, SQL> alter session set current_schema=system;Session altered.SQL> exec user1.definer_proc;Current User: USER1Session User: USER2Current Schema: USER1PL/SQL procedure successfully completed.SQL> exec user1.invoker_proc;Current User: USER2Session User: USER2Current Schema: SYSTEMPL/SQL procedure successfully completed. 对于存储过程,dba_procedures中AUTHID字段,表示当前的存储过程/函数,定义为“定义者”还是“调用者”, 11.2.0.4下,记录分布如下, 本文开始的问题,CREATE TABLE语句的存储过程,从现象来看,是定义者的权限,即使使用sys创建和执行,参考的是user1是否有相应权限,未参考sys用户自己的权限。 问题二:用户user1中创建一个序列,sys用户可以执行dbms_metadata.get_ddl('SEQUENCE','SEQ','USER1')得到序列创建语句,但user1用户看不了属于user2的序列定义? 其实第一个问题解决了,第二个问题,就容易理解了。 我们先模拟下实验过程,用sys为用户user1和user2创建序列, SQL> create sequence user1.SEQ_TEST;Sequence created.SQL> create sequence user2.SEQ_TEST;Sequence created. sys用户执行dbms_metadata.get_ddl,一切ok, SQL> set serveroutput onSQL> select dbms_metadata.get_ddl('SEQUENCE','SEQ_TEST','USER2') from dual;DBMS_METADATA.GET_DDL('SEQUENCE','SEQ_TEST','USER2')--------------------------------------------------------------------------------CREATE SEQUENCE "USER2"."SEQ_TEST" MINVALUE 1 MAXVALUE 9999999999999999999SQL> select dbms_metadata.get_ddl('SEQUENCE','SEQ_TEST','USER1') from dual;DBMS_METADATA.GET_DDL('SEQUENCE','SEQ_TEST','USER1')--------------------------------------------------------------------------------CREATE SEQUENCE "USER1"."SEQ_TEST" MINVALUE 1 MAXVALUE 9999999999999999999 用户user1检索user1自己的SEQ_TEST序列,可以正常操作, SQL> select dbms_metadata.get_ddl('SQUENCE','SEQ_TEST','USER1') from dual;DBMS_METADATA.GET_DDL('SEQUENCE','SEQ_TEST','USER1')--------------------------------------------------------------------------------CREATE SEQUENCE "USER1"."SEQ_TEST" MINVALUE 1 MAXVALUE 9999999999999999999 用户user1检索user2所属的SEQ_TEST序列, 报错USER2中找不着这个SEQ_TEST序列对象, SQL> select dbms_metadata.get_ddl('SQUENCE','SEQ_TEST','USER2') from dual;ERROR:ORA-31603: object "SEQ_TEST" of type SEQUENCE not found in schema "USER2"ORA-06512: at "SYS.DBMS_METADATA", line 5805ORA-06512: at "SYS.DBMS_METADATA", line 8344ORA-06512: at line 1no rows selected 对于某一个具体的对象,可以检索AUTHID字段,判断其为定义者,还是调用者权限,可以看出DBMS_METADATA.GET_DDL()就是调用者权限, 因此执行的时候,参考的是执行用户的权限,sys用户有检索user1和user2对象定义的权限,user1有检索自己对象的权限,但没有检索其他用户对象的权限。 这篇文章DBMS_METADATA.GET_DDL Returns Error When Select Types Ora-31603 (文档 ID 312883.1),针对这种问题,指出了原因所在, The appropriate privileges have not been granted to the schema executing the procedure as the same procedure works fine from the schema that owns the object and from SYS. 给出了两种解决方案, Step 1: Execute "GRANT SELECT_CATALOG_ROLE TO <schema>;" as SYS or another user with the privilege.Step 2. Modify the procedure to include "AUTHID CURRENT_USER" 方法1:授予GRANT SELECT_CATALOG_ROLE角色 sys用户执行, SQL> GRANT SELECT_CATALOG_ROLE TO user1;Grant succeeded. user1此时可以检索user2对象定义, SQL> select dbms_metadata.get_ddl('SEQUENCE','SEQ_TEST','USER2') from dual;DBMS_METADATA.GET_DDL('SEQUENCE','SEQ_TEST','USER2')--------------------------------------------------------------------------------CREATE SEQUENCE "USER2"."SEQ_TEST" MINVALUE 1 MAXVALUE 9999999999999999999 但这种方法不很推荐,因为SELECT_CATALOG_ROLE角色,允许用户访问所有数据字典,一般用户不应该有角色,权限级别较高。 方法2:使用“AUTHID CURRENT_USER”定义存储过程 如果将“dbms_metadata.get_ddl”封装于存储过程,可以参考杨长老这篇http://m.blog.itpub.net/4227/viewspace-69047,文章中就用了这种定义, CREATE OR REPLACE PROCEDURE P_TEST AUTHID CURRENT_USER ASBEGINFOR I IN (SELECT DBMS_METADATA.GET_DDL('TABLE', 'DUAL', 'SYS') DEFINE FROM DUAL) LOOPDBMS_OUTPUT.PUT_LINE(SUBSTR(I.DEFINE, 1, 255));END LOOP;END;/ 提问的兄弟很认真,回复我如下,这种追求问题答案的态度,值得我们学习, 我又折腾了两三个小时,写了个程序把SELECT_CATALOG_ROLE角色对应的2238个表或视图、过程的授权以及被包含在这个角色中的另一个角色HS_ADMIN_SELECT_ROLE都试过了,程序内用sys每授权一个对象后都会用user1重新登录并执行dbms_metadata.get_ddl处理,但实验结果没有像预期那样能知道到底是哪个对象授权有影响,所有对象都授权了仍然无法正常获取seq的create ddl。最后还是授权角色SELECT_CATALOG_ROLE才管用。我不打算再试了,感觉oracle可能还会有其他很隐蔽的内部逻辑。 我们使用fyunwrap(之前这篇文章《dbms_space.create_table_cost的unwrap解密和原理解析》介绍过),来看看这个dbms_metadata.get_ddl。 unwrap这个dbms_metadata包,可以看出,get_ddl是一个函数,调用了do_get函数, FUNCTION GET_DDL ( OBJECT_TYPE IN VARCHAR2, NAME IN VARCHAR2, SCHEMA IN VARCHAR2 DEFAULT NULL, VERSION IN VARCHAR2 DEFAULT 'COMPATIBLE', MODEL IN VARCHAR2 DEFAULT 'ORACLE', TRANSFORM IN VARCHAR2 DEFAULT 'DDL') RETURN CLOB IS BEGIN DO_GET(OBJECT_TYPE,NAME,SCHEMA,VERSION,MODEL,TRANSFORM,1,'GET_DDL'); RETURN DBMS_ASSERT.NOOP(GET$_DOC); END; 从do_get函数定义,可以看出,这是通用函数,其中参数PUBLIC_FUNC,接收的是'GET_DDL'参数, PROCEDURE DO_GET ( OBJECT_TYPE IN VARCHAR2, NAME IN VARCHAR2, SCHEMA IN VARCHAR2, VERSION IN VARCHAR2, MODEL IN VARCHAR2, TRANSFORM IN VARCHAR2, OBJECT_COUNT IN NUMBER, PUBLIC_FUNC IN VARCHAR2, NETWORK_LINK IN VARCHAR2 DEFAULT NULL) IS HANDLE NUMBER; SAVE_HANDLE NUMBER := CUR_HANDLE; IND NUMBER; TR_HANDLE NUMBER; SCHEMA_NAME VARCHAR2(30); OBJECT_NAME VARCHAR2(2000) := ' '; DUMMY_PARSE_ITEMS SYS.KU$_PARSED_ITEMS := SYS.KU$_PARSED_ITEMS(); DUMMY BOOLEAN; DUMMY_PROCOBJ_ERRORS SYS.KU$_VCNT; BEGIN IF GET$_DOC IS NOT NULL THEN DBMS_LOB.FREETEMPORARY(GET$_DOC); END IF; DBMS_LOB.CREATETEMPORARY(GET$_DOC,TRUE); HANDLE := DO_OPEN(OBJECT_TYPE, VERSION, MODEL, PUBLIC_FUNC, NETWORK_LINK); CUR_HANDLE := HANDLE; IF PUBLIC_FUNC = 'GET_XML' OR PUBLIC_FUNC = 'GET_SXML' OR PUBLIC_FUNC = 'GET_SXML_DDL' OR PUBLIC_FUNC = 'GET_DDL' THEN OBJECT_NAME := NAME; IF LENGTH(NAME) > 30 THEN SET_FILTER(HANDLE,'LONGNAME',DBMS_ASSERT.NOOP(NAME)); ELSE SET_FILTER(HANDLE,'NAME',DBMS_ASSERT.NOOP(NAME)); END IF; IF SCHEMA IS NOT NULL THEN SET_FILTER(HANDLE,'SCHEMA',DBMS_ASSERT.NOOP(SCHEMA)); END IF; ELSIF PUBLIC_FUNC = 'GET_DEPENDENT_XML' OR PUBLIC_FUNC = 'GET_DEPENDENT_SXML' OR PUBLIC_FUNC = 'GET_DEPENDENT_DDL' THEN SET_FILTER(HANDLE,'BASE_OBJECT_NAME',DBMS_ASSERT.NOOP(NAME)); IF SCHEMA IS NOT NULL THEN SET_FILTER(HANDLE,'BASE_OBJECT_SCHEMA',DBMS_ASSERT.NOOP(SCHEMA)); END IF; ELSIF PUBLIC_FUNC = 'GET_GRANTED_XML' OR PUBLIC_FUNC = 'GET_GRANTED_DDL' THEN IF NAME IS NOT NULL THEN SET_FILTER(HANDLE,'GRANTEE',DBMS_ASSERT.NOOP(NAME)); ELSE SCHEMA_NAME := GET_CURRENT_USER; SET_FILTER(HANDLE,'GRANTEE',DBMS_ASSERT.NOOP(SCHEMA_NAME)); END IF; END IF; SET_COUNT(HANDLE,OBJECT_COUNT,OBJECT_TYPE); IF TRANSFORM IS NULL THEN SET_XMLFORMAT(HANDLE,'PRETTY',TRUE); ELSIF PUBLIC_FUNC = 'GET_SXML_DDL' AND TRANSFORM = 'SXMLDDL' THEN TR_HANDLE := DBMS_METADATA.ADD_TRANSFORM(HANDLE,'SXML'); TR_HANDLE := DBMS_METADATA.ADD_TRANSFORM(HANDLE,'SXMLDDL'); ELSE TR_HANDLE := DBMS_METADATA.ADD_TRANSFORM(HANDLE,TRANSFORM); IF MODEL = 'ORACLE' AND TRANSFORM = 'DDL' THEN DBMS_METADATA.SET_TRANSFORM_PARAM(TR_HANDLE,'INHERIT',TRUE); END IF; END IF; IND := GET_CONTEXT_ENTRY(HANDLE,PUBLIC_FUNC,TRUE); DUMMY := DO_FETCH(IND,GET$_DOC,DUMMY_PARSE_ITEMS,0,DUMMY_PROCOBJ_ERRORS); DBMS_METADATA.CLOSE(HANDLE); IF GET$_DOC IS NULL THEN IF PUBLIC_FUNC = 'GET_XML' OR PUBLIC_FUNC = 'GET_SXML' OR PUBLIC_FUNC = 'GET_SXML_DDL' OR PUBLIC_FUNC = 'GET_DDL' THEN IF SCHEMA IS NOT NULL THEN SCHEMA_NAME := SCHEMA; ELSE SCHEMA_NAME := GET_CURRENT_USER; END IF; CUR_HANDLE := SAVE_HANDLE; DBMS_SYS_ERROR.RAISE_SYSTEM_ERROR(OBJECT_NOT_FOUND_NUM, OBJECT_NAME, OBJECT_TYPE, SCHEMA_NAME); ELSE CUR_HANDLE := SAVE_HANDLE; DBMS_SYS_ERROR.RAISE_SYSTEM_ERROR(OBJECT_NOT_FOUND2_NUM, OBJECT_TYPE); END IF; END IF; CUR_HANDLE := SAVE_HANDLE; EXCEPTION WHEN OTHERS THEN BEGIN CUR_HANDLE := SAVE_HANDLE; RAISE; END; END; 其中调用了do_fetch、do_fetch_local、do_fetch_direct等一系列函数,看的有些晕了,但可以说明一点,绝不是仅授权SELECT_CATALOG_ROLE角色中包含的某一个视图就可以执行dbms_metadata包,视图之间是有关联关系的。 总结: 1. 对于Definer和Invoker的含义要理解,Definer权限比较静态,Invoker权限则相对动态,通过procedures视图的AUTHID字段,可以了解对象,属于定义者还是调用者权限。 2. 两种执行DBMS_METADATA.GET_DDL权限相关的workaround,一种是授予SELECT_CATALOG_ROLE角色,一种是使用"AUTHID CURRENT_USER"定义存储过程,针对不同场景,可以选择不同的方案解决。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
今儿有位同事提出,一套MySQL 5.6的环境,从数据库服务器本地登录,一切正常,可是若从远程服务器访问,就会报错, ERROR 1045 (28000): Access denied for user 'bisal'@'x.x.x.x' (using password: YES) 我才开始接触MySQL,因此每一个错误场景,都是增长经验的机会,这种错误要么是密码错误,要么是未设置远程IP访问权限。 我们模拟下这个过程,首先,创建用户bisal,如果密码不加引号会报错, mysql> create user bisal identified by bisal; ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'bisal' at line 1 创建完成,可以看出,用户bisal的host是%,不是具体某个IP, mysql> create user bisal identified by 'bisal'; Query OK, 0 rows affected (0.00 sec) mysql> select user, password, host from user; +-------+-------------------------------------------+-----------------+ | user | password | host | +-------+-------------------------------------------+-----------------+ ... | bisal | *9AA096167EB7110830776F0438CEADA9A7987E31 | % |+-------+-------------------------------------------+-----------------+ 实验一:让指定IP访问数据库 假设数据库服务器IP是x.x.x.1,授权让x.x.x.3用户可以访问, mysql> grant all privileges on *.* to 'bisal'@'x.x.x.3'; Query OK, 0 rows affected (0.00 sec) 此时从x.x.x.2上访问数据库,就会提示错误,因为仅允许x.x.x.3服务器,可以访问数据库, mysql -h x.x.x.1 -ubisal ERROR 1045 (28000): Access denied for user 'bisal'@'app' (using password: YES) 授权让x.x.x.2用户可以访问, mysql> grant all privileges on *.* to 'bisal'@'x.x.x.2' identified by 'bisal'; Query OK, 0 rows affected (0.00 sec) 此时从x.x.x.2上,就可以访问数据库了, mysql -h x.x.x.1 -ubisal -pbisal Warning: Using a password on the command line interface can be insecure. Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 1008 Server version: 5.6.31-log MySQL Community Server (GPL) Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> use mysql Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed 实验二:让所有IP访问数据库 首先,收回刚才的授权, mysql> revoke all privileges on *.* from bisal@'%'; Query OK, 0 rows affected (0.00 sec) mysql> show grants for bisal; +--------------------------------------------------------------------------------------------+ | Grants for bisal@% | +--------------------------------------------------------------------------------------------+ | GRANT USAGE ON *.* TO 'bisal'@'%' IDENTIFIED BY PASSWORD '*9AA096167EB7110830776F0438CEADA9A7987E31' | +--------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec) 此时从x.x.x.2访问数据库,会提示错误, mysql -h x.x.x.x -ubisal -pbisal Warning: Using a password on the command line interface can be insecure. Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 997 Server version: 5.6.31-log MySQL Community Server (GPL) Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> use mysql ERROR 1044 (42000): Access denied for user 'bisal'@'%' to database 'mysql' 此时授予%所有机器访问权限, mysql> grant all privileges on *.* to 'bisal'@'%' identified by 'bisal'; Query OK, 0 rows affected (0.00 sec) 从x.x.x.2访问数据库,此处的报错,是因为未输入密码, mysql -ubisal ERROR 1045 (28000): Access denied for user 'bisal'@'localhost' (using password: YES) 但如果之前设置的密码,和输入的密码不同,还是会提示错误, mysql> grant all privileges on *.* to 'bisal'@'%' identified by '123'; Query OK, 0 rows affected (0.00 sec) [root@vm-kvm11853-app ~]# mysql -h x.x.x.129 -ubisal -pbisal Warning: Using a password on the command line interface can be insecure. ERROR 1045 (28000): Access denied for user 'bisal'@'vm-kvm11853-app' (using password: YES) 使用正确的密码登录,一切正常了, mysql -ubisal -p123 Warning: Using a password on the command line interface can be insecure. Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 987 Server version: 5.6.31-log MySQL Community Server (GPL) Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> use mysql Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed 总结: 1. MySQL中可以设置某个IP访问权限,也可以设置%所有IP访问权限。、 2. grant all privileges ... identified by 'password',此处的password可以不是这用户的密码,远程访问以这个密码为准。 3. create user设置密码,需要用引号括起来,否则会提示语法错误。 4. create user用户不加@信息,则默认创建的用户host是%。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
有一位兄弟,问了一问题, 用触发器实现一个功能,如果插入的字段AGE为空,则将此字段AGE的值置为0。 以下是一版实现, SQL> create table t (id number, age number);Table created. SQL> CREATE OR REPLACE TRIGGER TR1 AFTER INSERT ON T FOR EACH ROW WHEN (NEW.AGE='')BEGIN UPDATE T SET AGE=0 WHERE ID = :NEW.ID;END;/ Trigger created. 执行插入操作,但NULL值,并未改为0, SQL> insert into t values(1, '');1 row created.SQL> select * from t; ID AGE---------- ---------- 1 我对触发器,了解非常有限,只能试着来,乍一看判断空,即NULL,是不能用“=”,需要使用IS NULL/IS NOT NULL,改了一下,执行报错, SQL> CREATE OR REPLACE TRIGGER TR1 AFTER INSERT ON T FOR EACH ROW WHEN (NEW.AGE IS NULL)BEGIN UPDATE T SET AGE=0 WHERE ID = :NEW.ID;END;/ Trigger created. SQL> insert into t values(1, '');insert into t values(1, '') *ERROR at line 1:ORA-04091: table BISAL.T is mutating, trigger/function may not see itORA-06512: at "BISAL.TR1", line 2ORA-04088: error during execution of trigger 'BISAL.TR1'oerr ora 409104091, 00000, "table %s.%s is mutating, trigger/function may not see it"// *Cause: A trigger (or a user defined plsql function that is referenced in // this statement) attempted to look at (or modify) a table that was // in the middle of being modified by the statement which fired it.// *Action: Rewrite the trigger (or function) so it does not read that table. 《After Update Trigger Fails With ORA-04091 When Modifying a Column in the Same Table (文档 ID 156388.1)》指出, A mutating table is a table that is currently being modified by an update, delete, or insert statement. Oracle returns the ORA-04091 error if a row trigger reads or modifies the mutating table. For example, ifa trigger contains a select statement or an update statement referencing the table it is triggering off of. 翻译一下,mutating table是指一个当前正在被update,delete,insert语句修改的表,如果在一个行级别的trigger中读取或修改一个mutating table,则往往会遇到ORA-04091错误。例如,如果在trigger中使用了select或者update语句访问trigger所在的表,就像上面这个触发器。 解决方法,使用PLSQL存储需要更新行的ROWID,在触发器中使用这个值,即利用临时变量,保存行信息, One way to handle this situation is to use a package PL/SQL table to store ROWIDs of updated records in a row trigger, and reprocess the updated records in a statement trigger. 参考这篇文章,《SQL: Example Workaround for ORA-4091 Error (文档 ID 37861.1)》。 除此之外,自治事务是另一种方法,重新写触发器,插入数据后对刚插入这条无效,但对已有符合条件的数据有效,需求是能更新正insert是最好的,但是目前的逻辑就是insert一条null值,用触发器相当于收尾,更新所有已有的null记录,如下所示, SQL> select * from t; ID AGE---------- ---------- 1 SQL> CREATE OR REPLACE TRIGGER TR1 AFTER INSERT ON T FOR EACH ROW DECLARE t_rec NUMBER; PRAGMA AUTONOMOUS_TRANSACTION;BEGIN t_rec := 0; CASE when INSERTING then UPDATE T SET AGE=t_rec WHERE age is null; end case; COMMIT;EXCEPTION when OTHERS THEN ROLLBACK;END;/ Trigger created. SQL> insert into t values(2, '');1 row created.SQL> select * from t; ID AGE---------- ---------- 1 0 2 既然不是收尾,是需要让当前INSERT的记录,判断若是NULL,则更新值为0,是不是需要使用BEFORE INSERT,而不是AFTER INSERT,执行发现报错, SQL> CREATE OR REPLACE TRIGGER TR1 BEFORE INSERT ON T FOR EACH ROWBEGIN value := 0; IF (:NEW.AGE IS NULL) THEN :NEW.AGE:=0; END IF;END;/Warning: Trigger created with compilation errors. SQL> show errorErrors for TRIGGER TR1:LINE/COL ERROR-------- -----------------------------------------------------------------2/3 PL/SQL: Statement ignored2/3 PLS-00321: expression 'VALUE' is inappropriate as the left hand side of an assignment statement 调整一下参数值,定义变量,替换常量0, SQL> CREATE OR REPLACE TRIGGER TR1 BEFORE INSERT ON T FOR EACH ROW DECLARE value NUMBER;BEGIN value := 0; IF (:NEW.AGE IS NULL) THEN :NEW.AGE:=VALUE; END IF;END;/ SQL> insert into t values (1, '');1 row created.SQL> select * from t; ID AGE---------- ---------- 1 0 实现了最初的需求了,总结一下,使用BEFORE INSERT,插入之前,判断NEW.AGE是否为空,若是则用变量value=0赋值,此时执行INSERT,就会用0值,而不是原始NULL,进行操作。 若使用AFTER INSERT,我认为可以实现,但要注意避免,ORA-04091错误,感兴趣的朋友可以试一试,要是有结果,可以贴出来,分享一下。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
上周应用上线,有一个数据库脚本,包含改字段长度等操作,执行过程中,现象就是有些改字段成功了,有些执行出错,报了ORA-00054的错误。了解一下原理,就能对这个错误,有比较深入的理解了。 首先,我们模拟下报错过程,创建测试表,session 1执行update语句,但不提交,session 2执行alter table变更name字段长度,此时立即报错ORA-00054, SQL> create table tbl_lock(id number, name varchar2(10));Table created.SQL> select * from tbl_lock; ID NAME---------- ---------- 1 a 2 b session 1: SQL> update tbl_lock set name='c' where id=1;1 rows updated. session 2: SQL> alter table tbl_lock modify name varchar2(5);alter table tbl_lock modify name varchar2(5) *ERROR at line 1:ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired 我们看下报错,ORA-00054,提示的就是资源繁忙,因为设置了NOWAIT参数,或者超时,才返回这个错误, 看下此时的锁信息,其中14309是从dba_objects中根据object_name='TBL_LOCK'检索得出的,如下显示,TBL_LOCK表上有一个TM表锁, 表锁,又叫TM锁,当交易执行DML语句的时候,会拥有此锁。目的就是为了阻止此时有其他的进程正在执行DDL,修改表结构, A table lock, also called a TM lock, is acquired by a transaction when a table is modified by an INSERT, UPDATE, DELETE, MERGE, SELECT with the FORUPDATE clause, or LOCK TABLE statement. DML operations require table locks to reserve DML access to the table on behalf of a transaction and to prevent DDL operations that would conflict with the transaction. 结论: 至此,开始的问题,就可以解释清楚了,上线过程中,执行alter table改表的字段长度,但由于有些表,此时碰巧有业务操作,对数据做了DML,交易尚未提交,因此由于TM锁未释放,导致alter table这条DDL语句执行报错,对于alter table执行时尚未有DML未commit操作的表,自然就可以执行成功了。 解决方法: 就是等一会再执行,只要出现真空期,没有业务操作,就可以执行成功了,毕竟alter table改字段长度,需要改数据字典信息,对于表结构的变更,何时执行时间,会和表数据量有关,何时则无关,以前写了几篇小文章,不同的场景,有一些不同的结论,可以参考, 一张几亿的分区表,能改名么? alter table新增字段操作究竟有何影响?(上篇) alter table新增字段操作究竟有何影响?(下篇) 针对ORA-00054这问题,可以再了解一些。 从11g开始,出现了一个新的参数, 这个参数可以session级别设置,作用就是可以控制一条DDL语句等待一个DML锁释放的时间,默认值是0,表示NOWAIT,最大值是1000000秒,大约11.5天,如果在设置的时间之内,仍未获取DDL锁,则抛出异常错误,错误号就是ORA-00054, DDL_LOCK_TIMEOUT specifies a time limit for how long DDL statements will wait in a DML lock queue. The default value of zero indicates a status of NOWAIT. The maximum value of 1,000,000 seconds will result in the DDL statement waiting forever to acquire a DML lock. If a lock is not acquired before the timeout period expires, then an error is returned. 上面的实验中,DDL_LOCK_TIMEOUT默认值是0,因此执行alter table会立即报错, 设置参数值,改为10妙,执行alter table,确实SQL等待了10秒,才返回了ORA-00054错误, SQL> alter session set ddl_lock_timeout=10;Session altered.Elapsed: 00:00:00.00 SQL> alter table tbl_lock modify name varchar2(5);alter table tbl_lock modify name varchar2(5) *ERROR at line 1:ORA-00054: resource busy and acquire with NOWAIT specified or timeout expiredElapsed: 00:00:10.00 惜分飞文章(http://www.xifenfei.com/2012/07/oracle-11g%E7%9A%84ddl_lock_timeout%E5%8F%82%E6%95%B0.html)介绍了这个参数的作用, ddl_lock_timeout可以在一定程度上解决因为我们不清楚这个表是否有dml操作而导致ddl操作不能进行的情况,从一定程度上减少了自己去尝试ddl操作,或者查询相关视图然后找出相关会话,然后kill掉对应数据的情况,可以说是在修改表结构的时候一个很不错的新特性。 11g之前,DDL操作,碰见TM锁,是直接报错,11g则用这参数,通过设置等待时间,可以避免一些DDL语句重复执行,例如开始碰见的问题,如果设置了DDL_LOCK_TIMEOUT,可能等待一会就会执行成功,而不需要我们手工再执行。 但这参数有一个问题,就是对于alter table加字段操作,是不起作用,无论ddl_lock_timeout设置为0还是非0, SQL> alter table tbl_lock add sex varchar2(1); 会一直处于hang 直到人为中止 c^Calter table tbl_lock add sex varchar2(1)*ERROR at line 1:ORA-01013: user requested cancel of current operation 但是alter table删除字段、drop table删除表操作,可以生效, SQL> alter table tbl_lock drop column name;alter table tbl_lock drop column name *ERROR at line 1:ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired SQL> drop table tbl_lock;drop table tbl_lock *ERROR at line 1:ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired MOS(Alter Table Add Column Command Hangs With Wait Event 'blocking txn id for DDL' (文档 ID 1553725.1))这篇文章,同样说明了这一个问题,由于11g中,alter table add column操作,没有被DDL排他锁覆盖,因此不受DDL_LOCK_TIMEOUT参数的控制,更不会抛出ORA-00054错误,而是出于hang, In 11g, ALTER TABLE ADD COLUMN is not covered by an exclusive ddl lock; therefore, it will not wait for the specified time in DDL_LOCK_TIMEOUT parameter and it will not raise the ORA-00054 error. 《DDL_LOCK_TIMEOUT Behavior in 11G (文档 ID 779569.1)》介绍了这个参数。 11.1.0.6版本,有人开了《Bug 7707888 : DDL_LOCK_TIMEOUT IS NOT WORKING AS EXPECTED》这个bug,此版本中,若有seesion执行DML未提交,此时alter table add column可以执行,但是drop table可以执行。 总结: 1. DDL_LOCK_TIMEOUT是11g新参数,对于一些频繁DML的表,若需要结构变更,可以设置非0,一定程度上,可以避免人为重新执行,自动找出真空期,执行完成DDL语句。 2. alter table加字段操作,不受DDL_LOCK_TIMEOUT控制,需要人为控制。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
昨天的文章,用shell写了一个简单的MySQL系统运行状态实时监控的模版,《MySQL系统运行状态实时监控(shell版本)》,对于这种操作,任何语言都可以完成,今儿就用python写一下,写的不优雅的地方,请各位指正。 首先,为了让python能连接MySQL数据库,需要一些第三方的库,由于我用的是python 2.3版本,因此可以使用mysqldb,(若是python 3.x,则可以使用PyMySQL),可以从以下链接下载压缩,目前最新版本是1.2.5, https://pypi.python.org/pypi/MySQL-python/1.2.3 如果不确定本机是否安装了,可以使用, python >> import MySQLdb 看下是否报错,若提示了, ImportError: No module named MySQLdb 则表示未安装。 解压MySQL-python-1.2.3.tar.gz, 进入目录,执行以下命令,完成mysqldb的安装, python setup.py install 接下来开始coding,首先定义一个枚举类,方便常量调用,此处为五个状态参数, def enum(**enums): return type('Enum', (), enums) Status=enum(QPS="queries", Commit="com_commit", Rollback="com_rollback", Threads_con="Threads_connected", Threads_run="Threads_running") python连接数据库,确实比java这些语言,要简单些, dbConn=MySQLdb.connect( host='x.x.x.x', port=3306, user='bisal', passwd='xxxxx', db='mysql') cursor=dbConn.cursor() 比如我要检索QPS这个参数,执行以下SQL,由于是肯定只返回一条数据,所以用了fetchone()函数,为了只要返回值,使用str[a:b]进行了字符串截取。 sql='show global status like \'' + Status.QPS + '\'' cursor.execute(sql) result=cursor.fetchone() str= ''.join(result) q=str[len(Status.QPS):len(str)] 接下来就可以格式化打印,同样输出10次,重新打印表头,显示sleep一秒, if (count==0): print "|QPS |Commit |Rollback |TPS |Threads_con |Threads_run |" print "------------------------------------------------------------------------------" if (count>=10): count=0 print "------------------------------------------------------------------------------" print "|QPS |Commit |Rollback |TPS |Threads_con |Threads_run |" print "------------------------------------------------------------------------------" print "|%-10s |%-10s |%-10s |%-10s |%-12s |%-12s|" % (q,c,r,c+r,tc,tr) else: print "|%-10s |%-10s |%-10s |%-10s |%-12s |%-12s|" % (q,c,r,c+r,tc,tr) count+=1 time.sleep(1) 另外,记得需要关闭数据库连接, cursor.close() dbConn.close() 整个流程,其实很简单,就是执行show status语句,进行一些字符串处理,格式化输出,以上完整代码,可以下载: https://github.com/bisal-liu/mysql/blob/master/mysql_per_monitor_1.py 要说可以优化,就是上面这种方法中,对于每一个状态参数,都要执行一次show status,有些浪费,可以一次执行,多次解析,使用IN子句,实现执行一次SQL,返回不同参数, sql='show global status where variable_name in (\'' + Status.QPS + '\',\'' + Status.Commit \ + '\',\'' + Status.Rollback + '\',\'' + Status.Threads_con + '\',\'' + Status.Threads_run + '\')' 由于返回不止一条记录,因此需要使用for,if中根据字符串做匹配,以下写法中, 1. 若使用了''.join(line),则需要使用q=k1[len(Status.QPS):len(k1)]截取字符串。 2. 若使用了str(line),则需要使用q=k2.split('\'')[3]截取字符串。 for line in result: k1=''.join(line).lower() k2=str(line).lower() if (Status.QPS in k1): q=k1[len(Status.QPS):len(k1)] elif (Status.Commit in k1): c=k1[len(Status.Commit):len(k1)] elif (Status.Rollback in k1): r=k1[len(Status.Rollback):len(k1)] elif (Status.Threads_con in k1): tc=k1[len(Status.Threads_con):len(k1)] elif (Status.Threads_run in k1): tr=k1[len(Status.Threads_run):len(k1)] if (Status.QPS in k2): q=k2.split('\'')[3] elif (Status.Commit in k2): c=k2.split('\'')[3] elif (Status.Rollback in k2): r=k2.split('\'')[3] elif (Status.Threads_con in k2): tc=k2.split('\'')[3] elif (Status.Threads_run in k2): tr=k2.split('\'')[3] 以上完整代码,可以下载, https://github.com/bisal-liu/mysql/blob/master/mysql_per_monitor_2.py 以上两种写法,效果一样, 1. 每隔1秒,刷新一次, 2. 每隔10次,重新打印表头, 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
开始接触MySQL,还是和Oracle有些不一样的地方,需要逐步积累和学习,其中有一点不同,就是Oracle有一些数据字典,可以显示系统运行状态,但需要使用SQL来检索,另外AWR会有一些运行状态信息,相比之下,MySQL提供了一些指令,直接执行就可以显示,看起来要更方便一些。 MySQL要显示系统运行状态,可以有两种方法。 方法一:登陆数据库,执行命令show global status,如下所示, 方式二:不用登陆数据库,使用mysqladmin指令,如下所示, mysqladmin -uroot -p'My@sql' extended-status 其中extended-status可以用ext进行缩写。 为了不显示输入密码,可以配置文件中定义, [mysqladmin] host=localhost user=root password='My@sql' 直接用以下指令, mysqladmin extended-status 若没有用默认的配置文件,则需要注意,mysqladmin默认参数,按照如下顺序, /etc/my.cnf /etc/mysql/my.cnf /var/mysql/etc/my.cnf ~/.my.cnf 同时需要使用以下参数, --defaults-extra-file=# Read this file after the global files are read. 指令如下, mysqladmin --defaults-extra-file=/DATA/mysql/my.cnf ext 既然有以上指令,可以帮助我们了解,MySQL系统运行状态,我们自然考虑,是否可以自动化,几乎可以用任何语言,实现上面的指令过程,以下是用shell脚本实现的监控模版, /* 使用awk,截出mysqladmin ext的回显,-i1表示1秒钟,自动刷新一次 */ mysqladmin --defaults-extra-file=/DATA/mysql/my.cnf ext -i1 | awk 'BEGIN{lswitch=0; /* 打印信息表头 */ print "|QPS |Commit |Rollback |TPS |Threads_con |Threads_run |"; print "------------------------------------------------------------------------------";} /* 打印Queries、Com_commit、Com_rollback、Threads_connected、Threads_running这五个参数,前三个参数,是增量数据,因此需要记录上一次的值 */ $2 ~ /Queries$/ {q=$4-lq; lq=$4;} $2 ~ /Com_commit$/ {c=$4-lc; lc=$4;} $2 ~ /Com_rollback$/ {r=$4-lr; lr=$4;} $2 ~ /Threads_connected$/ {tc=$4;} $2 ~ /Threads_running$/ {tr=$4; /* 设置lswitch的原因,为了打印10次出现一次表头 */ if (lswitch==0) {lswitch=1; count=0;} else { /* 打印10次数据,重新显示表头 */ if (count>10) { count=0; print "------------------------------------------------------------------------------"; print "|QPS |Commit |Rollback |TPS |Threads_con |Threads_run |"; print "------------------------------------------------------------------------------"; } else { count+=1; /* 按照格式符进行打印,其中TPS值为Com_commit、Com_rollback的总和 */ printf "|%-10d |%-10d |%-10d |%-10d |%-12d |%-12d|", q,c,r,c+r,tc,tr; } } }' 每隔1秒,刷新一次, 每隔10次,重新打印表头, 以上完整代码,可以从我的GitHub下载, https://github.com/bisal-liu/oracle/blob/master/mysql_per_mon.sh 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
使用MySQL 5.6,搭建主从复制。关于5.6的安装,可以参考《MySQL 5.6 rpm安装方法和碰见的问题》。 主库创建slave用户,设置复制权限, mysql> create user 'slave'@'1.1.1.2' identified by 'root'; Query OK, 0 rows affected (0.00 sec) mysql> grant replication slave on *.* to 'slave'@'1.1.1.2' identified by 'root'; Query OK, 0 rows affected (0.00 sec) 编辑my.cnf配置文件,设置主库server-id=1,定义需要复制的库为test,忽略mysql数据库 [root@vm-kvm10000-app mysql]# vi /etc/my.cnf [mysqld] server-id=1 log-bin=mysql-bin binlog_do_db=test binlog_ignore_db=mysql 重启主库MySQL服务, [root@vm-kvm10000-app mysql]# service mysql restart Shutting down MySQL.. SUCCESS! Starting MySQL. SUCCESS! 看一下主库状态, mysql> show master status; +------------------+----------+--------------+------------------+-------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +------------------+----------+--------------+------------------+-------------------+ | mysql-bin.000002 | 120 | test | mysql | | +------------------+----------+--------------+------------------+-------------------+ 1 row in set (0.00 sec) 或者 mysql> show master status \G *************************** 1. row *************************** File: mysql-bin.000002 Position: 120 Binlog_Do_DB: test Binlog_Ignore_DB: mysql Executed_Gtid_Set: 1 row in set (0.00 sec) 从库,编辑my.cnf配置,设置server-id=2,区别于主库, [root@vm-kvm10001-app mysql]# vi /etc/my.cnf [mysqld] server-id=2 重启MySQL服务, [root@vm-kvm10001-app mysql]# service mysql restart Shutting down MySQL.. SUCCESS! Starting MySQL. SUCCESS! 设置主库信息, mysql> change master to master_host='1.1.1.1',master_user='slave',master_password='root',master_log_file='mysql-bin.000001',master_log_pos=120,master_connect_retry=10; Query OK, 0 rows affected, 2 warnings (0.03 sec) 检索从库状态, mysql> show slave status \G; *************************** 1. row *************************** Slave_IO_State: Master_Host: 10.221.3.129 Master_User: slave Master_Port: 3306 Connect_Retry: 10 Master_Log_File: mysql-bin.000001 Read_Master_Log_Pos: 120 Relay_Log_File: vm-kvm11853-app-relay-bin.000001 Relay_Log_Pos: 4 Relay_Master_Log_File: mysql-bin.000001 Slave_IO_Running: No Slave_SQL_Running: No Replicate_Do_DB: Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: Replicate_Wild_Ignore_Table: Last_Errno: 0 Last_Error: Skip_Counter: 0 Exec_Master_Log_Pos: 120 Relay_Log_Space: 120 Until_Condition: None Until_Log_File: Until_Log_Pos: 0 Master_SSL_Allowed: No Master_SSL_CA_File: Master_SSL_CA_Path: Master_SSL_Cert: Master_SSL_Cipher: Master_SSL_Key: Seconds_Behind_Master: NULL Master_SSL_Verify_Server_Cert: No Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 0 Last_SQL_Error: Replicate_Ignore_Server_Ids: Master_Server_Id: 0 Master_UUID: Master_Info_File: /var/lib/mysql/master.info SQL_Delay: 0 SQL_Remaining_Delay: NULL Slave_SQL_Running_State: Master_Retry_Count: 86400 Master_Bind: Last_IO_Error_Timestamp: Last_SQL_Error_Timestamp: Master_SSL_Crl: Master_SSL_Crlpath: Retrieved_Gtid_Set: Executed_Gtid_Set: Auto_Position: 0 1 row in set (0.00 sec) 其中最重要的就是,这两个参数,要求YES,但此处为NO, Slave_IO_Running: No Slave_SQL_Running: No 检索错误日志,提示无法找见./performance_schema/cond_instances.frm文件, [root@vm-kvm10001-app mysql]# vm-kvm10001-app.err 2017-08-29 16:10:37 22933 [ERROR] /usr/sbin/mysqld: Can't find file: './performance_schema/cond_instances.frm' (errno: 13 - Permission denied) 看一下其目录,发现performance_schema文件夹,是root权限,mysql用户无法访问,因此需要修改一下其权限, [root@vm-kvm10001-app mysql]# ls -rlht total 109M ...drwx------ 2 root root 4.0K Aug 29 15:05 performance_schema ... [root@vm-kvm10001-app mysql]# chown -R mysql:mysql * [root@vm-kvm10001-app mysql]# ls -rlht total 109M ...drwx------ 2 mysql mysql 4.0K Aug 29 15:05 performance_schema ... 重启服务, [root@vm-kvm10001-app mysql]# service mysql restart Shutting down MySQL.... SUCCESS! Starting MySQL. SUCCESS! 再看从库状态, mysql> show slave status \G *************************** 1. row *************************** ... Slave_IO_Running: Yes Slave_SQL_Running: Yes ... 主库导出数据,用于导入从库,首先需要设置读锁,避免数据不一致, mysql> flush tables with read lock; mysql> show master logs; +------------------+-----------+ | Log_name | File_size | +------------------+-----------+ | mysql-bin.000001 | 143 | | mysql-bin.000002 | 222 | | mysql-bin.000003 | 545 | +------------------+-----------+ 3 rows in set (0.00 sec) 执行mysqldump将test库,导出test.sql文件, [root@vm-kvm10000-app mysql]# mysqldump -uroot -p -B test > test.sql 然后解锁表, mysql> unlock tables; 从库执行导入, [root@vm-kvm10001-app mysql]# mysql -uroot -p < test.sql 要确保从库,这两个值正确, Slave_IO_Running: Yes Slave_SQL_Running: Yes 此时就完成了主从复制,向主库插入一条记录, mysql> INSERT INTO test -> (id, name) -> VALUES -> (1, "a");Query OK, 1 rows affected (0.00 sec) 从库中可以检索出来, mysql> select * from test; +-------+--------+ | id | name | +-------+--------+ | 1 | a | +-------+--------+ 总结:1. MySQL相关文件、文件夹需要的权限,可能会因为不正确,例如需要mysql权限,但却是root,导致数据库异常。 2. 主从复制,需要关注从库,这两个参数值,需要均为YES,出现NO,则可以检索错误日志,进一步定位。 Slave_IO_Running: Yes Slave_SQL_Running: Yes 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
前天写了篇文章《表中已存重复数据的情况,如何增加唯一性约束?》,提到了存在唯一约束前提下,重复数据的问题。 很感谢建荣兄,他给我补充了两点, 1. 冲突数据也可以考虑通过errorlog的方式,可以很快定位。 2. 对于含有null的复合索引,mysql和oracle的结果完全不同。 对于第二点,前同事曌哥,碰巧也说到了,MySQL下和Oracle的些许不同,这块我需要验证下,才能体会得更清楚些。 对于第一点,之前没用过,借着这次机会,学习一下。 我们先直接看使用过程。首先测试表有两条数据,并且创建(a, b, c)的唯一约束, SQL> select * from test; ID A B C---------- ---------- ---------- ---------- 1 a a a 2 b b b SQL> alter table test add constraint unq_test_01 unique(a, b, c);Table altered. 接着,使用DBMS_ERRLOG包的create_error_log存储过程,指定需要创建ERROR LOG的表, SQL> exec dbms_errlog.create_error_log(dml_table_name=>'TEST');PL/SQL procedure successfully completed. 此时会新增一张名为ERR$_TEST的表,可以看出,除了TEST表原有的ID、A、B和C字段外,还有另外五个ORA_ERR_开始的字段, 此时我们向TEST表插入一条重复的数据,自然会报错,违反唯一性约束的错误, SQL> insert into test values(3, 'a', 'a', 'a');insert into test values(3, 'a', 'a', 'a')*ERROR at line 1:ORA-00001: unique constraint (BISAL.UNQ_TEST_01) violated 我们增加log errors子句,再次执行, SQL> insert into test values(3, 'a', 'a', 'a') log errors into err$_test ('manual_load') reject limit 100;0 rows created. 此时未报错,TEST表没有新增数据, SQL> select * from test; ID A B C---------- ---------- ---------- ---------- 1 a a a 2 b b b ERR$_TEST表则增加了一条数据,从ID、A、B和C可以看出,就是刚才要插入的重复数据,换句话说,这条不可能插入TEST表的数据,插入了ERR$_TEST表,另外ORA_ERR_MESG$字段显示的错误信息,正是不加log errors子句时,控制台直接返回的错误信息,我们猜出ORA_ERR_OPTYP$字段是I表示的是INSERT,插入操作, 从上面的过程,可以了解ERROR LOG的基本用途,即可以存储一些操作原表数据错误的记录,一方面不会让原表操作报错,另一方面会自动记录这些错误,便于检索。 接下来我们看看,Oracle官方的介绍,从《Oracle® Database PL/SQL Packages and Types Reference》文档可以检索DBMS_ERRLOG包。 DBMS_ERRLOG包可以创建一张错误日志表,当执行一些DML操作碰见错误的时候,可以让这些操作继续执行,而不是自动终止和回滚,这样可以节省执行时间,以及系统资源, The DBMS_ERRLOG package provides a procedure that enables you to create an error logging table so that DML operations can continue after encountering errors rather than abort and roll back. This enables you to save time and system resources. 可以使用DBMS_ERRLOG包前提是,用户有EXECUTE包的权限,并且需要拥有SELECT基表或视图的权限,以及CREATE TABLE全县,当然表空间要有QUOTA配额, Security on this package can be controlled by granting EXECUTE on this package to selected users or roles. The EXECUTE privilege is granted publicly. However, to create an error logging table, you need SELECT access on the base table or view, the CREATE TABLE privilege, as well as tablespace quota for the target tablespace. DBMS_ERRLOG包中只有一个存储过程CREATE_ERROR_LOG,作用就是,创建记录发生DML错误的日志表。错误日志支持INSERT, UPDATE, MERGE, and DELETE这些操作。 不支持以下数据类型,即对以下类型字段执行DML,不会记录日志表, LONG, CLOB, BLOB, BFILE, and ADT 这是CREATE_ERROR_LOG的语法, 这是参数说明, dml_table_name The name of the DML table to base the error logging table on. The name can be fully qualified (for example, emp, scott.emp, "EMP", "SCOTT"."EMP"). If a name component is enclosed in double quotes, it will not be upper cased. err_log_table_name The name of the error logging table you will create. The default is the first 25 characters in the name of the DML table prefixed with 'ERR$_'. Examples are the following: dml_table_name: 'EMP', err_log_table_name: 'ERR$_EMP' dml_table_name: '"Emp2"', err_log_table_name: 'ERR$_Emp2' err_log_table_owner The name of the owner of the error logging table. You can specify the owner in dml_table_name. Otherwise, the schema of the current connected user is used. err_log_table_space The tablespace the error logging table will be created in. If not specified, the default tablespace for the user owning the DML error logging table will be used. skip_unsupported When set to TRUE, column types that are not supported by error logging will be skipped over and not added to the error logging table. When set to FALSE, an unsupported column type will cause the procedure to terminate. The default is FALSE. dml_table_name表示需要监控的表,如上面实验TEST表。 err_log_table_name表示需要新建的日志表,默认采用监控表的前25个字符,加上ERR$前缀,如上面实验ERR$_TEST表。 err_log_table_owner表示监控表dml_table_name拥有者,默认当前用户。 err_log_table_space表示日志表所在表空间,默认为和dml_table_name存储于相同的表空间。 skip_unsupported表示若碰见,上述不支持的数据类型,选择中止执行,还是仅略过这些错误,默认FALSE。 从上述实验,我们可以看出,日志表会有五个ORA_ERR_开始的字段, Column Name Data Type Description ORA_ERR_NUMBER$ NUMBER Oracle error number ORA_ERR_MESG$ VARCHAR2(2000) Oracle error message text ORA_ERR_ROWID$ ROWID Rowid of the row in error (for update and delete) ORA_ERR_OPTYP$ VARCHAR2(2) Type of operation: insert (I), update (U), delete (D) Note: Errors from the update clause and insert clause of a MERGE operation are distinguished by the U and I values. ORA_ERR_TAG$ VARCHAR2(2000) Value of the tag supplied by the user in the error logging clause 当然可以不用CREATE_ERROR_LOG自动建日志表,手工根据上述字段要求,同样可以创建日志表,使用log errors子句操作,需要注意的是,以上这些字段,可以不要求顺序,但必须是这张表的前几个字段,否则就会报错。 我们看下会报什么错误,首先需要获取ERR$_TEST创建语句, SQL> set long 1000 SQL> select dbms_metadata.get_ddl('TABLE','ERR$_TEST') FROM dual;DBMS_METADATA.GET_DDL('TABLE','ERR$_TEST')-------------------------------------------------------------------------------- CREATE TABLE "BISAL"."ERR$_TEST" ( "ORA_ERR_NUMBER$" NUMBER, "ORA_ERR_MESG$" VARCHAR2(2000), "ORA_ERR_ROWID$" UROWID (4000), "ORA_ERR_OPTYP$" VARCHAR2(2), "ORA_ERR_TAG$" VARCHAR2(2000), "ID" VARCHAR2(4000), "A" VARCHAR2(4000), "B" VARCHAR2(4000), "C" VARCHAR2(4000) ) SEGMENT CREATION IMMEDIATE PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645 PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT) TABLESPACE "USERS" 注意此处指定了SEGMENT CREATION IMMEDIATE,即禁用了11g默认的新特性,延迟段创建,建表同时,分配段空间。另外,NUMBER类型的ID列,此处变为了VARCHAR2类型,其他VARCHAR2类型的字段,长度变为了4000。 手工创建ERR$_TEST表,ORA_ERR_开始的字段,放置末尾, CREATE TABLE ERR$_TEST ( ID VARCHAR2(4000), A VARCHAR2(4000), B VARCHAR2(4000), C VARCHAR2(4000), ORA_ERR_NUMBER$ NUMBER, ORA_ERR_MESG$ VARCHAR2(2000), ORA_ERR_ROWID$ UROWID (4000), ORA_ERR_OPTYP$ VARCHAR2(2), ORA_ERR_TAG$ VARCHAR2(2000)); Table created. 执行重复数据的插入,报错ORA_ERR_MESG$字段必须是前五个字段, SQL> insert into test values(3, 'a', 'a', 'a') log errors into err$_test ('manual_load') reject limit 100;insert into test values(3, 'a', 'a', 'a') log errors into err$_test ('manual_load') reject limit 100 *ERROR at line 1:ORA-38901: column "ORA_ERR_MESG$" of table "ERR$_TEST" must be one of the first "5" columns 我们根据提示,将ORA_ERR_MESG$放置前五, CREATE TABLE ERR$_TEST ( ORA_ERR_MESG$ VARCHAR2(2000), ID VARCHAR2(4000), A VARCHAR2(4000), B VARCHAR2(4000), C VARCHAR2(4000), ORA_ERR_NUMBER$ NUMBER, ORA_ERR_ROWID$ UROWID (4000), ORA_ERR_OPTYP$ VARCHAR2(2), ORA_ERR_TAG$ VARCHAR2(2000)); Table created. 执行报错,说明确实这五个字段,需要放置前五, SQL> insert into test values(3, 'a', 'a', 'a') log errors into err$_test ('manual_load') reject limit 100;insert into test values(3, 'a', 'a', 'a') log errors into err$_test ('manual_load') reject limit 100 *ERROR at line 1:ORA-38901: column "ORA_ERR_NUMBER$" of table "ERR$_TEST" must be one of the first "5" columns 再看一下log errors子句,“manual_load”是一个标签,要求数字或字符类型,辅助定位。另一个可选参数,是reject limit,定义了INSERT操作报错前,日志表记录最大值,可以设置为UNLIMITED,默认只是0,意思是碰见第一个错误,就记录日志表,并且回滚语句。 我们执行log errors子句,此时出现错误,即使执行rollback,TEST表和ERR$_TEST表数据不会回滚,有可能 SQL> insert into test values(3, 'a', 'a', 'a') log errors into err$_test ('manual_load') reject limit 100;0 rows created.SQL> rollback;Rollback complete.SQL> select count(*) from test; COUNT(*)---------- 2SQL> select count(*) from err$_test; COUNT(*)---------- 1 错误日志可以记录如下,DML操作错误, Column values that are too large Constraint violations (NOT NULL, unique, referential, and check constraints) Errors raised during trigger execution Errors resulting from type conversion between a column in a subquery and the corresponding column of the table Partition mapping errors Certain MERGE operation errors (ORA-30926: Unable to get a stable set of rows for MERGE operation.) 以下操作错误,不会记录日志, Violated deferred constraints. Any direct-path INSERT or MERGE operation that raises a unique constraint or index violation. Any update operation UPDATE or MERGE that raises a unique constraint or index violation. 总结: 1. 错误日志表,可以记录DML一些操作错误,当然有一些限制。 2. 错误日志表,可以使用DBMS_ERRLOG包自动创建,也可以手工创建,但要求五个ORA_ERR_字段必须位于表定义前列,和原表相比,NUMBER类型变为VARCHAR2(4000),所有VARCHAR2类型均变为4000长度定义。 3. 错误日志表,有些数据类型不支持,可以使用标签,以及reject limit设置一些错误记录的属性。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
这周某系统上线,有一个需求就是,为一张表修改唯一性约束,原因就是之前发现,由于唯一性约束设置不当,导致业务处理出现异常。 举例来说,如下测试表,原先唯一性约束是a和b俩字段,但发现实际业务中,a和b的组合是可能重复的,加上c字段才会是唯一, SQL> create table test( 2 id number, 3 a varchar2(10), 4 b varchar2(10), 5 c varchar2(10));Table created.SQL> insert into test values(1, 'a', 'a', 'a');1 row created.SQL> insert into test values(2, 'b', 'b', 'b');1 row created.SQL> commit;Commit complete. SQL> select * from test; ID A B C---------- ---------- ---------- ---------- 1 a a a 2 b b b 基于以上数据,新建唯一性约束,可以看出,对于唯一性约束,Oracle会自动创建一个,普通的唯一索引,索引名称默认采用约束名。 SQL> alter table test add constraint unq_test_01 unique(a, b, c);Table altered. SQL> select table_name, constraint_name, constraint_type from user_constraints where table_name='TEST';TABLE_NAME CONSTRAINT_NAME CONSTRAINT_TYPE---------- ------------------------------ ------------------------------TEST UNQ_TEST_01 U SQL> select table_name, index_name, index_type, uniqueness from user_indexes where table_name = 'TEST';TABLE_NAME INDEX_NAME INDEX_TYPE UNIQUENESS---------- ------------------------------ -------------------- ----------TEST UNQ_TEST_01 NORMAL UNIQUE Because the database enforces a unique constraint by implicitly creating or reusing an index on the key columns, the term unique key is sometimes incorrectly used as a synonym for unique key constraint or unique index. 确实插入(a, b, c)相同的数据,就会报唯一性约束错误, SQL> insert into test values(3, 'a', 'a', 'a');insert into test values(3, 'a', 'a', 'a')*ERROR at line 1:ORA-00001: unique constraint (BISAL.UNQ_TEST_01) violated 这就完了么? 需要注意一点,上述创建过程的前提,是表中已存在数据,没有违反唯一性约束的,如果表中已存在数据,已经有重复数据,该如何处理? 我们删除刚才创建的约束,插入重复记录,此时表中存在(a, b, c)相同的记录, SQL> alter table test drop constraint unq_test_01;Table altered. SQL> insert into test values(3, 'a', 'a', 'a');1 row created.SQL> commit;Commit complete.SQL> select * from test; ID A B C---------- ---------- ---------- ---------- 1 a a a 2 b b b 3 a a a 我们再用上面的方法,创建唯一性约束,可以看出,报了错误,提示信息很明确,由于存在重复的键值,因此无法生效唯一性约束, SQL> alter table test add constraint unq_test_01 unique(a, b, c);alter table test add constraint unq_test_01 unique(a, b, c) *ERROR at line 1:ORA-02299: cannot validate (BISAL.UNQ_TEST_01) - duplicate keys found 是否可以直接创建一个,唯一性索引? SQL> create unique index idx_test_01 on test(a, b, c);create unique index idx_test_01 on test(a, b, c) *ERROR at line 1:ORA-01452: cannot CREATE UNIQUE INDEX; duplicate keys found 这种情况下,最简单的方法,就是删除重复的记录,这样就可以按照正常流程,创建唯一性约束。但往往这些重复数据,有实际的业务意义,因此不能删除,所以就需要其他方法workaround一下。 其实,Oracle官方手册,就给了我们选择,我们是可以控制,约束生效的状态, As part of constraint definition, you can specify how and when Oracle Database should enforce the constraint, thereby determining the constraint state. The database enables you to specify whether a constraint applies to existing data or future data. If a constraint is enabled, then the database checks new data as it is entered or updated. Data that does not conform to the constraint cannot enter the database. If a constraint is disabled, then the table can contain rows that violate the constraint. You can set constraints to validate (VALIDATE) or not validate (NOVALIDATE) existing data. If VALIDATE is specified, then existing data must conform to the constraint. You can set constraints to validate (VALIDATE) or not validate (NOVALIDATE) existing data. If VALIDATE is specified, then existing data must conform to the constraint. The behavior of VALIDATE and NOVALIDATE always depends on whether the constraint is enabled or disabled. 简言之, 如果约束设置enabled,则会检查新插入或更新的数据是否符合约束条件。 如果约束设置disabled,则表中可以包含,违反约束的记录。 如果约束设置validate,则表中存在的数据,必须符合约束。 如果约束设置novalidate,则表中存在的数据,不必符合约束。 validate和novalidate的行为,依赖于是否设置了enabled/disabled。 相应地可以设置组合,如下所示, 针对上面的需求,我们采用enable和novalidate的组合,是不是就可以解决问题了? 我们直接创建唯一性约束,报的相同错误,原因就是虽然此时,不检查存在数据,是否符合约束,但由于需要自动创建,唯一性索引,却发现存在重复的值,因此报错。 SQL> alter table test add constraint unq_test_01 unique(a, b, c) enable novalidate;alter table test add constraint unq_test_01 unique(a, b, c) enable novalidate *ERROR at line 1:ORA-02299: cannot validate (BISAL.UNQ_TEST_01) - duplicate keys found 既然表中存在重复数据,就不能创建唯一性索引,只能是普通索引,但使用enable novalidate组合,可以设置约束,换句话说,利用唯一性约束,限制数据唯一性,同时有相应的非唯一索引,达到相同效果, SQL> create index idx_test_01 on test(a, b, c);Index created.SQL> alter table test add constraint unq_test_01 unique(a, b, c) enable novalidate;Table altered. SQL> select table_name, constraint_name, constraint_type from user_constraints where table_name='TEST';TABLE_NAME CONSTRAINT_NAME CONSTRAINT_TYPE---------- ------------------------------ ------------------------------TEST UNQ_TEST_01 U SQL> select table_name, index_name, index_type, uniqueness from user_indexes where table_name = 'TEST'; TABLE_NAME INDEX_NAME INDEX_TYPE UNIQUENESS---------- ------------------------- ------------------------------ ----------TEST IDX_TEST_01 NORMAL NONUNIQU 可以看出,虽然表中存在重复数据,但新增数据,需要符合唯一性约束条件,符合我们的最初需求, SQL> select * from test; ID A B C---------- ---------- ---------- ---------- 1 a a a 2 b b b 3 a a aSQL> insert into test values(4, 'b', 'b', 'b');insert into test values(4, 'b', 'b', 'b')*ERROR at line 1:ORA-00001: unique constraint (BISAL.UNQ_TEST_01) violated 再进一步,提一个问题, 存在唯一性约束的情况下,是否可以插入相同的空值? 看着好像简单的一个问题,是不是有些犹豫?我们测试一下,就可以知道了。 测试表现在有(a, b, c)唯一性约束,此时插入两条记录,且三个字段均为空值,分别用null和''两种方法,插入空值数据,是可以插入的,并未违反唯一性约束, SQL> insert into test values(4, null, null, null);1 row created.SQL> insert into test values(5, '', '', '');1 row created.SQL> select * from test; ID A B C---------- ---------- ---------- ---------- 1 a a a 2 b b b 3 a a a 4 5 其实很容易理解,空值是一种特殊的值,表示不确定、未知,因此空值和空值比较,结果不会是true,唯一性约束,不认为两个空值相等,所以可以插入两个空值。 如果三字段中有一个、两个空值,可以看出,会报错误, SQL> insert into test values(4, 'a', '', 'a');1 row created. SQL> insert into test values(5, 'a', '', 'a');insert into test values(5, 'a', '', 'a')*ERROR at line 1:ORA-00001: unique constraint (BISAL.UNQ_TEST_01) violated SQL> insert into test values(4, '', '', 'a');1 row created.SQL> insert into test values(5, '', '', 'a');insert into test values(5, '', '', 'a')*ERROR at line 1:ORA-00001: unique constraint (BISAL.UNQ_TEST_01) violated 如果我们仔细看官方文档,就可以找出答案, Unless a NOT NULL constraint is also defined, a null always satisfies a unique key constraint. Thus, columns with both unique key constraints and NOT NULLconstraints are typical. This combination forces the user to enter values in the unique key and eliminates the possibility that new row data conflicts with existing row data. 除非指定了非空约束,否则null值满足唯一性约束。(准确地说,唯一性约束字段均为null) Because of the search mechanism for unique key constraints on multiple columns, you cannot have identical values in the non-null columns of a partially null composite unique key constraint. 含有部分空值的复合唯一性约束的非空列上不能有相同的值。 总结: 1. 表中不存在重复的数据,可以直接创建唯一性约束,Oracle会自动创建唯一性索引,索引名称默认为约束名。 2. 表中已存在重复的数据,此时若需要创建唯一性约束,可以按照“创建非唯一索引”-“创建唯一性约束”的顺序来实现。 3. 表中有唯一性约束的限制,若所有字段均为null,则可以插入相同的空值,不违反唯一性约束,若复合唯一性约束,包含部分空值,且非空列上有相同的值,则违反唯一性约束。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
前几天尝试装了MySQL 5.7,《MySQL的rpm和源码两种安装操作》,用了rpm和源码编译两种方法,由于项目需要,这次使用MySQL 5.6版本,rpm安装方法,记录了一些安装过程的问题。 1. 卸载机器上自带的MySQL包 检索已安装组件, rpm -qa | grep mysql mysql-libs-5.1.61-4.el6.x86_64 卸载已安装组件,使用--nodeps则强制忽略依赖, rpm -e mysql-libs-5.1.61-4.el6.x86_64 --nodeps 2. 安装MySQL 5.7 rpm包 注意,因为包之间有彼此依赖,所以安装有顺序要求, rpm -ivh MySQL-server-5.6.31-1.linux_glibc2.5.x86_64.rpm rpm -ivh MySQL-devel-5.6.31-1.linux_glibc2.5.x86_64.rpm rpm -ivh MySQL-client-5.6.31-1.linux_glibc2.5.x86_64.rpm 3. 初始化数据库,启动和配置 安装完成,需要初始化数据库, mysql_install_db 复制配置文件, cp /usr/share/mysql/my-default.cnf /etc/my.cnf 启动MySQL服务, service mysql start 设置开机启动, chkconfig mysql on 检索设置生效, chkconfig --list | grep mysqlmysql 0:off 1:off 2:on 3:on 4:on 5:on 6:off 4. 重置密码 上面初始化操作,记录了root账户的临时密码,存储于文件/root/.mysql_secret中, A RANDOM PASSWORD HAS BEEN SET FOR THE MySQL root USER ! You will find that password in '/root/.mysql_secret'. You must change that password on your first connect, no other statement but 'SET PASSWORD' will be accepted. See the manual for the semantics of the 'password expired' flag. /root/.mysql_secret文件, # The random password set for the root user at Mon Aug 28 09:40:09 2017 (local time): pcqEmPanlf3emxWW 登陆数据库,直接执行set password="新密码",会报错误,要求使用41位的十六进制数字才可以, mysql> set password="My@sql"; ERROR 1372 (HY000): Password hash should be a 41-digit hexadecimal number 一种方法是可以从另一个库,执行select password("新密码")语句,得到对应的十六进制数字, mysql> select password('My@sql'); +-------------------------------------------+ | password('My@sql') | +-------------------------------------------+ | *02ED29277F2482ED4AAE65574FC9940A4895D6D7 | +-------------------------------------------+ 1 row in set, 1 warning (0.01 sec) 再用此十六进制设置, mysql> set password='*02ED29277F2482ED4AAE65574FC9940A4895D6D7'; Query OK, 0 rows affected (0.00 sec) 可以执行flush privileges mysql> flush privileges; Query OK, 0 rows affected (0.00 sec) 这是官方文档,对于flush privileges解释, 引用一段网络描述语, flush privileges命令本质上的作用是将当前user和privilige表中的用户信息/权限设置从mysql库(MySQL数据库的内置库)中提取到内存里。MySQL用户数据和权限有修改后,希望在"不重启MySQL服务"的情况下直接生效,那么就需要执行这个命令。通常是在修改ROOT帐号的设置后,怕重启后无法再登录进来,那么直接flush之后就可以看权限设置是否生效。而不必冒太大风险。 另一种设置密码的方法,就是使用PASSWORD函数 mysql> set password=PASSWORD('My@sql'); Query OK, 0 rows affected (0.00 sec) 如下是针对PASSWORD函数的介绍, 可以看一下,设置的用户密码信息, mysql> use mysql Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql> select host, user, password from user; +-----------------+------+-------------------------------------------+ | host | user | password | +-----------------+------+-------------------------------------------+ | localhost | root | *02ED29277F2482ED4AAE65574FC9940A4895D6D7 | | vm-100-app | root | *DB2464B7F3447DFCC3CACE1A0FD19BFC6101339C | | 127.0.0.1 | root | *DB2464B7F3447DFCC3CACE1A0FD19BFC6101339C | | ::1 | root | *DB2464B7F3447DFCC3CACE1A0FD19BFC6101339C | +-----------------+------+-------------------------------------------+ 4 rows in set (0.00 sec) 5. 允许远程登录 默认情况下,用户只能使用本地登陆,可以设置允许远程登录, mysql> update user set host='%' where user='root' and host='hostloacl'; Query OK, 0 rows affected (0.00 sec) Rows matched: 0 Changed: 0 Warnings: 0 mysql> flush privileges; Query OK, 0 rows affected (0.00 sec) 6. 安装默认路径 数据库目录:/var/lib/mysql/ 配置文件目录:/usr/share/mysql 相关命令目录:/usr/bin 启动脚本:/etc/init.d/mysql 碰见的几个问题, (1) 登陆提示错误 bash-4.1# mysql -uroot -p Enter password: ERROR 1045 (28000): Unknown error 1045 发现是用户密码错误,提示了上述报错信息。 (2) my.cnf配置文件如下, [mysqld]port=3306character_set_server=utf8character_set_client=utf8collation-server=utf8_general_cilower_case_table_names=1max_connections=1000default-character-set=utf8 [client]password=My@sqlport=3306default-character-set=utf8 重启服务时,报错, service mysql restartShutting down MySQL.. SUCCESS! Starting MySQL... ERROR! The server quit without updating PID file (/var/lib/mysql/vm-kvm11852-app.pid). 检索vm-kvm11852-app.err文件, ... 2017-08-29 16:02:01 23566 [ERROR] /usr/sbin/mysqld: unknown variable 'default-character-set=utf8' 2017-08-29 16:02:01 23566 [ERROR] Aborting ... 提示无法识别参数值default-character-set=utf8,再次看一下配置文件,确实包含了default-character-set=utf8,将其注释, [mysqld] port=3306 character_set_server=utf8 character_set_client=utf8 collation-server=utf8_general_ci lower_case_table_names=1 max_connections=1000 #default-character-set=utf8 重启服务正常, [root@vm-kvm11852-app mysql]# service mysql restart ERROR! MySQL server PID file could not be found! Starting MySQL. SUCCESS! 针对default-character-set=utf8,不同版本,是有所不同的, 原来在5.1版本时,为了解决中文乱码问题设置默认字符集为utf8时,在my.ini内的[mysql]和[mysqld]项中都是写:default-character-set=utf8 到了5.5版本, [mysql]项内可以这么写, [mysqld]项内不能再这么写了,而是必须写:character_set_server=utf8,否则在启动MySQL服务时会有1067错误 (3) 可以使用verbose参数,检索mysqld命令帮助, mysqld --defaults-file=/opt/mysql/my.cnf --user=mysql --verbose --help ... Usage: mysqld [OPTIONS] ... --print-defaults Print the program argument list and exit. --no-defaults Don't read default options from any option file, except for login file. --defaults-file=# Only read default options from the given file #. ... 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
下周要为新员工介绍Oracle数据库,为了让课程更接地气,准备了虚拟机环境,用于实验和练习,在此过程中出现了两个ORA-600的错误,偶然中又有必然,记录于此。 操作过程: 1. 我在MAC上创建完成虚拟机环境,未关闭虚拟机操作系统。 2. 用移动硬盘,拷贝了次环境。 3. DELL笔记本中打开VMWare,引用移动硬盘中的环境。首先提示了这个选项,我选择的是“我已移动该虚拟机”, 两者区别: “我已移动该虚拟机”:网卡MAC地址会保持不变,但若复制本地,同时开机在一个vmnet可能造成冲突。 ”我已复制该虚拟机“:网卡MAC地址会变化,即虚拟机网卡物理地址,是新生成。 4. 打开虚拟机后,需要启动Oracle数据库, SQL> startupORACLE instance started....Database mounted.ORA-00600: internal error code, arguments: [kcratr_scan_lastbwr], [], [], [],[], [], [], [], [], [], [], [] 执行startup命令的时候,提示了ORA-00600错误,参数名称是kcratr_scan_lastbwr。 放到以前,此时第一个念头,必定是重新拷贝一份,是不是就能解决了。的确,有可能就解决了,但这就是“知其然,不知其所以然”,不是一名严谨的技术人员,解决问题的方法,于是乎需要探究一下。 ORA-00600是Oracle中非常著名的一个错误号,同时可能是一个会让你非常头疼的一个错误号,类似于Java语言中抛出的异常, The ORA-600 error is the generic internal error number for Oracle program exceptions. It indicates that a process has encountered a low-level, unexpected condition. 他的通用提示信息如下, ORA 600 "internal error code, arguments: [%s], [%s],[%s], [%s], [%s]" 其中,第一个参数是一个内部信息数字或字符串类型。这个参数,以及数据库版本号,非常重要,用这些信息,可以明确问题根本原因,以及对系统的潜在影响。其他参数,则用来提供进一步的信息(例如内部变量的值等)。非常详细的堆栈信息,则会记录于ORA-600的trace文件中。在Java语言中,抛出的异常,通常也会有一些提示信息、堆栈信息。 这篇文章《ORA-600/ORA-7445/ORA-700 Error Look-up Tool (文档 ID 153788.1)》提供了针对这几种Oracle错误的快捷查找工具, 回到上面描述的问题,查看alert日志,记录的错误信息如下所示, ALTER DATABASE OPEN Beginning crash recovery of 1 threads Started redo scan Hex dump of (file 3, block 144) in trace file /u01/app/oracle/diag/rdbms/bisal/BISAL/trace/BISAL_ora_3392.trc Reading datafile '/u01/app/oracle/oradata/BISAL/undotbs01.dbf' for corruption at rdba: 0x00c00090 (file 3, block 144) Reread (file 3, block 144) found same corrupt data (logically corrupt) Write verification failed for File 3 Block 144 (rdba 0xc00090) Errors in file /u01/app/oracle/diag/rdbms/bisal/BISAL/trace/BISAL_ora_3392.trc (incident=2554): ORA-00600: internal error code, arguments: [kcratr_scan_lastbwr], [], [], [], [], [], [], [], [], [], [], [] Incident details in: /u01/app/oracle/diag/rdbms/bisal/BISAL/incident/incdir_2554/BISAL_ora_3392_i2554.trc Use ADRCI or Support Workbench to package the incident. See Note 411.1 at My Oracle Support for error and packaging details. Aborting crash recovery due to error 600 Errors in file /u01/app/oracle/diag/rdbms/bisal/BISAL/trace/BISAL_ora_3392.trc: ORA-00600: internal error code, arguments: [kcratr_scan_lastbwr], [], [], [], [], [], [], [], [], [], [], [] Errors in file /u01/app/oracle/diag/rdbms/bisal/BISAL/trace/BISAL_ora_3392.trc: ORA-00600: internal error code, arguments: [kcratr_scan_lastbwr], [], [], [], [], [], [], [], [], [], [], [] ORA-600 signalled during: ALTER DATABASE OPEN... Dumping diagnostic data in directory=[cdmp_20170901062027], requested by (instance=1, osid=3392), summary=[incident=2554]. Fri Sep 01 06:21:24 2017 Sweep [inc][2554]: completed Sweep [inc2][2554]: completed 提示open数据库阶段,读取undotbs01.dbf回滚表空间数据文件,3号文件144号块出现了逻辑坏块。 MOS这篇文章《Bug 9584943 - Crash / recovery failure due to lost write even if mirror has a good image (文档 ID 9584943.8)》介绍了这种ORA-00600错误, 指出即使镜像拷贝正常,但由于缺失一部分写入,导致实例恢复失败。读取错误的文件头,能毁坏一个正常的镜像拷贝。这种情况下,可能会抛出这两种错误,ORA-600 [kcratr_scan_lostwrt]或者ORA-600 [kcratr_scan_lastbwr]。 回想一下,从MAC中拷贝出的虚拟机,是未关闭状态,但从DELL打开移动硬盘的镜像,则是关闭状态,需要重新开机,换句话说,这就是异常断电的场景,此时log buffer中的redo信息未必来得及触发写出条件,即持久化至在线重做日志,当重新开启数据库的时候,由于不是正常关闭数据库,因此需要执行实例恢复,我们知道,实例恢复包括两个阶段,一是利用日志文件中的redo信息,进行交易的前滚操作,恢复到异常断电时刻的状态,此时数据库包含已提交和未提交两种类型的改变向量,二是利用UNDO表空间中的数据进行交易回滚操作,阶段一中除了恢复出了已提交的交易,还恢复出了未提交的事务,此时就会执行rollback操作,回滚所有未提交的事务,此刻数据库中仅包含已提交的事务,状态一致,才能继续open数据库。 上述kcratr_scan_lastbwr则是因为执行实例恢复,第二个阶段,即读取回滚数据的时候,发现了逻辑坏块,因此无法执行,导致open失败。 《ORA-600 [kcratr_scan_lastbwr] (文档 ID 1267231.1)》这篇文章则介绍了这种ORA-00600错误的解决方案,即需要做数据库的逻辑恢复, 数据库启动到mount状态,执行recover database操作,然后open数据库。 按此操作执行, SQL> startup mount; ORACLE instance started. ... SQL> recover database; Media recovery complete. ... SQL> alter database open; alter database open * ERROR at line 1: ORA-00600: internal error code, arguments: [kcratr_nab_less_than_odr], [1], [7], [160384], [160414], [], [], [], [], [], [], [] open的时候,又报错了,还是ORA-00600,但这次参数不同,是kcratr_nab_less_than_odr。 查看alert日志, alter database open Beginning crash recovery of 1 threads Started redo scan Completed redo scan read 47 KB redo, 0 data blocks need recovery Errors in file /u01/app/oracle/diag/rdbms/bisal/BISAL/trace/BISAL_ora_3464.trc (incident=3755): ORA-00600: internal error code, arguments: [kcratr_nab_less_than_odr], [1], [7], [160384], [160414], [], [], [], [], [], [], [] Incident details in: /u01/app/oracle/diag/rdbms/bisal/BISAL/incident/incdir_3755/BISAL_ora_3464_i3755.trc Use ADRCI or Support Workbench to package the incident. See Note 411.1 at My Oracle Support for error and packaging details. Aborting crash recovery due to error 600 Errors in file /u01/app/oracle/diag/rdbms/bisal/BISAL/trace/BISAL_ora_3464.trc: ORA-00600: internal error code, arguments: [kcratr_nab_less_than_odr], [1], [7], [160384], [160414], [], [], [], [], [], [], [] Errors in file /u01/app/oracle/diag/rdbms/bisal/BISAL/trace/BISAL_ora_3464.trc: ORA-00600: internal error code, arguments: [kcratr_nab_less_than_odr], [1], [7], [160384], [160414], [], [], [], [], [], [], [] ORA-600 signalled during: alter database open... Fri Sep 01 06:26:24 2017 Dumping diagnostic data in directory=[cdmp_20170901062624], requested by (instance=1, osid=3464), summary=[incident=3755]. Fri Sep 01 06:26:32 2017 Sweep [inc][3755]: completed Sweep [inc2][3755]: completed 再次检索MOS,《Alter database open fails with ORA-00600 kcratr_nab_less_than_odr (文档 ID 1296264.1)》这篇文章,详细介绍了kcratr_nab_less_than_odr错误信息,首先是现象, After Power Fail Alter database open fails with ORA-00600: internal error code, arguments: [kcratr_nab_less_than_odr]# The Database can't open at this Point. In the corresponding Tracefile we can find this Error Callstack:dbkedDefDump(): Starting incident default dumps (flags=0x2, level=3, mask=0x0)----- Current SQL Statement for this session (sql_id=1h50ks4ncswfn) -----ALTER DATABASE OPEN----- Call Stack Trace -----ksedst1 <- ksedst <- dbkedDefDump <- ksedmp <- dbgexPhaseII <- dbgexProcessError <- dbgePostErrorKGE <- kgeasnmierr <- kcratr_odr_check <- kcratr <- kctrec <- kcvcrv <- kcfopd <- adbdrv <- opiexe <- opiosq0 <- kpoal8 <- opiodr <- ttcpip <- opitsk <- opiino <- opiodr <- opidrv <- sou2o <- opimai_real <- ssthrdmain <- main <- start 指出异常断电后,open数据库则会报这个错误。此时数据库无法open,并且trace文件中会记录以上调用栈。 给出了一些原因, There was a power failure causing logical corruption in controlfileThis Problem is caused by Storage Problem of the Database Files. The Subsystem (eg. SAN) crashed while the Database was open. The Database then crashed because the Database Files were not accessible anymore. This caused a lost Write into the Online RedoLogs and/or causing logical corruption in controlfile so Instance Recovery is not possible and raising the ORA-600. 指出是由于异常断电,导致控制文件,出现了逻辑损坏。诱因可能是以下几种之一: 1. 数据库文件的存储问题。 2. 数据库open状态下,例如SAN子系统崩溃。 3. 由于数据库文件不可访问,导致数据库崩溃。 4. 在线重做日志写入丢失,或者控制文件出现逻辑损坏,以至于实例恢复不可执行,也会出现这种ORA-00600错误信息。 根据以上信息,我的问题,是由4这个诱因引起的,由于开机拷贝,导致在线重做日志,并未完成所有持久化,导致open数据库,无法执行实例恢复操作。 解决方案一: Do cancel based reocvery, and apply 'current online redolog' manually 做一次不完全恢复,并手工应用当前的在线重做日志。 首先检索控制文件名称,以及当前的日志文件, SQL> Show parameter control_files NAME control_files TYPE string VALUE /u01/app/oracle/oradata/BISAL/control01.ctl, /u01/app/oracle/oradata/BISAL/control02.ctl SQL> select a.member, a.group#, b.status from v$logfile a ,v$log b where a.group#=b.group# and b.status='CURRENT' ; MEMBER /u01/app/oracle/oradata/BISAL/redo01a.log GROUP# 1 STATUS CURRENT 执行shutdown abort, SQL> shutdown abort ORACLE instance shut down. 操作系统中备份控制文件,接着将数据库,启动mount状态,执行不完全恢复,输入当前使用的在线重做日志文件名信息,完成介质恢复, SQL> startup mount; ORACLE instance started. SQL> recover database using backup controlfile until cancel ; ORA-00279: change 243562 generated at 08/30/2017 08:10:54 needed for thread 1 ORA-00289: suggestion : /u01/app/oracle/fast_recovery_area/BISAL/archivelog/2017_09_01/o1_mf_1_7_%u_.arc ORA-00280: change 243562 for thread 1 is in sequence #7 Specify log: {<RET>=suggested | filename | AUTO | CANCEL} /u01/app/oracle/oradata/BISAL/redo01a.log Log applied. Media recovery complete. 从alert日志看, ALTER DATABASE MOUNT Successful mount of redo thread 1, with mount id 286455927 Database mounted in Exclusive Mode Lost write protection disabled Completed: ALTER DATABASE MOUNT Fri Sep 01 06:36:57 2017 ALTER DATABASE RECOVER database using backup controlfile until cancel Media Recovery Start Serial Media Recovery started ORA-279 signalled during: ALTER DATABASE RECOVER database using backup controlfile until cancel ... Fri Sep 01 06:37:16 2017 ALTER DATABASE RECOVER LOGFILE '/u01/app/oracle/oradata/BISAL/redo01a.log' Media Recovery Log /u01/app/oracle/oradata/BISAL/redo01a.log Incomplete recovery applied all redo ever generated. Recovery completed through change 243563 time 08/30/2017 08:10:54 Media Recovery Complete (BISAL) Completed: ALTER DATABASE RECOVER LOGFILE '/u01/app/oracle/oradata/BISAL/redo01a.log' 以resetlogs方式,open数据库, SQL> alter database open resetlogs; Database altered. 从alert日志看, alter database open resetlogs RESETLOGS after complete recovery through change 243563 Clearing online redo logfile 1 /u01/app/oracle/oradata/BISAL/redo01a.log Clearing online log 1 of thread 1 sequence number 7 Clearing online redo logfile 1 complete Clearing online redo logfile 2 /u01/app/oracle/oradata/BISAL/redo02a.log Clearing online log 2 of thread 1 sequence number 5 Clearing online redo logfile 2 complete Clearing online redo logfile 3 /u01/app/oracle/oradata/BISAL/redo03a.log Clearing online log 3 of thread 1 sequence number 6 Clearing online redo logfile 3 complete Resetting resetlogs activation ID 286366553 (0x11119b59) Online log /u01/app/oracle/oradata/BISAL/redo01a.log: Thread 1 Group 1 was previously cleared Online log /u01/app/oracle/oradata/BISAL/redo02a.log: Thread 1 Group 2 was previously cleared Online log /u01/app/oracle/oradata/BISAL/redo03a.log: Thread 1 Group 3 was previously cleared Fri Sep 01 06:39:57 2017 Setting recovery target incarnation to 2 Fri Sep 01 06:39:57 2017 Assigning activation ID 286455927 (0x1112f877) Thread 1 opened at log sequence 1 Current log# 1 seq# 1 mem# 0: /u01/app/oracle/oradata/BISAL/redo01a.log Successful open of redo thread 1 Fri Sep 01 06:39:57 2017 MTTR advisory is disabled because FAST_START_MTTR_TARGET is not set Fri Sep 01 06:39:57 2017 SMON: enabling cache recovery [3636] Successfully onlined Undo Tablespace 2. Undo initialization finished serial:0 start:688494 end:689074 diff:580 (5 seconds) Dictionary check beginning Dictionary check complete Verifying file header compatibility for 11g tablespace encryption.. Verifying 11g file header compatibility for tablespace encryption completed SMON: enabling tx recovery Database Characterset is ZHS16GBK No Resource Manager plan active replication_dependency_tracking turned off (no async multimaster replication found) Starting background process QMNC Fri Sep 01 06:40:01 2017 QMNC started with pid=20, OS id=3664 LOGSTDBY: Validating controlfile with logical metadata LOGSTDBY: Validation complete Completed: alter database open resetlogs Fri Sep 01 06:40:03 2017 db_recovery_file_dest_size of 2048 MB is 0.00% used. This is a user-specified limit on the amount of space that will be used by this database for recovery-related files, and does not reflect the amount of space available in the underlying filesystem or ASM diskgroup. Fri Sep 01 06:40:04 2017 Starting background process CJQ0 Fri Sep 01 06:40:04 2017 CJQ0 started with pid=22, OS id=3681 此时数据库已打开,可以执行一次正常关闭、打开操作,进行确认, SQL> shutdown immediate; SQL> startup; 解决方案二: Recreate the controlfile using the Controlfile recreation script 使用控制文件重建脚本,重建控制文件。 没有实操,直接展示一下文章的操作, $ rman target /rman> spool log to '/tmp/rman.log';rman> list backup ;rman> exit# Keep this log handyGo to sqlplusSQL> Show parameter control_files Keep this location handy.SQL> oradebug setmypid SQL> Alter session set tracefile_identifier='controlfilerecreate' ;SQL> Alter database backup controlfile to trace noresetlogs;SQL> oradebug tracefile_name ; --> This command will give the path and name of the trace fileGo to this location ,Open this trace file and select the controlfile recreation script with NOResetlogs option SQL> Shutdown immediate;Rename the existing controlfile to <originalname>_old ---> This is Important as we need to have a backup of existing controlfile since we plan to recreate itSQL> Startup nomountNow run the Controlfile recreation script with NO Resetlogs option.SQL> Alter database open ;For database version 10g and above Once database is opened you can recatalog the rman backup information present in the list /tmp/rman.log usingRman> Catalog start with '<location of backupiece>' ;If backups are on tape, and you are not using a catalog, backups can be cataloged using information in: How to Catalog Tape Backup Pieces (Doc ID 550082.1) 主要过程,是在RMAN中使用Alter database backup controlfile to trace noresetlogs,创建控制文件重建脚本,数据库启动至nomount状态,执行脚本,以非resetlogs方式,open数据库。 无论用上述何种方法,数据库open了,此时就应该做一次热备,下次再碰见这种情况,一旦上述两种方法不起作用,或者备份集不全,则可以从最近的一次备份,进行数据库的restore和recover, Once the database has been opened using the Option a or Option b, it is recommended to take a hot backup of the database. The same steps are applicable to RAC if all instance are down with same error.If both Options (a|b) fails, or you do not have the full set of files, then you have to restore and recover the Database from a recent Backup. 总结: 1. 对于虚拟机环境的拷贝,建议需要关闭操作系统,再拷贝文件,避免这种异常断电的场景。 2. ORA-00600是Oracle中的一种通用错误号,和普通ORA报错不同,可能会需要根据堆栈信息,才能进一步定位问题,MOS有工具可以方便检索,但终究还是需要靠积累和学习,才能从容面对更多的错误,对于我这样的小白来说,需要继续学习。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
这几天因为一些特殊原因,网站值班表不能用,一旦出现问题,找相应系统的值班人员,就比较困难了,但通过一些渠道,可以有一个文本文件,其中包含了这几天的值班信息,为了更明白的说明问题,我们假设有A系统和B系统,两个值班,其中A系统值班人员为,每人值一天, B系统值班人员为,每人值一天, json格式的文件,准确的说,应该是json数组,如下所示, { "dlist": [ { "dId": "1743664", "dName": "A值班", "dPerson": "梅西", "dEmail": "abc@abc.com", "dPhone": "10000000000", "startDate": "2017-09-02 08:00:00", "endDate": "2017-09-02 18:00:59" }, { "dId": "1850998", "dName": "B值班", "dPerson": "C罗", "dEmail": "xyz@xyz.com", "dPhone": "10000000001", "startDate": "2017-09-02 00:00:01", "endDate": "2017-09-03 00:00:00" }, { "dId": "1743600", "dName": "A值班", "dPerson": "内马尔", "dEmail": "jhk@jhk.com", "dPhone": "10000000002", "startDate": "2017-09-03 08:00:00", "endDate": "2017-09-04 18:00:59" }, { "dId": "1850901", "dName": "B值班", "dPerson": "阿扎尔", "dEmail": "lox@lox.com", "dPhone": "10000000003", "startDate": "2017-09-03 00:00:01", "endDate": "2017-09-04 00:00:00" } ]} 其实从这个文件中,直接使用ctrl+f,也能实现检索,但毕竟稍微不方便一些,另外就是想练练手,于是乎就考虑,清理一下格式,让其看起来更可读一些。 既然是json,那么就需要解析json,json解析器很多,这里则用了gson, GSON是Google开发的Java API,用于转换Java对象和Json对象。更多关于GSON的API可以访问:http://sites.google.com/site/gson/. 最高版本是2.8,可以从以下网址,查看maven配置,或者直接下载jar, http://www.mvnrepository.com/artifact/com.google.code.gson/gson maven配置, <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson --><dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.0</version></dependency> 使用gson解析json数据,可以分为三步, 1. 首先需要创建Gson解析器。2. 创建JSONObject对象。3. 将json数据转为为相应的数据。 咱直接上代码, package com.bisal.zb;import java.io.FileNotFoundException;import java.io.FileReader;import java.io.FileWriter;import java.io.IOException;import com.google.gson.JsonArray;import com.google.gson.JsonIOException;import com.google.gson.JsonObject;import com.google.gson.JsonParser;import com.google.gson.JsonSyntaxException;public class ZB_1 { public static void main(String[] args) { try { JsonParser parser = new JsonParser(); // 创建JSON解析器 JsonObject object = (JsonObject) parser.parse(new FileReader( "file/zhiban.log")); // 创建JsonObject对象 JsonArray array = object.get("dlist").getAsJsonArray(); // 得到为json的数组 FileWriter writer = new FileWriter("file/output1.txt"); String dName; String dPerson; String dEmail; String dPhone; String dDate; for (int i = 0; i < array.size(); i++) { JsonObject subObject = array.get(i).getAsJsonObject(); dutyName = subObject.get("dName").getAsString(); dutyPerson = subObject.get("dPerson").getAsString(); dutyPhone = subObject.get("dPhone").getAsString(); dutyEmail = subObject.get("dEmail").getAsString(); dutyDate = subObject.get("startDate").getAsString() .substring(0, 10); writer.write("日期=[" + dDate + "] 值班项=[" + dName + "] 值班人=[" + dPerson + "] 邮箱=[" + dEmail + "] 电话=[" + dPhone + "]\n"); } writer.close(); } catch (JsonIOException e) { e.printStackTrace(); } catch (JsonSyntaxException e) { e.printStackTrace(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }} 解析json后,写入文件中,输出文件内容, 日期=[2017-09-02] 值班项=[A值班] 值班人=[梅西] 邮箱=[abc@abc.com] 电话=[10000000000] 日期=[2017-09-02] 值班项=[B值班] 值班人=[C罗] 邮箱=[xyz@xzy.com] 电话=[10000000001] 日期=[2017-09-03] 值班项=[A值班] 值班人=[内马尔] 邮箱=[jkh@jkh.com] 电话=[10000000002] 日期=[2017-09-03] 值班项=[B值班] 值班人=[阿扎尔] 邮箱=[lox@lox.com] 电话=[10000000003] 注意,这里是按照时间排序,同一个值班,每天都有,因此同一个值班,位置不是相邻的,我们日常检索,往往根据值班项,直接定位某一天的值班人员,所以可以据此做一些处理,按照值班项排序。 为了排序,首先定义一个实体类,重要的是,继承Comparable接口,重写compareTo方法,为的就是按照值班项dName,进行排序,此处为按照中文字符排序。 package com.bisal.zb;public class ZBObject implements Comparable { private String dutyName; private String dutyPerson; private String dPhone; private String dEmail; private String dDate; public String getDName() { return dName; } public void setDName(String dName) { this.dName = dName; } public String getDPerson() { return dPerson; } public void setDPerson(String dPerson) { this.dPerson = dPerson; } public String getDPhone() { return dPhone; } public void setDPhone(String dPhone) { this.dPhone = dPhone; } public String getDEmail() { return dEmail; } public void setDEmail(String dEmail) { this.dEmail = dEmail; } public String getDDate() { return dDate; } public void setDDate(String dDate) { this.dDate = dDate; } @Override public int compareTo(Object o) { ZBObject zbo = (ZBObject)o; String otherDName = zbo.getDName(); return this.dName.compareTo(otherDName); }} 解析json,将每一项数据,存储于ZBObject对象,用Collections.sort()方法,进行排序, package com.bisal.zb;import java.io.FileNotFoundException;import java.io.FileReader;import java.io.FileWriter;import java.io.IOException;import java.text.Collator;import java.util.ArrayList;import java.util.Collections;import java.util.List;import com.google.gson.JsonArray;import com.google.gson.JsonIOException;import com.google.gson.JsonObject;import com.google.gson.JsonParser;import com.google.gson.JsonSyntaxException;public class ZB_2 { public static void main(String[] args) { try { JsonParser parser = new JsonParser(); // 创建JSON解析器 JsonObject object = (JsonObject) parser.parse(new FileReader( "file/zhiban.log")); // 创建JsonObject对象 JsonArray array = object.get("dutylist").getAsJsonArray(); // 得到为json的数组 String dName = ""; String dPerson = ""; String dEmail = ""; String dPhone = ""; String dDate = ""; List<ZBObject> zbObjectList = new ArrayList<ZBObject>(); for (int i = 0; i < array.size(); i++) { JsonObject subObject = array.get(i).getAsJsonObject(); dName = subObject.get("dName").getAsString(); dPerson = subObject.get("dPerson").getAsString(); dPhone = subObject.get("dPhone").getAsString(); dEmail = subObject.get("dEmail").getAsString(); dDate = subObject.get("startDate").getAsString() .substring(0, 10); ZBObject zbObject = new ZBObject(); zbObject.setDName(dName); zbObject.setDPerson(dPerson); zbObject.setDEmail(dEmail); zbObject.setDPhone(dPhone); zbObject.setDDate(dDate); zbObjectList.add(zbObject); } FileWriter writer = new FileWriter("file/output2.txt"); Collections.sort(zbObjectList); for (ZBObject zbo : zbObjectList) { writer.write("值班项=[" + zbo.getDName() + "] 日期=[" + zbo.getDDate() + "] 值班人=[" + zbo.getDPerson() + "] 邮箱=[" + zbo.getDEmail() + "] 电话=[" + zbo.getDPhone() + "]\n"); } writer.close(); } catch (JsonIOException e) { e.printStackTrace(); } catch (JsonSyntaxException e) { e.printStackTrace(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }} 输出文件内容, 值班项=[A值班] 日期=[2017-09-02] 值班人=[梅西] 邮箱=[abc@abc.com] 电话=[10000000000] 值班项=[A值班] 日期=[2017-09-03] 值班人=[内马尔] 邮箱=[jkh@jkh.com] 电话=[10000000002] 值班项=[B值班] 日期=[2017-09-02] 值班人=[C罗] 邮箱=[xyz@xzy.com] 电话=[10000000001] 值班项=[B值班]日期=[2017-09-03] 值班人=[阿扎尔] 邮箱=[lox@lox.com] 电话=[10000000003] 按照值班项进行的排序,符合原始需求。 源代码可以从github上下载, https://github.com/bisal-liu/java 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
一个微信群中有位朋友问“一张几亿的分区表,能改名么?”。我想他要表达的,不是语法上是否可以改名,而是改名是否有什么影响? 是否有影响,需要看看背后做了什么。 创建测试表, create table tbl_par ( id number, insert_time date ) partition by range (insert_time) ( partition par_1 values less than (to_date('2017-01-02', 'yyyy-mm-dd')), partition par_2 values less than (to_date('2017-01-03', 'yyyy-mm-dd')), partition par_3 values less than (to_date('2017-01-04', 'yyyy-mm-dd')) ); 插入测试数据, SQL> select insert_time, count(*) from tbl_par group by insert_time;INSERT_TI COUNT(*)--------- ----------01-JAN-17 1000002-JAN-17 1000003-JAN-17 10000 SQL> select count(*) from tbl_par partition(par_1); COUNT(*) ---------- 10000 SQL> select count(*) from tbl_par partition(par_2); COUNT(*) ---------- 10000 SQL> select count(*) from tbl_par partition(par_3); COUNT(*) ---------- 10000 执行10046事件, SQL> alter session set events '10046 trace name context forever, level 12'; Session altered. SQL> alter table tbl_par rename to tbl_par_k; Table altered. SQL> alter session set events '10046 trace name context off'; Session altered. 从trace看,首先以对表TBL_PAR以NOWAIT加了EXCLUSIVE表级排他锁, LOCK TABLE "TBL_PAR" IN EXCLUSIVE MODE NOWAIT 此时禁止对表进行DDL或DML操作,若执行了DDL操作,直接提示“ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired”的错误,若执行了DML操作,则处于hang,但允许执行select(非for update)操作。 接着有一段自治事务,判断table rename的操作, 又做了一系列的CRUD操作,主要是针对数据字典表,总计100次select,7次insert,16次delete,10次update, 整个trace文件一共4107行,大约执行一半的时候,从obj$%E
我们有一台ES服务器,设置了每天02:00执行一次清理索引数据的定时任务,但这两天总是出现磁盘空间抖动,一线一看见超了阈值,就打电话报警,可能整晚要被叫几次,ES作为日志平台的一部分,只是为了方便我们检索日志,不影响实际业务,这就比较烦了。 除了挂起报警,一种暴力的方法,就是再设置一些定时作业时间点,简单是简单,但弊端就是有可能到点儿了,空间还有,不需要删除,但仍执行了删除,可见的数据就少了,而且如果间隔时间设置不合理,很有可能还会超阈值。 今儿单位值班,就简单思考一下,既然报警是有阈值的,那么执行删除的操作,是不是可以参考执行? 以下实现采用了shell脚本,简单的方式,当然你用python,甚至重一些的java,都可以实现类似的功能,实现原理相同,实现方式不同,无伤大雅。我们一步一步拆解来看,如何实现这一个功能。 如下是执行df -h回显, 这里我要监控的是/opt/app路径,要做的判断就是如果此路径空间使用率超过90%,则执行删除ES索引数据的操作。 首先需要获取/opt/app的空间使用率,df -h中第四列,已经给出了使用率百分比,为了获取这个值,可以使用awk命令,来截取这个值。awk接收df -h的输出,作为输入,$4表示打印第四列(空格或者TAB隔开), 我其实只是需要/opt/app对应的信息,df -h指定/opt/app路径, 再进一步,我只需要百分比数据,只检索包含/opt/app这行, 这里是百分比,我需要数字进行阈值比较,因此需要删除%,用了sed做值替换,即将%换为空, 现在我们得到了当前使用率,要和阈值进行比较了,其中CURRENT是当前使用率,THRESHOLD是阈值,如果CURRENT>=THRESHOLD,则执行curl -XDELETE $URL操作,进行ES索引数据的清理工作, 可以将其中的变量,设为参数,便于统一管理,还可以log一些信息, 以上脚本加入crontab中,定时10分钟执行一次,就可以实现,根据磁盘空间阈值,来做索引数据删除的操作。一个比较简单的功能需求,涉及了awk、管道、sed这些常用的指令,用起来就可以慢慢理解了。 完整的shell脚本可以从GitHub上来下载, https://github.com/bisal-liu/oracle/blob/master/crontab_es_del.sh 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
相比于传统行业,互联网浪潮中MySQL一直是数据库的主力军,无论是曾经的SUN,还是现在的Oracle,虽然可能商业策略不同,但发展方向还是平稳向前的,因此说自己不会MySQL,还真有些不好意思了。 我只能算是MySQL中的小小白,研究生毕设的时候,装过windows版本的MySQL,就像对Oracle一样,仅仅是用,但对于一门技术来说,没有深入理解运行的原理,就不算真正了解这门技术。又有一种说法,就是相似的技术是相通的,原理性质的知识,有些是可以复用学习和了解。 碰巧有一个旁支的项目,要用MySQL,借此机会逼着自己学习MySQL。为了学习MySQL,首先就要有一个MySQL的环境,第一步就是安装,这里说的安装,肯定不是Windows中“下一步”这种的安装。我们的环境是Linux 6.5,需要安装MySQL 5.7.19版本。 P.S. 以下只是我自己学习的体会,有不准确的地方,还请各位牛人指出来。 MySQL的安装主要有两种方式,一种就是利用源码,自行编译安装,这是开源相对于商业闭源软件,独特的风景线。另一种方式,就是使用二进制文件,又分为两种,一种是不针对特定平台,使用.tar.gz压缩文件来安装,一种是针对特定平台,使用.rpm文件安装。 官网中相应地有以上三种方式,对应的下载链接,其中源码安装,对应"Source Code",.tar.gz对应"Linux-Generic",.rpm则对应于"Red Hat Enterprise Linux/Oracle Linux",如下图所示, 这次我用rpm和源码,这两种方式进行安装。 1. rpm安装 首先需要选择对应平台,位数,下载完整版RPM Bundle, 解压缩安装包, tar xvf mysql-5.7.19-1.el6.x86_64.rpm-bundle.tar 基本安装,只需要几个rpm包,但有顺序要求, rpm -ivh mysql-community-common-5.7.19-1.el6.x86_64.rpm rpm -ivh mysql-community-libs-5.7.19-1.el6.x86_64.rpm(依赖于common) rpm -ivh mysql-community-client-5.7.19-1.el6.x86_64.rpm(依赖于libs) rpm -ivh mysql-community-server-5.7.19-1.el6.x86_64.rpm(依赖于client、common) 安装完成后,会自动创建mysql用户和组, [root@RAC2 rb]# id mysql uid=27(mysql) gid=11002(mysql) groups=11002(mysql) 启动mysqld服务,记住不是mysql,默认3306端口打开, [root@RAC2 mysql]# service mysqld start Initializing MySQL database: test [ OK ] Starting mysqld: [ OK ] [root@RAC2 mysql]# service mysqld status mysqld (pid 30775) is running... [root@DEPRAC2 mysql]# netstat -anp | grep 3306 tcp 0 0 :::3306 :::* LISTEN 30775/mysqld 登录提示错误, [root@RAC2 mysql]# mysql ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO) 参考了周老师的书,这是因为MySQL5.7新版本中,调整了安全策略,默认情况下root不能没有密码,数据库启动的时候,会自动为root随机生成一个密码,从error日志中可以看见,注意这由于采用了默认安装,因此error日志默认路径是/var/log,/etc/my.cnf是默认的配置文件路径, [root@RAC2 mysql]# cat /var/log/mysqld.log | more 2017-08-24T06:24:04.897029Z 1 [Note] A temporary password is generated for root@localhost: MDjfrXl;E9hp 临时强密码为“MDjfrXl;E9hp”。 再次登录, [root@RAC2 mysql]# mysql -uroot -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 6 Server version: 5.7.19 Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> 执行报错,提示需要改密码才能继续, mysql> show variables like "%sock%"; ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement. 一般的密码策略,会认为是不符合规范, mysql> alter user 'root'@'localhost' identified by 'mysql'; ERROR 1819 (HY000): Your password does not satisfy the current policy requirements mysql> alter user 'root'@'localhost' identified by 'Mysql@admin'; ERROR 1819 (HY000): Your password does not satisfy the current policy requirements mysql> alter user 'root'@'localhost' identified by 'My@38@sql'; Query OK, 0 rows affected (0.01 sec) 此时,就可以执行任何指令了, mysql> select user from mysql.user; +---------------+ | user | +---------------+ | mysql.session | | mysql.sys | | root | +---------------+ 至此,完成了rpm安装。 若需要卸载,可以不按顺序执行以下指令, rpm -e mysql-community-common-5.7.19-1.el6.x86_64 --nodeps rpm -e mysql-community-client-5.7.19-1.el6.x86_64 --nodeps rpm -e mysql-community-server-5.7.19-1.el6.x86_64 --nodeps rpm -e mysql-community-libs-5.7.19-1.el6.x86_64 --nodeps 此时,/etc/my.cnf配置文件、mysql用户和组,都会被删除。 2. 源码安装 整个过程,对于第一次接触的人来说,还是比较曲折,尤其是中间碰见了各种坑,需要一一化解。 Source Code中一种是针对版本的src.rpm包,一种是tar.gz包,前者包含的组件比较完整,我们下载这包, 由于这种安装,不会像rpm自动完成配置,因此需要先创建用户, [root@RAC2 mysql-5.7.19]# groupadd mysql [root@RAC2 mysql-5.7.19]# useradd -r -g mysql mysql 其中-r表示用户是系统用户,不可登录系统。 源码安装前,还需要装一些辅助包, yum install cmake yum install bison yum install libaio-devel* 安装src.rpm, [root@RAC2 src]# rpm -ivh mysql-community-5.7.19-1.el6.src.rpm warning: mysql-community-5.7.19-1.el6.src.rpm: Header V3 DSA/SHA1 Signature, key ID 5072e1f5: NOKEY 1:mysql-community ########################################### [100%] 一开始我不清楚,本地目录下,未看见有解压的文件夹,再看上面有warning,以为是因为有错,实际并非如此。 问题1:warning: mysql-community-5.7.19-1.el6.src.rpm: Header V3 DSA/SHA1 Signature, key ID 5072e1f5: NOKEY 这个错误是因为yum安装了旧版本的GPG keys造成,有帖子做法是rpm跟着--force --nodeps参数,或者rpm --import /etc/pki/rpm-gpg/RPM*再来安装。 可实际操作后,发现错误依旧。 参考官方 https://dev.mysql.com/doc/refman/5.7/en/checking-gpg-signature.html ,拷贝以下内容,作为mysql_pubkey.asc, 执行gpg --import指令, [root@RAC2 src]# gpg --import mysql_pubkey.asc gpg: directory `/root/.gnupg' created gpg: new configuration file `/root/.gnupg/gpg.conf' created gpg: WARNING: options in `/root/.gnupg/gpg.conf' are not yet active during this run gpg: keyring `/root/.gnupg/secring.gpg' created gpg: keyring `/root/.gnupg/pubring.gpg' created gpg: /root/.gnupg/trustdb.gpg: trustdb created gpg: key 5072E1F5: public key "MySQL Release Engineering <mysql-build@oss.oracle.com>" imported gpg: Total number processed: 1 gpg: imported: 1 gpg: no ultimately trusted keys found 执行导出gpg --export [root@RAC2 src]# gpg --export -a 5072e1f5 > 5072e1f5.asc 之所以这么做,官方解释是, If you are using RPM 4.1 and it complains about (GPG) NOT OK (MISSING KEYS: GPG#5072e1f5), even though you have imported the MySQL public build key into your own GPGkeyring, you need to import the key into the RPM keyringfirst. RPM 4.1 no longer uses your personal GPG keyring (or GPG itself). Rather, RPM maintains a separate keyring becauseit is a system-wide application and a user's GPG publickeyring is a user-specific file. To import the MySQL public key into the RPM keyring, first obtain the key, then use rpm --import to import the key. RPM包有内置的GPG签名和MD5校验,用户和系统层级范围的问题,需要手工export、import操作。 如果之前未导入pubkey,则此时执行export会报错,没有需要导出的信息, [root@RAC2 src]# gpg --export -a 5072e1f5 > 5072e1f5.asc gpg: WARNING: nothing exported 执行导入, [root@RAC2 src]# rpm --import 5072e1f5.asc 如果之前未导入pubkey,则此时执行import会报错,没有需要导入的信息, [root@RAC2 src]# rpm --import 5072e1f5.asc error: 5072e1f5.asc: import read failed(0). 此时执行,不会报错了, [root@RAC2 src]# rpm --checksig mysql-community-5.7.19-1.el6.src.rpm mysql-community-5.7.19-1.el6.src.rpm: (sha1) dsa sha1 md5 gpg OK [root@RAC2 src]# rpm -ivh mysql-community-5.7.19-1.el6.src.rpm 1:mysql-community ########################################### [100%] 问题2:rpm ...src.rpm,到底安装在哪了? 实际存储在用户家目录中,此处使用root,则为/root目录,文件夹结构为, rpmbuild | SOURCE | SPECS SOURCE路径内容为, [root@RAC2 SOURCES]# lsboost_1_59_0.tar.bz2 filter-provides.sh filter-requires.sh mysql-5.1.72.tar.gz mysql-5.7.19.tar.gz 解压mysql-5.7.19.tar.gz,执行jar -jxvf boost_1_59_0.tar.bz2解压文件,放置mysql-5.7.19路径下,创建debug文件夹,用于存储安装的中间文件, 接下来就要出现各种坑了。 参考周老师的书,通过CMake生成编译环境。 [root@RAC2 debug]# cmake .. -DBUILD_CONFIG=mysql_release \ > -DINSTALL_LAYOUT=STANDLONE \ > -DCMAKE_BUILD_TYPE=RelWithDebInfo \ > -DENABLE_DTRACE=OFF \ > -DWITH_EMBEDDED_SERVER=OFF \ > -DWITH_INNODB_MEMCACHED=ON \ > -DWITH_SSL=bundled \ > -DWITH_ZLIB=system \ > -DWITH_PAM=ON \ > -DCMAKE_INSTALL_PREFIX=/var/mysql \ > -DINSTALL_PLUGINDIR="/var/mysql/lib/plugin" \ > -DDEFAULT_CHARSET=utf-8 \ > -DDEFAULT_COLLATION=utf8_general_ci \ > -DWITH_EDITLINE=bundled \ > -DFEATURE_SET=community \ > -DCOMPILATION_COMMENT="MySQL Server(GPL)" \ > -DWITH_DEBUG=OFF \ > -DWITH_BOOST=.. CMake Error at CMakeLists.txt:21 (CMAKE_MINIMUM_REQUIRED): CMake 2.8.2 or higher is required. You are running version 2.6.4 提示错误,CMake版本需要2.8.2以上,当前仅为2.6.4。 下载高版本cmake,http://www.cmake.org/cmake/resources/software.html,解压缩cmake-2.8.12.2.tar.gz,依次执行, ./bootstrap make make install 报了一些错误, [root@RAC2 cmake-2.8.12.2]# ./bootstrap --------------------------------------------- CMake 2.8.12.2, Copyright 2000-2012 Kitware, Inc. --------------------------------------------- Error when bootstrapping CMake: Cannot find appropriate C compiler on this system. Please specify one using environment variable CC. See cmake_bootstrap.log for compilers attempted. --------------------------------------------- Log of errors: /root/cmake-2.8.12.2/Bootstrap.cmk/cmake_bootstrap.log --------------------------------------------- 提示需要cc的编译环境,咱就安装, [root@RAC2 cmake-2.8.12.2]# yum install gcc gcc-c++ make automake 执行完成, [root@RAC2 cmake-2.8.12.2]# ./bootstrap ... Curses libraries were not found. Curses GUI for CMake will not be built. -- Looking for elf.h -- Looking for elf.h - found -- Looking for a Fortran compiler -- Looking for a Fortran compiler - NOTFOUND -- Found unsuitable Qt version "" from NOTFOUND -- Performing Test run_pic_test -- Performing Test run_pic_test - Success -- Configuring done -- Generating done -- Build files have been written to: /root/cmake-2.8.12.2 --------------------------------------------- CMake has bootstrapped. Now run gmake. 再执行指令make和make install,此时可以看出,已经升级为最新版本了, [root@DEPRAC2 cmake-2.8.12.2]# ls -l /usr/local/bin/cmake -rwxr-xr-x 1 root root 10204177 Aug 24 16:51 /usr/local/bin/cmake [root@DEPRAC2 mysql-5.7.19]# /usr/local/bin/cmake -versioncmake version 2.8.12.2 再次执行上述cmake,又出现了几个错误,一个是STANDLONE拼错了,应该是STANDALONE, -- Packaging as: mysql-5.7.19-Linux-x86_64 CMake Error at cmake/install_layout.cmake:107 (MESSAGE): Invalid INSTALL_LAYOUT parameter:STANDLONE. Choose between RPM;DEB;SVR4;FREEBSD;GLIBC;OSX;TARGZ;SLES;STANDALONE Call Stack (most recent call first): CMakeLists.txt:213 (INCLUDE) 一个是字符集utf-8,应该写为utf8(这个错误是安装完成后,初始化数据库时才报的),需要改正,重新执行cmake指令。 另一个是, -- Could NOT find Curses (missing: CURSES_LIBRARY CURSES_INCLUDE_PATH) CMake Error at cmake/readline.cmake:64 (MESSAGE): Curses library not found. Please install appropriate package, remove CMakeCache.txt and rerun cmake.On Debian/Ubuntu, package name is libncurses5-dev, on Redhat and derivates it is ncurses-devel. Call Stack (most recent call first): cmake/readline.cmake:107 (FIND_CURSES) cmake/readline.cmake:197 (MYSQL_USE_BUNDLED_EDITLINE) CMakeLists.txt:519 (MYSQL_CHECK_EDITLINE) 删除原始CMakeCache.txt,安装ncurses-devel, mv CMakeCache.txt CMakeCache.txt.k yum install ncurses-devel* 再次执行,出现Configuring done和Generating done,安装完成, -- CMAKE_C_FLAGS_RELWITHDEBINFO: -O3 -g -fabi-version=2 -fno-omit-frame-pointer -fno-strict-aliasing -DDBUG_OFF -- CMAKE_CXX_FLAGS_RELWITHDEBINFO: -O3 -g -fabi-version=2 -fno-omit-frame-pointer -fno-strict-aliasing -DDBUG_OFF -- Configuring done -- Generating done CMake Warning: Manually-specified variables were not used by the project: WITH_PAM -- Build files have been written to: /root/rpmbuild/SOURCES/mysql-5.7.19/debug 接下来执行make -j 30,启动30个线程,进行编译,否则编译慢,-j含义, -j [jobs], --jobs[=jobs] Specifies the number of jobs (commands) to run simultaneously. If there is more than one -j option, the last one is effective. If the -j option is given without an argument, make will not limit the number of jobs that can run simultaneously. 碰见了另一个坑,就是磁盘空间满了, Linking CXX static library libsql.a /usr/bin/ar: libsql.a: No space left on device make[2]: *** [sql/libsql.a] Error 1 make[1]: *** [sql/CMakeFiles/sql.dir/all] Error 2 因为默认放在了/root下,将其mv至空闲磁盘中,再次执行,提示100%完成, [100%] Building CXX object sql/CMakeFiles/udf_example.dir/udf_example.cc.o Linking CXX shared module udf_example.so [100%] Built target udf_example [100%] Built target pfs_connect_attr-t 执行make install, -- Installing: /var/mysql/mysql-test/lib/My/SafeProcess/Base.pm -- Installing: /var/mysql/support-files/mysqld_multi.server -- Installing: /var/mysql/support-files/mysql-log-rotate -- Installing: /var/mysql/support-files/magic -- Installing: /var/mysql/share/aclocal/mysql.m4 -- Installing: /var/mysql/support-files/mysql.server 数据库默认安装于/var/mysql, 编辑配置文件my.cnf, [mysqld]port=3306datadir=/DATA/mysql/data_3306log_error=/DATA/mysql/data_3306/error.logbasedir=/DATA/mysql 创建数据库,指定使用mysql用户, [root@RAC2 mysql]# /var/mysql/bin/mysqld --defaults-file=/DATA/mysql/my.cnf --initialize --user=mysql 此时可以看出,/DATA/mysql/data_3306目录属主是mysql, 启动数据库,注意不是用mysqld,而是mysql, [root@RAC2 mysql]# /var/mysql/bin/mysql --defaults-file=/DATA/mysql/my.cnf --user=mysql 查看进程、端口, [root@RAC2 data_3306]# ps -ef | grep mysql mysql 26428 23453 0 18:56 pts/2 00:00:00 /var/mysql/bin/mysqld --defaults-file=/DATA/mysql/my.cnf --user=mysql root 26527 29090 0 19:08 pts/1 00:00:00 grep mysql [root@DEPRAC2 data_3306]# netstat -anp | grep 3306 tcp 0 0 :::3306 :::* LISTEN 26428/mysqld 接下来和rpm安装相同,从error日志中找出临时密码,登录改密码,完成数据库初始化工作。注意此时error日志,存储于配置文件定义的log_error=/DATA/mysql/data_3306/error.log了。 总结: 1. rpm方式的安装,类似于Windows下安装,确实比较简单,容易上手。 2. 源码安装,过程比较繁琐,而且碰见不少坑,但这种方式,可以接触更多,学到不少东西,还是值得推荐。 当然,我这儿只是学了些皮毛,MySQL还是比较精深,需要学习的地方还有不少,一步一步来吧,有可能机会就在眼前了。 参考文献: 1. 《MySQL运维内参》 2. 《MySQL官方参考》 https://dev.mysql.com/doc/refman/5.7/en/checking-rpm-signature.html https://dev.mysql.com/doc/refman/5.7/en/linux-installation-rpm.html https://dev.mysql.com/doc/refman/5.7/en/installing-source-distribution.html 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
单位有一套Oracle 9i的古老测试数据库,因为机房搬迁,所以需要迁移数据,新库是Oracle 11g了,一个比较简单的需求,但过程中碰见了一些问题,看似比较琐碎,值得总结一下。 由于源库是9i,因此只能用imp/exp,不能用数据泵。 问题1:导入目标库用户的默认表空间 源库由于不规范的使用,对象默认存储的是数据库默认表空间USERS,既然是迁移,新库就要尽量规范一些。但问题来了,impdp/expdp可以使用remap_tablespace映射新旧表空间,exp/imp应该如何做? 网上有一种说法是,首先收回用户user的unlimited tablespace权限,然后设置user默认表空间为bank_tbs,再将user对system和users表空间配额设置为0,意图是让imp导入的时候,发现users表空间无权限,则自动找用户的默认表空间bank_tbs。 revokeunlimited tablespace from user; alteruser user quota unlimited on bank_tbs; alteruser user quota 0 on system; alteruser user quota 0 on users; 但从我实测看,并不是这样,可以使用imp命令的show选项,看dmp文件内容,create table子句是会跟着tablespace users,即指定了表使用的表空间名称,由于user用户在users表空间配额为0,因此会报quota相关的错误,并不会找用户默认的bank_tbs表空间。 我们再捋一下, 1. dump文件中有指定了tablespace users表空间。 2. 目标库存在users表空间,但用户在users表空间配额为0,其默认表空间为bank_tbs。 3. imp执行导入,报错users表空间quota错误。 用户默认表空间的作用,是若create table语句未指定tablespace子句,则会默认存储此表空间,既然如此,既然如此,又由于这是一套测试库,因此首先改一下users表空间名称, alter tablespace users rename to users_k; 然后执行imp导入,就可以正常存入user用户默认的bank_tbs中。顺着思路想,可以改一下数据库的默认表空间users,只要保证不存在users表空间,dmp中create table语句就不能根据tablesapce子句,插入对应的表空间,而是找用户默认的表空间。 除此之外,可以初始化就导入users表空间,然后拼接SQL语句,将对象可以move至其他表空间,当然这就需要两倍的空间。另外还可以收工改一下dmp文件中tablespace子句对应的表空间,但只适应于小容量文件。 这里有一些知识点值得关注, 1. unlimited tablespace权限,是为用户授予resource角色是自动添加的,但从安全性的角度来考虑,在创建用户并且授予resource角色之后应该回收unlimited tablespace这个系统权限,原因就是有了这个权限,用户可以在任意表空间中创建对象,就有可能恶意占领系统表空间,影响数据库的正常运行。 2. Oracle 9i以前,数据库默认用户的表空间是SYSTEM,这是极为不合理的,因为SYSTEM存储的是数据库重要的底层数据字典信息,如果无限制地存储用户数据,极有可能影响数据库的运行。从9i开始,默认表空间则变为了USERS,建库的时候会默认创建。 使用如下语句,可以查询当前系统默认表空间, select property_value from database_properties where property_name = 'DEFAULT_PERMANENT_TABLESPACE'; 使用如下语句,可以改下当前数据库默认的用户表空间以及临时表空间, alter database default [temporary] tablespace tablespace_name; 问题2:数据库字符集 为了保证数据导出导入,不会出现乱码,字符集要尽量保持一致,可以使用如下语句检索当前数据库使用的字符集, select userenv('language') from dual; 例如返回结果是AMERICAN_AMERICA.ZHS16GBK。 若要检索当前操作系统字符集,可以使用, echo $NLS_LANG 例如返回结果是AMERICAN_AMERICA.AL32UTF8。 若要更新操作系统字符集,可以使用, export NLS_LANG=AMERICAN_AMERICA.ZHS16GBK 问题3:导入过程中的一些报错 报错1: Export file created by EXPORT:V09.02.00 via conventional pathIMP-00013: only a DBA can import a file exported by another DBAIMP-00000: Import terminated unsuccessfully 错误信息提示,只用DBA用户可以导入另一个DBA导出的文件。意思就是这个dmp文件,导出用户是有DBA角色的,因此导入使用的用户,必须要有DBA角色。 解决方法1:使用非DBA角色的用户,重新exp导出,再用非DBA用户imp导入。 解决方法2:使用DBA用户执行imp导入操作。 相比而言,生产系统一般会选择方案1,毕竟一般业务数据的属主,不会是一个DBA角色的用户,如果用方案2,则要求目标端用户需要DBA角色,未来要是再有导出导入需求,还是需要DBA角色,无休无止了。 报错2: Export file created by EXPORT:V09.02.00 via conventional pathimport done in ZHS16GBK character set and AL16UTF16 NCHAR character setIMP-00031: Must specify FULL=Y or provide FROMUSER/TOUSER or TABLES argumentsIMP-00000: Import terminated unsuccessfully 此时执行imp可以指定full=y,或者使用fromuser和touser参数,例如, imp user/user file=... log=... fromuser=user touser=user 明确导出和导入的用户名称。 问题4:创建视图报错 导入日志中显示,创建视图的时候报错了, ORA-01031: insufficient privileges 原因就是为用户授予resource和connect常规角色,并不会自动授予创建视图的权限,具体可以参考(http://blog.csdn.net/bisal/article/details/31735185),此时可以授予, SQL> grant createany view to user; Grant succeeded. 再次导入,即可以正常完成了。 对于测试数据迁移,其实还有一点,就是是不是所有数据,都需要迁移?因为往往测试库中有一些,仅临时使用的表对象等信息,如果执行前,筛选一下真正需要的数据,再开始执行导出导入,可能只需要迁移小部分数据,对于垃圾数据就可以直接忽略,这就是人们常说优化的极致,即不做任何事。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
前段时间,家里原来的路由器,总是时不时掉线,因为网络环境并未有变化,所以打算直接换一个新的,于是乎开始广撒网,找应该买什么样的路由器,隔行如隔山,不看不知道,一看吓一跳,虽然以前学过CCNA,但现在看来,还是皮毛,更何况忘的干净,原来网络设备的世界,如此宏伟,不是想象的那么简单。 我主要参考了知乎上的一些文章,总结了一些和网络、路由器相关的要点,版权还是要归属原作者。 例如:https://www.zhihu.com/question/48860976 1. 带宽容量的单位 通常运营商号称我们是XXX带宽,其实真正用起来,并不是这样,利用了换算方法屏蔽了人们的认识,例如宽带100M指的是带宽,这里M跟着的是"b",即Mb=Mbit,但实际下载速度用的是字节Byte,1个Byte=8个bit位,即1MB=8Mbit,因此100M光纤的速率是100Mb/8=12.5MB,当然这还是理论峰值,换句话说,一般可能到不了。 2. 上行速度和下行速度 民用的宽带,一般上行速度和下行速度,是有区别的,例如100M的电信光纤,家用的话,下行带宽是100M,但是上行只有8M,即上行速度是1M,如果是8M的宽带上行顶多就1M,即上行速度是128KB/s。简单讲上行速度,就是只发出数据的速度,即上传速度,例如QQ或微信视频聊天,下行速度,就是指收到数据的速度,即下载速度。 3. 什么叫路由器高速双频? 从电商网站上看路由器,总是宣称自己是高速双频,什么叫高速双频? 其实,路由器的频段粗分为两个,2.4G频段和5G频段,单频的其实就指的是只支持2.4G的802.11b/g/n三种协议,双频又叫ac路由器指的是同时支持802.11bgn和802.11ac两种协议。 2.4G里的协议分为三代,各个频率的传输速度, 802.11b:理论最高11Mbps(1.375MB每秒钟这个的确可以做到) 802.11g:理论最高54Mbps(6.75MB每秒这个的确很虚了) 802.11n:理论最高600Mbps(能做到就见鬼了) 之所以一般达不到理论峰值,主要是因为2.4G频段的污染严重,比如蓝牙,手机4G信号,无线键鼠各种无人机遥控甚至连微波炉,都会影响这个频段的使用,因此理论峰值,看看就行了。 5G的802.11ac,最高可以达到40-60MB/s,100M宽带可以轻松跑满。 4. 穿墙 从广告上我们还能看见,这么一项宣传,看清楚了是穿墙,不是穿越。 其实很容易理解,穿墙就是表示无线信号,隔着墙是不是能接收。能否穿墙,可能有几个因素,或者说几个疑问。 (1) 天线数量是否影响穿墙? 有人说“天线越多,穿墙性能越好“,这其实是扯淡。路由器里面有个叫做MIMO的技术(Multiple-Input Multiple-Output),指的是多入多出,比如一个路由器有2T2R,那么就是说可以支持同时两个设备在线上传下行数据并且互不干扰,缩写成2X2。一般情况下,天线的数量靠这个来设置是最靠谱的。一般情况下,150Mbps的路由器,一根天线足矣,300的两根,450的三根,如果出现了2.4G路由器出现了好多根线,其实一般都是两根三根并作一根用,说白了就是唬人。但如果是2.4G和5G并存的路由,那么,一般是有双数的天线,一半用来2.4G,一半用于5G,所以说,天线的多少只能证明其带宽数量等等,其他的没用。 (2) 功率是否会影响穿墙? 其实是有关系,但由于工信部把无线路由器的发射功率限制死了在100毫瓦,因此基本没有关系,100毫瓦是个什么概念?手机天线的发射功率都有800毫瓦,所以某些品牌说自己的路由器有穿墙模式,顶多就个噱头,当然也有些品牌会隐藏自己更改发射功率的能力,这个例外了。 总结来讲,对于无线路由器的选择,了解一些基础,不要盲目听从厂商的宣传,一般家用来说,百元左右的基本可以用了。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
前两天测试同学问了一个问题,表中某一个字段,需要改一下长度,对业务是否会有影响? 可能隐约之中,我们觉得没影响,但又好像有影响,究竟有何影响,我们从实验来看最科学。 首先建测试表,NAME字段是VARCHAR2(10),10个字节的字符串类型,表有256万数据。我们将其长度改为20,从执行时间看,只有20毫秒, 我们对上面的操作,做一下10046 trace,发现确实,首先使用LOCK以EXCLUSIVE模式锁定了TBL表, 接下来执行alter table修改操作, 从trace文件看,主要是针对一些数据字典表的操作,其中包含28次select,10次update,12次delete,可以想象一个改字段长度的操作,就有几十次SQL操作,但用时仅为毫秒级,效率可见一斑。 我们从alter table新增字段操作究竟有何影响?(下篇)的介绍,可以知道,EXCLUESIVE模式的锁,是最高级别的锁,Alter table,Drop table,Drop index,Truncate table这些常见的DDL操作,都会需要这种级别的锁,我们知道Oracle中select这种查询(不带for update)是不会有锁的,因此若表有EXCLUSIVE级别的锁时,仅允许select操作(不带for update),禁止其他类型的操作, 从锁的强弱看,EXCLUSIVE(exclusive,X)>SHARE ROW EXCLUSIVE(S/Row-X,SRX)>SHARE(Share,S)>ROW EXCLUSIVE(Row-X,RX)>ROW SHARE(Row-S,RS)。 最后,引述一篇博客的总结(http://blog.itpub.net/9252210/viewspace-626388/), 2级锁Row-S行共享(RS):共享表锁,sub share,锁有:Select for update,Lock For Update,Lock Row Share。 3级锁Row-X行独占(RX):用于行的修改,sub exclusive,锁有:Insert, Update, Delete, Lock Row Exclusive。 4级锁Share共享锁(S):阻止其他DML操作,share,锁有:Create Index, Lock Share,locked_mode为2,3,4不影响DML(insert,delete,update,select)操作, 但DDL(alter,drop等)操作会hang。 5级锁S/Row-X共享行独占(SRX):阻止其他事务操作,share/sub exclusive,锁有:Lock Share Row Exclusive,具体来讲有主外键约束时update / delete … ; 可能会产生4,5的锁。 6级锁exclusive 独占(X):独立访问使用,exclusive,锁有:Alter table, Drop table, Drop Index, Truncate table, Lock Exclusive。 因此,针对上面VARCHAR2(10)改为VARCHAR2(20)的操作,我们的结论是修改字段长度的操作是会阻碍其他非select操作,但是持续的时间很有限,几乎可以说是忽略不计,因为需要操作的是数据字典信息,并不是表自身,所以和要操作表的记录总量,没有任何关系。 无意之中,发现了另一个问题,将字段长度从VARCHAR2(20)改为VARCHAR2(10),用时比之前要久,540毫秒,几乎是之前的10倍, 我们看下他的trace,首先还是以EXCLUSIVE模式锁表, 接着执行alter table操作, 我们从下面的信息,看出了一些端倪, 以FIRST_ROWS优化器模式执行select操作,条件是字段NAME长度>10,因为现在是要将字段长度,从20改为10,就需要判断是否已存数据中,有违反长度的记录,如果有则禁止此操作,所以需要以全表扫描,来检索表中所有记录,rows是0,则继续执行其他操作,需要注意的是,他采用了FIRST_ROWS模式,会以最快的速度返回记录,因此执行时间还是可控的,从操作上来看,整个操作包含27次select,10次update,12次delete,其中判断LENGTH("NAME")>10的语句占用了几乎90%的SQL执行时间。 总结: 1. 若是增加长度的操作,会以EXCLUSIVE模式锁表,但其主要操作的是数据字典表,锁占用时间几乎可以忽略不计,所以几乎不会影响业务。 2. 若是缩短长度的操作,还会以EXCLUSIVE模式锁表,但需要以FIRST_ROWS优化器模式,执行全表扫描,判断已存数据是否有超长的记录,因此相比(1)执行时间会略久,但基本可控。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
一直有朋友问,是不是表建了索引,一定会使用索引,在RBO时代,访问效率会参考一些规则,优先级高的,认为效率就高,例如索引就比全表扫描效率高,但CBO时代,则会以成本为依据,谁的成本低,谁的效率就高,这样更科学。 建了索引,SQL却未使用索引,有很多情况,何况我不精通,所以不能一一枚举出来,但结合昨天广分一位兄弟的问题,列举出两个场景,提供一些思路和方法。 场景一:正确的有索引却不用 创建测试表,插入一条数据,创建索引,采集表和索引的统计信息,USER_TABLES视图显示有1条记录,平均行长为14字节。 执行update语句,条件是索引字段id,执行计划显示,对表的扫描,用全表扫描而不是索引扫描, 如果各位对索引的结构,比较了解的话,就比较容易理解其原因了,我们此处用的是BTree索引,即平衡二叉树索引,他的结构类似一棵树形,有根节点、分支节点,以及叶子结点,唯一索引和非唯一索引,叶子结点存储的信息会略有不同,我们此处建立的是非唯一索引,因此叶子结点中存储的,则是索引字段键值,以及对应的rowid,rowid是一个伪列,通过他可以快速定位,一条记录对应的物理位置,因为他的信息包括了,这条记录对应的文件号、块号、行号等信息,rowid的访问CBO时代他的优先级是最高的,关于rowid,内容其实还是很丰富的,有机会我们再聊。 再说索引结构,为什么说索引快,主要就是因为索引的查找,就是以这棵树的根节点开始,找分支节点,如果等值查询,则可以直接定位到具体的叶子结点,如果是范围查询,因为叶子结点是排序的,因此只要找出起始节点,按照叶子结点的指针,就可以找出对应结果集,无论何种用法,我们可以看出,他的执行路径都是有限的,根节点-分支节点-叶子结点,而且即使表的数据量再增加,只要索引数层级不变,其消耗的代价就是稳定的,而全表扫描,则会随着表数据量的增加,高水位不断上升,导致增加的成本消耗。 但一些情况下,索引扫描效率未必高,比如上面实验,因为要是SQL语句需要的数据,除了索引字段外,还有其他字段,则首先使用索引扫描,定位叶子结点,根据其中存储的rowid,回表找出对应的其他字段信息,而且若是INDEX RANGE SCAN这种索引范围扫描,会是单块读,而全表扫描则是多块读,相比之下,1次IO读的数据块数量就不同,对应的数据量就不同,效率就会不同,如果使用全表扫描,由于只有1条记录,则可以1次IO就完成数据读取。如果使用索引扫描,则先要消耗IO扫描索引,再回表消耗IO读取数据,成本高于全表。 虽然此处用了1条记录测试,有些极端,但即使有很多记录,还是需要综合考虑多块读、单块读、表的记录数、平均行长、回表等各种因素,只要TABLE ACCESS FULL的成本值低,无论是否有索引,都会选择TABLE ACCESS FULL。如果要用科学的数据,则可以做一个10053事件,就可以看出全表扫描和索引扫描两种方法对应的成本计算过程和结果,了解Oracle自己的选择。 场景二:错误的有索引却不用 我们接着插入10000条记录,但不执行统计信息更新,USER_TABLES视图显示表只有1条记录,可实际此时应该有10001条记录了。 我们执行comment表操作,让Oracle重新生成执行计划,但发现还是采用了全表扫描, 其实此处我们就可以看出问题,TABLE ACCESS FULL会扫描所有数据,但此处Rows值是1,说明Oracle认为表记录只有1条,自然TABLE ACCESS FULL是比较合适的选择,无可厚非。 接下来我们用一个11g推出的工具,STA(SQL Tuning Advisor),来看看此时Oracle可以给我们什么建议,首先创建任务,其中sql_id是我们执行update语句对应的sqlid, 接着执行report_tuning_task输出建议结果,请注意要是不设置开始的set,则可能结果显示为空, 内容如下,表示Oracle对这条SQL有两个建议, 第一个建议是,手工采集表和索引的统计信息,并且给出了SQL语句, 第二个建议,则是使用SQL Profile,固定执行计划, 并且给出了按照原始SQL,以及使用了SQL Profile的SQL,各执行10次的统计信息平均值数据,原始SQL用的TABLE ACCESS FULL, 使用SQL Profile的SQL,用的索引扫描INDEX RANGE SCAN, 可以看出,通过SQL Tuning Advisor,可以让Oracle来提供一些优化建议,并且直接给出了一些方法SQL,能辅助我们进行优化工作。 接下来针对实验问题,我们采用手工收集统计信息,再次执行,就会发现SQL用了索引范围扫描,相应地可以看10053事件,就会发现索引的成本,此时就会低于TABLE ACCESS FULL, 总结: 1. CBO时代,并不是有了索引,就一定会用索引,能不能用上,需要看谁的成本更低,影响成本值计算的因素很多,本文的问题,只有1条记录的时候,不用索引是对的,因为多块读的全表扫描,成本低于单块读的索引扫描(需要回表),但当有10001条记录的时候,不用索引就是错误的了,原因就是由于统计信息不准,造成Oracle计算成本值出现偏差,此时要么手工采集统计信息,要么使用SQL Profile固化执行计划,当然有索引但不用的场景,还有其他的因素,具体问题具体分析。 2. 像本文中,灌入大量数据,此时需要手工收集统计信息,才能保证Oracle估算成本值的正确,虽然Oracle有自动收集统计信息的job,但前提是要求这张表,当日的增删改数据量超过表总量的10%(参数可以调整),或者执行过truncate操作,可以参考dbsnake的书,而且每晚开始,因此之前这段时间其实是有可能使用了错误的执行计划,这就会有一些副作用。 3. SQL Tuning Advisor工具,可以让Oracle为我们优化SQL提出一些建议,自动化指出一些方向,还是比较有用的一种方法。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
用expdp导出生产库数据到测试库,执行impdp的时候报了ORA-02298错误,提示生效TBL_B表的外键约束FK_B_ID的时候出错, 看看ORA-02298的错误描述,因为存在独立的字节点记录,导致生效约束操作报错, 通俗一些,就是子表外键对应的主表主键/唯一约束键值不存在,所以此时无法生效外键约束。 方案1: 既然错误提示子表存在一些主表无记录的外键值,那么只要找出这些不符合主外键关系的子表记录,并且删除这些,保证子表中的外键记录,主表中均有对应的记录。 创建测试表和相应数据, 主表不存在id=2这条记录,但子表中存在外键字段id_a=2的这条记录,只是由于disable了约束所以才可以insert,但实际此时是无法enable约束,这和上面执行impdp的效果相同, 使用如下SQL,可以找出子表TBL_B中外键字段id_a的值未在主表TBL_A中有定义的记录,并且删除, 此时就可以正常enable约束。 使用如下SQL,可以根据子表名称和子表外键约束名称,自动拼接出需要删除子表非法数据的SQL语句,复制出来继续执行就行, SELECT ' delete from ' || a.table_name || ' a where not exists ( select 1 from ' || c_pk.table_name || ' b where b.' || b.column_name || '=a.' || a.column_name ||');'FROM user_cons_columns aJOIN user_constraints cON a.constraint_name = c.constraint_nameJOIN user_constraints c_pkON c.r_constraint_name = c_pk.constraint_nameJOIN user_cons_columns bON c_pk.constraint_name = b.constraint_nameWHERE c.constraint_type = 'R'AND upper(a.table_name) = upper('&Table_Name')AND upper(a.constraint_name) = upper('&FK_NAME'); 可以从我的GitHub上下载这一个SQL脚本, https://github.com/bisal-liu/oracle/blob/832c9c34c068981405a68bae55de885d78cf7bca/solve_illegal_constraint_data 方案2:出现错误的根本原因,是因为expdp导出的过程中,对于数据表是有DML操作的,即执行expdp指令导出的数据并不能确保属于同一个事务,要从根本解决这问题,就需要确保执行expdp的操作对应的数据属于同一个事务。 exp下可以使用consistent参数,默认值是N, CONSISTENT cross-table consistency(N) 使用consistent=y,则会设置set transaction read only,即使用了只读事务机制,保证exp导出数据属于一个事务了, 但其有一些弊端,例如由于需要读取回滚段中未提交的事务数据,因此exp表会变慢,同时官方文档列出了一些使用consistent=y的适用场景以及注意事项, expdp下可以使用flashback_scn和flashback_time参数,和闪回表类似,支持设置SCN和TIME两种, FLASHBACK_SCN介绍, FLASHBACK_TIME介绍, 总结: 1. 解决ORA-02289错误,要理解其本质,即子表外键值存在不属于主表主键/唯一约束键的情况。 2. 一种方法是手工删除子表中存在的非法数据,保证主子表关系正确。 3. 一种方法是保证导出的时候就要求数据属于同一事物,不受其他事务的影响,此时exp有consistent参数,expdp有flashback_scn和flashback_time参数可以支持此操作,而且需要清楚用这些参数的原理、弊端,以及适用场景。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
我们某一个系统的夜维出现了性能的问题,删除N张表,数据量从几万到几百万不等,现在需要3.5-4个小时,看了一下SQL AWR,有些采用了TABLE ACCESS FULL,而且是数据量百万级的表,并且一次删除5000条,批量要删除几百次,相当于执行几百次TABLE ACCESS FULL,效率可想而知。 大家讨论后,决定除了索引、SQL语句,从夜维程序逻辑上也要优化,提出了一些方案,目前正处于测试中。不过今儿先不说这事儿,这有个问题,就是测试库数据是模拟出来的,无论从字段含义,还是数据量,均不能和生产相比,因此为了验证,夜维程序优化的作用,需要将生产数据导入测试环境。 这个过程中,碰见了一些琐碎的问题,有些可能是常见的问题,记录于此, 1. 首先需要了解生产库数据。 生产库用户下数据存放于数据表空间TABLE_DAT中,目前使用量为25G左右,索引数据存放于索引表空间TABLE_IDX,目前使用量为10G左右。 2. 导出生产库数据。 使用expdp system/oracle schema=xxx directory=xxx dumpfile=xxx logfile=xxx,导出生产库xxx这个schema所有对象信息,dump文件17G(因为expdp数据泵会有压缩,因此不是25+10=35G),用时十几分钟。 3. 准备测试数据库。 为了满足生产库数据的需求,测试环境就至少需要35G的空间存放数据。找遍了手头上的资源,才找到一台异地机房的服务器,可用空间为50G左右,安装了Oracle 11.2.0.4,剩余42G左右。接着为了便于数据导入,创建了和生产环境一致的用户、数据表空间名称(26G),以及索引表空间名称(11G)。 问题来了,需要倒入的文件dump有17G,本地空间只有42-26-11=5G左右,不足以存放dump文件。 4. 寻找中间服务器。 最直接的方法,就是找一台中间服务器,作为中转,满足空间容量,执行导出导入。但找的服务器,发现和这台开发服务器,网络策略不通,因为我们生产库和测试库,属于两地机房,之间互访需要开通策略,现申请网络策略,需要一些时间,所以这一条路行不通。 5. 山穷水尽疑无路。 目前的问题,测试库服务器,磁盘空间只有5G,但一个文件dump就需要17G,而且这台服务器,由于一些原因,不能加新的磁盘容量。那么如何增加存储空间? 6. 柳暗花明又一村。 前两天为了学习RAC,特意看了下NFS,因为我这台服务器,属于同网段的还有几台,虽然容量相近(不能满足数据库+数据文件+dump的空间需求),但足以存放17G文件,我来搭一个NFS,dump文件放其中,让这台数据库服务器,可以访问这个dump,是不是就可以了? 编辑用于提供存储的服务器exports, [root@RAC1 DATA]# vi /etc/exports /shared_disk 10.x.x.x(sync,rw) 启动NFS服务, [root@RAC1 DATA]# service nfs start Starting NFS services: [ OK ] Starting NFS quotas: [ OK ] Starting NFS mountd: [ OK ] Starting NFS daemon: [ OK ] Starting RPC idmapd: [ OK ] 查看信息, [root@RAC1 DATA]# showmount -e 10.221.x.x Export list for 10.221.x.x /shared_disk 10.x.x.x 这台测试数据库服务器,执行挂载, [root@RAC2 /]# mount -t nfs 10.x.x.x:/shared_disk /u01 此时执行df -h就可以看出,有一个10.x.x.x:/shared_disk /u0挂载点了。 7. 执行导入操作。 首先测试服务器,创建用于导入的目录结构, create directory dump_dir as '/home/oracle'; grant read,write on directory 执行impdp, impdp xxx/xxx directory=dump_dir dumpfile=xxx logfile=xxx 8. 异常操作。 由于临时忘了是不是用户名正确,我执行了ctrl+c强行终止了impdp进程,当我再次执行impdp的时候则提示我表已经存在,需要使用TABLE_EXISTS_ACTION参数,检索数据库确实有了数据,且数据量正确。 奇怪,我刚才是终止了这一次impdp操作,怎么会有数据了? 9. 那人却在灯火阑珊处。 Oracle 11g下的impdp/expdp数据泵的执行,操作系统层面是执行了impdp/expdp,启了一进程,但实际后台是启动了一个job,因此只kill这个操作系统的进程,并未从数据库层kill这个job,所以job继续执行。 正在运行的job,可以从这张视图了解, 正在执行的impdp进程窗口,ctrl+c就可以进入impdp的交互界面,如下是一些参数,例如kill_job就可以删除此任务, 如果已经关闭了执行窗口,可以从上面是图中检索job名称,执行impdp命令加上attach=job_name,就可以进入这个job的命令行,继续操作了。 总结: 1. 为了解决空间不足的问题,采用NFS临时借用其他节点存储,算是一种方案。 2. 11g下的数据泵impdp/expdp,后台采用job执行,只是kill进程无法停止job,可以使用impdp/expdp命令行操作和管理job任务。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
今天测试过程中,同事提出了一个,看似诡异,实则很基础的问题,乍一看会被迷惑。 用实验来复现下这个问题, (1) 创建测试表,A表的id字段是主键,B表的id_a字段是外键,参考A表的id主键, (2) 应用有这么一个逻辑,一个事务中,先更新表A,再INSERT表B,其中表B的id_a字段值是来自于表A刚才操作的主键,模拟如下, 可以看出,更新表A的操作正常,但使用表A的主键值id=1,来INSERT表B的时候,报了FK_B_A外键完整性约束的错误。 明明A表有id=1的记录,并且更新UPDATE操作成功了,为什么用id_a=1来INSERT表B,提示了外键完整性约束错误,其含义就是无法从主表找出字表要INSERT的外键值id=1,两者相矛盾么? 此处为分割线,朋友们可以思考下,为什么会有这种问题? 使用log miner利器,挖掘下redo日志,发现这张表曾经做过rename操作, 此时检索下约束信息,表B的外键约束FK_B_A,即ID_A字段,参考引用的是约束PK_A, 记得没错的话,PK_A是表A的主键字段id,可实际上,约束PK_A是表A_BAK表的主键字段列id, 原因就是之前rename了表A为表A_BAK,虽然表名变了,但表上的约束名称未变,因此表B的外键参考的表名,从表A变为了表A_BAK。 如果此时删除A_BAK可以么? 报错的原因是因为有子表参考引用了这张表的唯一键/主键,和删除数据相同,必须从子表开始操作,关系干净了,然后才能操作主表。 啰嗦几句,这里使用了drop,其实11g下这些对象并为真正删除,而是放入了回收站, 可以看出,表B、表A_BAK以及表A_BAK的主键索引,这些对象名均被改写了。 约束名称也同时被改了, 不变的则是表的字段列, 如果不想存回收站了,直接删除,则可以使用purge属性。 总结: 1. 凡是有主外键约束的表,无论删除数据还是删除表,均需要从子表开始,所有子表清理干净了,才能继续操作主表数据。 2. 任何看似诡异的现象背后,都有他存在的原因,即哲学所说的“存在即合理”。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
之前介绍了CBC,就是cache buffer chains这个等待事件的影响,《缓解latch: cache buffers chains的案例》,解决逻辑读过高的SQL语句,是优化方向。为了更直观地说明这个问题,通过模拟实验,来了解下。 创建测试表,test表三个字段,分别是id1,id2和name,insert入100万行记录,其中id1每个distinct值100次,id2针对每个id1的distinct值,其是唯一的,namedbms_random则是取随机数, test表总计为127MB, 新建test表id1字段,为非唯一单键值索引,收集表的统计信息,cascade=true,这张表每行平均占用字节108个, 根据id1=1、id2=746,以及name的取值执行SQL, 执行计划中,E-Rows和A-Rows一样,第一步是根据id1索引检索符合条件的rowid键值,根据数据特征,会返回1万条记录,需要回表,根据id2和name字段,过滤检索,返回符合条件的1条记录。相应内存消耗181, 删除原索引,新建id1和id2的复合索引, 执行同一条SQL,E-Rows和A-Rows一样,第一步会根据id1和id2的复合索引,检索出1条记录的rowid,第二步回表检索这个rowid,对应的数据,根据name过滤条件,返回检索。相应内存消耗5, 内存消耗之所以下降了,就是因为从数据特征看,id1、id2和name条件检索结果只会是1条记录,但id1索引会返回1万条数据,在此基础上,做过滤处理,相当于要将1万条数据加载至buffer cache,108bytes(单行平均长度)*10000条=1MB,即一次执行需要1MB的数据内存空间,而id1和id2的复合索引,从索引扫描阶段,就只会返回唯一一条记录,根据name过滤处理,需要的数据空间为108bytes(单行平均长度)*1条=108bytes,即一次执行需要108字节数据内存空间。 我们从AWR看下两种索引的区别, (1) id1这个单键值非唯一索引,两个session,并发总计执行20万次select操作,基本需要10分钟所有的时间,逻辑读消耗5万次/秒,每次交易消耗逻辑读21万, SQL ordered by Gets中则显示这条SQL逻辑读消耗,排名第一,每次执行消耗179次逻辑读,执行20万次,总计消耗3千5百万次逻辑读, (2) id1和id2复合索引,两个session并发执行总计40万次select操作,基本每20万次执行之需要1-2秒,逻辑读消耗1万9千/秒,每次交易消耗逻辑读2万次, SQL ordered by Gets中则显示这条SQL逻辑读消耗,排名第一,但每次执行仅消耗4次逻辑读,执行40万次,总计消耗160万次逻辑读,执行次数翻了一倍了,但逻辑读消耗仅为原来的1/21,自然支持的TPS就会提升了, 总结: 1. CBC等待事件,结合Load Profile中的逻辑读部分,往往需要关注SQL ordered by Gets中排名靠前的SQL语句,如何使这些SQL语句逻辑读降低,是优化方向。 2. 可能需要分析SQL执行计划,看是否实际执行,加载了本不需要的内存,索引设置是否合理了。 3. AWR、SQL AWR这些工具,均是我们的利器,要充分利用这些。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
今天学习和介绍一个有用的工具,来自TOM大神的show_space,其实这就是一个存储过程,用他可以统计一些段的用度,非常方便,网上流传着不同的版本。 首先我们看下原版的脚本,https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:5350053031470 create or replace procedure show_space ( p_segname in varchar2, p_owner in varchar2 default user, p_type in varchar2 default 'TABLE', p_partition in varchar2 default NULL ) authid current_user as l_free_blks number; l_total_blocks number; l_total_bytes number; l_unused_blocks number; l_unused_bytes number; l_LastUsedExtFileId number; l_LastUsedExtBlockId number; l_LAST_USED_BLOCK number; procedure p( p_label in varchar2, p_num in number ) is begin dbms_output.put_line( rpad(p_label,40,'.') || p_num ); end; begin for x in ( select tablespace_name from dba_tablespaces where tablespace_name = ( select tablespace_name from dba_segments where segment_type = p_type and segment_name = p_segname and SEGMENT_SPACE_MANAGEMENT <> 'AUTO' ) ) loop dbms_space.free_blocks ( segment_owner => p_owner, segment_name => p_segname, segment_type => p_type, partition_name => p_partition, freelist_group_id => 0, free_blks => l_free_blks ); end loop; dbms_space.unused_space ( segment_owner => p_owner, segment_name => p_segname, segment_type => p_type, partition_name => p_partition, total_blocks => l_total_blocks, total_bytes => l_total_bytes, unused_blocks => l_unused_blocks, unused_bytes => l_unused_bytes, LAST_USED_EXTENT_FILE_ID => l_LastUsedExtFileId, LAST_USED_EXTENT_BLOCK_ID => l_LastUsedExtBlockId, LAST_USED_BLOCK => l_LAST_USED_BLOCK ); p( 'Free Blocks', l_free_blks ); p( 'Total Blocks', l_total_blocks ); p( 'Total Bytes', l_total_bytes ); p( 'Total MBytes', trunc(l_total_bytes/1024/1024) ); p( 'Unused Blocks', l_unused_blocks ); p( 'Unused Bytes', l_unused_bytes ); p( 'Last Used Ext FileId', l_LastUsedExtFileId ); p( 'Last Used Ext BlockId', l_LastUsedExtBlockId ); p( 'Last Used Block', l_LAST_USED_BLOCK ); end; / 从原版来看,根据dba_tablespaces、dba_segments检索出表空间的名称,使用dbms_space包的free_blocks和unused_space计算相应空间,格式化输出,其中有一点需要注意,就是只会统计SEGMENT_SPACE_MANAGEMENT <> 'AUTO'的表空间,即MANUAL手工管理的表空间。 这个脚本最开始是2002年针对9i版本发布的帖子, 直到2014年,还有人跟帖,足以见其影响力, 我们用实验来体会一下,创建测试表, 执行存储过程, 删除数据, 空间不会释放, truncate数据, 此时表空间则被初始化, 我们看几个改良的版本, (1) 惜分飞版本, http://www.xifenfei.com/2011/09/tom%E7%9A%84show_space%E8%BF%87%E7%A8%8B%E4%BD%BF%E7%94%A8.html create or replace procedure show_space( p_segname_1 in varchar2,p_owner_1 in varchar2 default user,p_type_1 in varchar2 default 'TABLE',p_space in varchar2 default 'AUTO',p_analyzed in varchar2 default 'Y')asp_segname varchar2(100);p_type varchar2(10);p_owner varchar2(30); l_unformatted_blocks number;l_unformatted_bytes number;l_fs1_blocks number;l_fs1_bytes number;l_fs2_blocks number;l_fs2_bytes number;l_fs3_blocks number;l_fs3_bytes number;l_fs4_blocks number;l_fs4_bytes number;l_full_blocks number;l_full_bytes number; l_free_blks number;l_total_blocks number;l_total_bytes number;l_unused_blocks number;l_unused_bytes number;l_LastUsedExtFileId number;l_LastUsedExtBlockId number;l_LAST_USED_BLOCK number; procedure p( p_label in varchar2, p_num in number )isbegindbms_output.put_line( rpad(p_label,40,'.') ||p_num );end;beginp_segname := upper(p_segname_1); -- rainy changedp_owner := upper(p_owner_1);p_type := p_type_1; if (p_type_1 = 'i' or p_type_1 = 'I') then --rainy changedp_type := 'INDEX';end if; if (p_type_1 = 't' or p_type_1 = 'T') then --rainy changedp_type := 'TABLE';end if; if (p_type_1 = 'c' or p_type_1 = 'C') then --rainy changedp_type := 'CLUSTER';end if;dbms_space.unused_space( segment_owner => p_owner,segment_name => p_segname,segment_type => p_type,total_blocks => l_total_blocks,total_bytes => l_total_bytes,unused_blocks => l_unused_blocks,unused_bytes => l_unused_bytes,LAST_USED_EXTENT_FILE_ID => l_LastUsedExtFileId,LAST_USED_EXTENT_BLOCK_ID => l_LastUsedExtBlockId,LAST_USED_BLOCK => l_LAST_USED_BLOCK ); if p_space = 'MANUAL' or (p_space <> 'auto' and p_space <> 'AUTO') thendbms_space.free_blocks( segment_owner => p_owner,segment_name => p_segname,segment_type => p_type,freelist_group_id => 0,free_blks => l_free_blks ); p( 'Free Blocks', l_free_blks );end if; p( 'Total Blocks', l_total_blocks );p( 'Total Bytes', l_total_bytes );p( 'Unused Blocks', l_unused_blocks );p( 'Unused Bytes', l_unused_bytes );p( 'Last Used Ext FileId', l_LastUsedExtFileId );p( 'Last Used Ext BlockId', l_LastUsedExtBlockId );p( 'Last Used Block', l_LAST_USED_BLOCK );/*IF the segment is analyzed */if p_analyzed = 'Y' thendbms_space.space_usage(segment_owner => p_owner ,segment_name => p_segname ,segment_type => p_type ,unformatted_blocks => l_unformatted_blocks ,unformatted_bytes => l_unformatted_bytes,fs1_blocks => l_fs1_blocks,fs1_bytes => l_fs1_bytes ,fs2_blocks => l_fs2_blocks,fs2_bytes => l_fs2_bytes,fs3_blocks => l_fs3_blocks ,fs3_bytes => l_fs3_bytes,fs4_blocks => l_fs4_blocks,fs4_bytes => l_fs4_bytes,full_blocks => l_full_blocks,full_bytes => l_full_bytes);dbms_output.put_line(rpad(' ',50,'*'));dbms_output.put_line('The segment is analyzed');p( '0% -- 25% free space blocks', l_fs1_blocks);p( '0% -- 25% free space bytes', l_fs1_bytes);p( '25% -- 50% free space blocks', l_fs2_blocks);p( '25% -- 50% free space bytes', l_fs2_bytes);p( '50% -- 75% free space blocks', l_fs3_blocks);p( '50% -- 75% free space bytes', l_fs3_bytes);p( '75% -- 100% free space blocks', l_fs4_blocks);p( '75% -- 100% free space bytes', l_fs4_bytes);p( 'Unused Blocks', l_unformatted_blocks );p( 'Unused Bytes', l_unformatted_bytes );p( 'Total Blocks', l_full_blocks);p( 'Total bytes', l_full_bytes); end if; end;/ 其特点就是,可以接受AUTO类型管理的表空间,可以接受I、T、C作为索引、堆表和聚簇表的简写,如果段已被分析,则回显中会包含空闲空间块百分比,更直观展示表的使用情况, (2) Dave版本 http://blog.csdn.net/tianlesoftware/article/details/8151129 CREATE OR REPLACE PROCEDURE show_space ( p_segname_1 IN VARCHAR2, p_type_1 IN VARCHAR2 DEFAULT 'TABLE',p_space IN VARCHAR2 DEFAULT'MANUAL', p_analyzed IN VARCHAR2 DEFAULT 'N',p_partition_1 IN VARCHAR2 DEFAULTNULL, p_owner_1 IN VARCHAR2 DEFAULT USER) AUTHID CURRENT_USERAS p_segname VARCHAR2 (100); p_type VARCHAR2 (30); p_owner VARCHAR2 (30); p_partition VARCHAR2 (50); l_unformatted_blocks NUMBER; l_unformatted_bytes NUMBER; l_fs1_blocks NUMBER; l_fs1_bytes NUMBER; l_fs2_blocks NUMBER; l_fs2_bytes NUMBER; l_fs3_blocks NUMBER; l_fs3_bytes NUMBER; l_fs4_blocks NUMBER; l_fs4_bytes NUMBER; l_full_blocks NUMBER; l_full_bytes NUMBER; l_free_blks NUMBER; l_total_blocks NUMBER; l_total_bytes NUMBER; l_unused_blocks NUMBER; l_unused_bytes NUMBER; l_LastUsedExtFileId NUMBER; l_LastUsedExtBlockId NUMBER; l_LAST_USED_BLOCK NUMBER; PROCEDURE p (p_label IN VARCHAR2,p_num IN NUMBER) IS BEGIN DBMS_OUTPUT.put_line (RPAD(p_label, 40, '.') || p_num); END;BEGIN p_segname := UPPER (p_segname_1); p_owner := UPPER (p_owner_1); p_type := p_type_1; p_partition := UPPER(p_partition_1); IF (p_type_1 = 'i' OR p_type_1 ='I') THEN p_type := 'INDEX'; END IF; IF (p_type_1 = 't' OR p_type_1 ='T') THEN p_type := 'TABLE'; END IF; IF (p_type_1 = 'tp' OR p_type_1 ='TP') THEN p_type := 'TABLE PARTITION'; END IF; IF (p_type_1 = 'ip' OR p_type_1 = 'IP') THEN p_type := 'INDEX PARTITION'; END IF; IF (p_type_1 = 'c' OR p_type_1 ='C') THEN p_type := 'CLUSTER'; END IF; DBMS_SPACE.UNUSED_SPACE ( segment_owner => p_owner, segment_name => p_segname, segment_type => p_type, partition_name => p_partition, total_blocks => l_total_blocks, total_bytes => l_total_bytes, unused_blocks => l_unused_blocks, unused_bytes => l_unused_bytes, LAST_USED_EXTENT_FILE_ID => l_LastUsedExtFileId, LAST_USED_EXTENT_BLOCK_ID => l_LastUsedExtBlockId, LAST_USED_BLOCK => l_LAST_USED_BLOCK); IF p_space = 'MANUAL' OR (p_space<> 'auto' AND p_space <> 'AUTO') THEN DBMS_SPACE.FREE_BLOCKS (segment_owner => p_owner, segment_name =>p_segname, segment_type => p_type, partition_name =>p_partition, freelist_group_id => 0, free_blks =>l_free_blks); p ('Free Blocks', l_free_blks); END IF; p ('Total Blocks',l_total_blocks); p ('Total Bytes', l_total_bytes); p ('Unused Blocks',l_unused_blocks); p ('Unused Bytes',l_unused_bytes); p ('Last Used Ext FileId',l_LastUsedExtFileId); p ('Last Used Ext BlockId', l_LastUsedExtBlockId); p ('Last Used Block',l_LAST_USED_BLOCK); /*IF the segment is analyzed */ IF p_analyzed = 'Y' THEN DBMS_SPACE.SPACE_USAGE(segment_owner => p_owner, segment_name => p_segname, segment_type => p_type, partition_name =>p_partition, unformatted_blocks => l_unformatted_blocks, unformatted_bytes =>l_unformatted_bytes, fs1_blocks =>l_fs1_blocks, fs1_bytes =>l_fs1_bytes, fs2_blocks =>l_fs2_blocks, fs2_bytes => l_fs2_bytes, fs3_blocks =>l_fs3_blocks, fs3_bytes =>l_fs3_bytes, fs4_blocks =>l_fs4_blocks, fs4_bytes => l_fs4_bytes, full_blocks =>l_full_blocks, full_bytes =>l_full_bytes); DBMS_OUTPUT.put_line (RPAD ('', 50, '*')); DBMS_OUTPUT.put_line ('Thesegment is analyzed'); p ('0% -- 25% free spaceblocks', l_fs1_blocks); p ('0% -- 25% free spacebytes', l_fs1_bytes); p ('25% -- 50% free spaceblocks', l_fs2_blocks); p ('25% -- 50% free spacebytes', l_fs2_bytes); p ('50% -- 75% free spaceblocks', l_fs3_blocks); p ('50% -- 75% free spacebytes', l_fs3_bytes); p ('75% -- 100% free spaceblocks', l_fs4_blocks); p ('75% -- 100% free spacebytes', l_fs4_bytes); p ('Unused Blocks', l_unformatted_blocks); p ('Unused Bytes',l_unformatted_bytes); p ('Total Blocks',l_full_blocks); p ('Total bytes',l_full_bytes); END IF;END;/ 这个版本是(1)的基础上支持了分区,以及接受IP和TP作为索引分区和表分区的简写。 另外,以下文章中有类似show_space的改良版,但逻辑原理基本一致,可以根据自己的需求,选择适合自己的一个版本, http://blog.csdn.net/indexman/article/details/47207987 http://blog.csdn.net/huang_xw/article/details/7015349 总结: 1. show_space这个存储过程可以方便我们统计表/索引/聚簇表等段的使用情况,不用写一些SQL来实现此目标。 2. show_space有不同版本,有的支持分区,有的支持各种不同段的简写,有的支持细粒度的统计,根据自己的需求,选择一款适合自己的版本,了解其中的实现原理,将工具的设计思想,据为己用,触类旁通,甚至可以进行一些改造。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
昨天类总在微信公众号,给我留言, 这是2014年写的一篇文章(http://blog.csdn.net/bisal/article/details/18910785#reply),看了一下,当时的实验和说明是, SQL> exec dbms_stats.gather_table_stats(ownname=>'SYS', tabname=>'T2');PL/SQL procedure successfully completed. 查询dba_tables表,看到NUM_ROWS值是11218,说明此处采样比例是100%。 这里必须纠正,我的说法有误,不能因为从dba_tables中看见了NUM_ROWS值和表实际记录数相同,就认为默认采样比例就是100%。崔老师书中说了,11g默认值是DBMS_STATS.AUTO_SAMPLE_SIZE。 从官方文档看,gather_table_stats的estimate_percent参数,取值范围是[0.000001,100]默认值是DBMS_STATS.AUTO_SAMPLE_SIZE, AUTO_SAMPLE_SIZE是一个NUMBER类型的常量,默认值是0,表示采用自动采样算法, 问题来了,AUTO_SAMPLE_SIZE下Oracle采用的采样比例究竟是什么?究竟之前我所说的默认比例是100%,是否完全错误? (1) 9i和10g的描述,How to Gather Optimizer Statistics on 10g (文档 ID 605439.1) 指出, (1) 9i中ESTIMATE_PERCENT自动采样比例默认为100%。 (2) 10g中ESTIMATE_PERCENT自动采样比例默认为DBMS_STATS.AUTO_SAMPLE_SIZE,注意这指出该值非常小。 (3) 11g中ESTIMATE_PERCENT自动采样比例默认为DBMS_STATS.AUTO_SAMPLE_SIZE,注意这说的是一个相对larger的estimate percentage,一直到100%。 Note that on 11g, although using auto size for ESTIMATE_PERCENT tends to default to 100% ,because this is an auto sample, the engine may still decide to use a different sample size for tables and columns. This means that Column statistics could still be gathered with a small sample size and create a histogram that is missing key values. When ESTIMATE_PERCENT is set to a specific numeric value, that value will be used for both the table and columns. Additionally, even though a 100% sample is collected, the gathering process is really fast since a new hashing algorithm is used to compute the statistics rather than sorting (in 9i and 10g the "slow" part was typically the sorting). In 10g, support experience has shown that the default ESTIMATE_PERCENT sample size was extremely small which often resulted in poor statistics and is therefore not recommended. 这段描述说明,11g中ESTIMATE_PERCENT使用AUTO,会倾向于默认100%采样,由数据库引擎决定表和列,采样不同的采样比例。如果ESTIMATE_PERCENT设置了具体数值,则该值会应用于表和列。尽管100%采样,采集过程也会非常迅速,因为Oracle采用了一种新的HASH算法来计算统计信息,而不会像9i和10g中采用排序的方法,会显得非常slow。特别指出,10g中由于ESTIMATE_PERCENT默认值是一个非常非常小的数,通常会造成poor的统计信息,因此并不建议使用AUTO。 这篇文章则介绍了一些9i中ESTIMATE_PERCENT参数值设置的说明, Using DBMS_STATS.GATHER_TABLE_STATS With ESTIMATE_PERCENT Parameter Samples All Rows (文档 ID 223065.1) 指出ESTIMATE_PERCENT有一个上限,超过则会采样所有行数据。9.0.1版本之前,这个上限是25%,9.0.1版本则是15%,从9.2版本开始,采样比例则由用户指定了。 (2) 11g的描述,How to Gather Optimizer Statistics on 11g (文档 ID 749227.1) On 11g support suggests using the default DBMS_STATS.AUTO_SAMPLE_SIZE for ESTIMATE_PERCENT. This will generate estimate sample size of 100% for the table (if it is possible for this to fit within the maintenance window), even if that means that statistics are gathered on a reduced frequency. If this 100% sample is not feasible, then try using at least an estimate of 30%, however since 11g uses a hashing algorithm to compute the statistic, performance should be acceptable in most cases. Generally, the accuracy of the statistics overall outweighs the day to day changes in most applications. See below for notes regarding earlier versions and this setting. 11g建议使用DBMS_STATS.AUTO_SAMPLE_SIZE,维护窗口内,尽可能按照100%完成自动采样,若100%的方式不适合,则会at least采用30%的采样比例。并且强调了11g采用了HASH算法,计算统计信息,因此几乎在所有场景下,性能都不是问题。 对于默认值,和上面10g文档描述是一样的, In 11g, using auto size for ESTIMATE_PERCENT defaults to 100% and therefore is as accurate as possible for the table itself. In prior versions a 100% sample may have been impossible due to time collection constraints, however 11g implements a new hashing algorithm to compute the statistics rather than sorting (in 9i and 10g the "slow" part was typically the sorting) which significantly improves collection time and resource usage. Note that the column statistics are automatically decided and so a more variable sample may apply here. 11g中ESTIMATE_PERCENT设置AUTO,默认会采用100%的采样比例,因此对于表来说,统计信息会比较准确。之前的数据库版本中,限于采集时间,100%比例几乎不可能,然而11g使用了一种新的HASH算法,不会像之前9i和10g采用排序的方法,以前这种方法会增加采集的时间以及系统的资源消耗。 这篇文章中(How to Change Default Parameters for Gathering Statistics in Oracle 11g or Later (文档 ID 1493227.1))则介绍了如何修改统计信息收集中的默认值。 指出采样比例参数默认值是DBMS_STATS.AUTO_SAMPLE_SIZE,该参数可以设置为: (1) DBMS_STATS.AUTO_SAMPLE_SIZE (2) 从0.000001到100之间的有效值。 (3) NULL(会采用计算比例,100%) 如果没有显示使用NULL,则会使用默认值DBMS_STATS.AUTO_SAMPLE_SIZE,只有明确指出NULL,才会使用NULL的计算方法。 (3) 12的描述, 说明和之前的11g版本,并无区别,因此建议参考11g。 总结: 1. 9i中ESTIMATE_PERCENT默认100%。 2. 10g中ESTIMATE_PERCENT默认为DBMS_STATS.AUTO_SAMPLE_SIZE,一个非常非常小的数,通常会造成poor的统计信息,因此并不建议使用AUTO。 3. 11g中ESTIMATE_PERCENT默认为DBMS_STATS.AUTO_SAMPLE_SIZE,但由于其采用了一种新的HASH算法,即使倾向于默认100%采样,其性能要比9i和10g中更优,因此一般情况下,建议使用DBMS_STATS.AUTO_SAMPLE_SIZE,由Oracle来自主选择采样比例。 4. 我之前说的,默认采样比例是100%,其实是需要有一些前提条件的,从1-3可以看出,9i确实默认是100%,但10g肯定不是了,11g一般情况下是100%,但不能保证所有情况均为100%。 5. 任何说法,需要有理论和实践来论证,有时需要猜测,但一定是靠谱的猜测,并且可以证明。 6. 要感谢像类总这样的朋友们,对之前一些问题的疑问,我一直认为,我只是一个Oracle的爱好者,道行还很浅,相关的理论和实践知识,还很薄弱,有错误不怕,但我一直在努力中,所以欢迎朋友们指出各种问题,共同努力,共同进步! 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
今儿碰见一个略诡异的问题,source .bash_profile有一个警告, su - oracle有相同的警告, 提示/home/oracle是一个目录。 看一下profile文件, 没看出有什么不同,和另外一台正常的机器比较,看着是一样的,奇怪了? 仔细看用光标,发现了一些端倪, 结尾行有一个~符号,颜色和下面行的不同,另外这行可以使用光标达到,下面行是不能达到,难道是这个问题? 尝试删除黑色的~符号这行, 执行source,或者su,均不会报错了, 看来问题就是bash_profile中这一行中~产生的作用,可这是为什么? 既然因为bash_profile多了一行~,有这个错误,我们尝试使用~,显示的错误和之前一样, ~是什么意思?其实了解Linux系统的朋友们,肯定非常清楚,比如当前目录是/home/oracle, cd上一级路径, cd ~,回到了/home/oracle, 其实~表示的就是用户的家目录, 这就解释了为何bash_profile中,多一个~行,执行source或者su的时候,就会报/home/oracle是一个目录的错误,因为~表示用户的家目录,因此source执行会报错,su的时候由于会执行bash_profile,因此会报相同的错误。 总结: 1. 虽然这报错只是warning,并未影响什么,但作为一名程序员来说,我们不能视而不见,凡事有因果,根据错误信息,找出原因,并解决之,才是我们对待问题的态度,善于思考,不忽视细节,才能让我们得到锻炼和提高。 2. Linux中~符号表示的就是用户的家目录,因此执行cd ~可以回到家目录,单独执行~则会提示这只是一个目录,换言之不是一个可执行程序。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
这两天正在写一个日报的code,其中有一处是涉及系统用时,简单来看,就是俩时间戳字段相减,方法可能有很多,这里列出一些,朋友们要是有更简单、更好玩的方法,可以回复,一起玩耍。 创建测试表,t1和t2是TIMESTAMP类型,t1比t2快1分钟, 直接使用t1-t2得到正值,t2-t1得到负值, 使用substr截取字段前18位,得到的是“yyyy-mm-dd hh24:mi:ss“格式的日期,两个日期字段相减则可以得到粒度为秒的结果值,乘以1440(24*60)*60,换算为秒,乘以1000,换算为毫秒,相减值为60000毫秒, 使用substr截取20位开始的6位,得到TIMESTAMP的6位,相减(001811-000000), 另一种方法,可以使用extract函数,得到DAY、HOUR、MINUTE、SECOND各个部分, 按照DAY、HOUR、MINUTE、SECOND各自换算为毫秒,相加得到总的毫秒,相减得到用时,单位是毫秒,这有两种用法,一种是首先换算t1和t2的值为毫秒,再相减这两个值,另一种是直接从t1-t2执行extract函数,换算为毫秒,等价的两种方法, 这种用法的优点在于,相比上面to_date相减,会有毫秒的精度,因为to_date截取了秒这一级别,毫秒被忽略了,因此使用extract函数,可以得到更精确的用时。 总结: 1. 两个时间戳类型的字段,可以相减得到两者用时。若用to_date则会按照格式符,有一定的截断,精度会被忽略。若用extract提取,SECOND包含毫秒,因此毫秒的精度会被保存,得到的结果会更精确些。extract可以接受两个字段相减,作为参数。 2. 实践,才可能准确、理性地知道一些用法细节。 对于上面的需求,如果有朋友有其他更好的办法,欢迎回复,一起学习! 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
今天同事手一抖,误删除了一套测试环境中的所有sequence序列对象。序列不像表这种对象,drop删除可以从回收站中找回来,当然一般认为序列并不是那么重要,只要记得名称以及一些非默认参数,可以选择重建。唯一可能有问题的就是,一般序列常用于主键字段,如果之前的序列已经被用了,他的last_number可能就不是初始值了,换句话说,如果此时只是简单地重建,很有可能使用过程中,会出现seq.nextval的值之前已经用了主键,此时会报ORA-00001约束冲突的错误。 我觉得针对这个问题,有两种解决方法。 1. create sequence的时候设置minvalue为一个比较大的值,尽量可以超过之前使用的值。但这种方法掺杂蒙的成分,且不一定准确。 2. 是否可以找回之前删除的序列? 对于问题2,答案是可能找回,可能找不回。 1. 可能找回的实验 之所以可以找回,原理就是用闪回,准确说是闪回查询的方法,利用的就是UNDO表空间,如下可以检索dba_seqences视图中sysdate - 60/1440,即1小时之前的数据镜像, 之前我们说了,之所以不是直接重建,是因为我要知道删除之前,序列last_number用到什么了,因此拼接create sequence的时候可以将minvalue参数值设为last_number的实际值。同时可以指定序列属主,避免扰乱, 使用minus将当前和历史镜像相减,是另一种方法, sys.seq$亦可以检索序列,但需要使用OBJ#字段和dba_objects的OBJECT_ID字段关联才可以知道对应的序列名称, 无论用什么方法,得到SQL语句,就可以直接用来重建序列了。 2. 不可能找回的实验 这问题其实和闪回查询的原理有关,因为闪回查询使用的是UNDO表空间,因此回滚段是否包含指定删除时间的镜像,就成为了是否可以找回的关键,如果当前需要找回的数据已经从UNDO删除,则可能报错ORA-01555,说明已经从UNDO找不着前镜像了,因此无法执行SQL, 总结: 1. 使用闪回查询,可能找回删除的序列定义,进而可以知道last_number值,将其设为minvalue,就不会影响使用,相当于了断点续传的作用。 2. 若闪回查询使用的UNDO记录已被删除,此时查询报错ORA-01555,这种方法就不支持找回sequence了。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
有同事问一个问题, 一张非分区表,是否可以创建分区索引? 答案是可以,但分区索引的类型有限制。 MOS这篇文章给出了答案,以及一些例子,What Is The Global Partitioned Index On Non Partitioned Table? (文档 ID 1612359.1)。 依据文章中的示例,以下实验操作, 1. 创建测试表,TEST表有四条测试数据, 2. 首先创建全局哈希分区索引, 3. 创建全局范围分区索引, 3. 创建全局列表分区索引,报错ORA-14151,需要制定正确的分区方法, 4. 创建未加GLOBAL关键字的哈希分区索引,报错, 5. 创建未加GLOBAL关键字的范围分区索引,报错, 6. 创建增加LOCAL关键字的列表分区索引,报错, 报错为ORA-02158,提示需要使用有效的CREATE INDEX选项, 总结: 1. 非分区表可以创建分区索引。 2. 非分区表的分区索引必须是GLOBAL。 3. 非分区表的分区索引,可以是哈希全局分区索引、全局范围分区索引,但不可以是全局列表分区索引。 一句话“证明某一个功能是否可用,实践是检验真理的唯一标准”。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
最近测试同事有一个需求,搭建一套Tuxedo域,连接网关,通过其和其他域交互,搭建过程中配置正确,但域连接报错,模拟过程如下,假设本地域名称为LOCALDOM,网关域名称为GWDOM,主机操作系统是Solaris。 从LOCALDOM本地域执行pd发现无连接的域,connect显示无法连接网关, 使用co强制连接网关域,报错。网关执行pd,显示无法连接LOCALDOM, 本地域重启tmboot,ULOG报错, 找不着一个GWADMIN服务,但实际应该找的不是这服务,因为我们尝试配置这个服务, 重启应用,和预期一致,报了找不着这个可执行文件, 重新检索ULOG日志,提示连接本地域7777端口,Network error, LIBGWT_CAT-1243错误域连接IP、端口有问题, 本机执行netstat -anp | grep 7777,检索7777端口是否被占用,为空,原因是什么? 其实这块有经验的朋友,应该可以知道了,可能了解判断的方向了。 Solaris下的netstat指令,和Linux下的netstat指令,若干参数含义有一些区别,Linux下netstat的-p参数含义, -p, --programs display PID/Program name for sockets Solaris下netstat的-p参数含义, -p Displays the net to media mapping table. For IPv4, the address resolution table is displayed. See arp(1M). For IPv6, the neighbor cache is displayed. 这我们用netstat -anp实际需要看7777端口,目前是否被占用,可Solaris下未按照语义返回占用的进程,我们看下不用-p,Solaris执行返回, 说明7777端口已被占用,处于监听状态,进一步说明是由于本地域7777端口,被占用因而导致GWADM无法连接本地IP的7777端口,进而无法和网关建立连接。 解决方法就是将7777改为空闲端口,例如7000,需要改一下本机,以及网关的domain配置文件,此时本机执行pd,显示连接了网关,connect提示和网关的连接已打开, 网关执行pd,显示连接了LOCALDOM,connect提示和LOCALDOM连接已打开, 总结: Tuxedo的ULOG日志,大部分系统错误,可以有一些提示,虽然可能错误提示信息简略,但往往可以指明方向。 netstat指令很有用,可以帮助我们找出,端口是否被占用、以及什么进程占用的,可是Solaris和Linux下netstat的参数略有不同,使用man可以检索详细介绍,不一定要记住所有参数,但需要知道如何检索。判断问题的方向和方法是我需要提高的地方,根据错误提示,是否可以快速定位问题,是否可以了解具体指令、工具的一些区别和用法,快速解决问题,例如文中提到了ULOG,记录了网络错误,无法连接本地IP和端口,直觉上应该首先检索端口,看下是否被占用,用netstat指令,根据当前是Solaris,是否知道-p参数不能用,如果上述两步判断正确,发现问题和解决问题,就易如反掌了。 如果您觉得此篇文章对您有帮助,欢迎关注微信公众号:bisal的个人杂货铺,您的支持是对我最大的鼓励!共同学习,共同进步:)
前两天有一个开发库,报了ORA-01654的错误,提示的是SYS_IL000…$$的对象不能分配表空间了,首先这种SYS_IL的对象名称是系统默认为LOB大对象LOBINDEX设置的名称,其次开发人员检索dba_segments视图看这个LOB所属表的空间,似乎占比一般。 这次涉及的问题,就是如何计算包含LOB对象的表空间实际容量的方法,之所以是说实际容量,是因为仅检索表所占空间,并不能反映LOB的容量。 这篇文章《How to Compute the Size of a Table containing Outline CLOBs and BLOBs (文档 ID 118531.1)》,就介绍了计算方法,接下来的实验会以此为依据。 LOB是一种大对象类型,分为CLOB存储字符串类型,BLOB存储二进制类型。例如普通的VARCHAR2类型存储的字符串,容量不能超过4000字节,因此若是超过了,则可以用CLOB存储。LOB中的存储很奇特,若是4000字节以内,则和普通的VARCHAR2一样,若超过则会存储于和表不同的,一个特殊的LOB段中,并且会有一个INDEX段,因此简单来看,一个包含LOB的表,可能包含表段、LOB段和LOB INDEX段三个segment对象。但dba_segments中表的容量不包含LOB段的容量(CLOB/BLOB),因此包含LOB的表实际容量为, 实验: 1.创建测试表和数据 TEST_TABLE表包含了两个CLOB列(超过4000字节),INSERT了1000条记录。 2.检索user_segments视图, 了解TEST_TABLE包含了五个段,一个TEST_TABLE表段,两个LOBINDEX段,两个LOBSEGMENT段,且计算所有段容量为12845056字节。 3.检索user_lobs视图, 可知这两个LOBSEGMENT段属于具体的列。 4.使用文章中提供的脚本, dba_segments可以计算出表段容量。 dba_segments和dba_lobs联合计算出LOBSEGMENT容量。 dba_segments和dba_indexes根据INDEX类型为LOB计算出LOBINDEX容量。 提供用户名、表名参数, 容量为1284506,和(2)结果一致。 总结: 1.包含LOB对象的表,实际包含了表段、LOBSEGMENT和LOBINDEX三个对象类型,因此dba_segments仅检索表段则不是实际容量。 2.根据LOB包含的对象类型,可以根据dba_segments、dba_lobs和dba_indexes来计算实际容量。 欢迎关注我的个人微信公众号:bisal的个人杂货铺
上周,兄弟部门提出了一个问题, 描述如下, 开发库,对表X他们查询,或者DELETE的时候,经常出这个问题, 好像还与查询或者DELETE的数据量有关,是不是由于没建索引的原因,我查百度也没解决 select sum(bytes/1024/1024) sizeMB from dba_free_space z where z.tablespace_name=’XXX_DAT’ 为null 问题模拟: 我们看下ORA-01654是什么错误,相应的有一个ORA-01653错误, ORA-01653表示某个表空间中的表段不能分配新的分区了,ORA-01654表示某个表空间的索引段不能分配新的分区了,两者含义一致,表空间容量不足了,解决方法一致,增加新的数据文件到这个表空间,另外的方法就是resize原始表空间数据文件。 错误提示的问题比较明白了,但上面兄弟问的dba_free_space记录为何为空?继续模拟此问题。 创建测试表空间和表, 创建了1MB的表空间,表空间下创建了一张表。 检索初始表大小以及dba_free_space记录, 其中dba_free_space显示有0.875MB剩余(按此计算,使用0.125MB),dba_extents和dba_segments显示有0.0625MB使用。 这有一些题外话的问题, (1) 为何dba_extents和dba_segments显示和dba_free_space不同? 参考《Mismatch Between Free Space Reported from DBA_DATA_FILES - DBA_SEGMENTS and DBA_FREE_SPACE (文档 ID 416744.1)》 究其原因主要为 Locally managed tablespaces files contain space metadata blocks which do not show in DBA_FREE_SPACE, DBA_EXTENTS nor DBA_SEGMENTS. (2) 本实验使用的是11.2.0.4,按说有延迟段的特性,即表段尚未使用前,不会分配空间,为何此处分配了空间? 原因是延迟段特性不对SYS表空间有效,我这偷懒,用的sys,若此处使用非sys则显示为, 继续模拟实验,向TEST表INSERT了1999条记录,继续INSERT了10000条记录则报错,ORA-01653,提示表空间TBL_SMALL不能分配表段TEST, 此时检索dba_extents和dba_segments视图, 显示使用了0.9375MB的空间容量。 检索dba_free_space视图, 检索dba_free_space中的表空间,发现未有TBL_SMALL, 因此可知,表空间不能分配新的分区给表段(/索引段),则dba_free_space记录为空,因为未有free的空间可用了。 注意:若上面的INSERT语句第一次就执行where rownum<10000,会报ORA-01653的错误,但此时检索dba_free_space有记录,因为第一次执行报错,语句ROLLBACK,实际表空间未被占用,因此dba_free_space有空闲空间可用。 另外,《Using DBA_FREE_SPACE (文档 ID 121259.1)》提供了一系列是用脚本,可以了解表空间使用,(仅用于教学用途,Oracle不负责任) 总结: (1) ORA-01653/01654错误,基本可以判断由于表空间容量不能分配新的extent给表/索引段而导致的错误。解决方法就是新增数据文件/resize原有数据文件。 (2) dba_free_space显示了表空间可用容量,若此时表空间容量不足,则视图中无此表空间记录。dba_free_space和dba_segments/dba_extents的计算方式不同,因此取值可能会不同。 欢迎关注我的个人微信公众号:bisal的个人杂货铺
有一个应用,需要创建索引,创建索引一般有两种方法,一种是 CREATE INDEX ...; 一种是 CREATE INDEX ... ONLINE; 字面意思上看,一个是在线,一个是非在线,有什么不同? 1.语句执行时间的不同 创建测试表, 使用非在线创建索引,用时00.06秒, 使用在线方式创建索引,用时00.32秒, 表只有一条数据,ONLINE是非ONLINE用时的5倍以上了。 2.阻塞对象的不同 非在线方式创建索引期间,执行任何DML语句,会hang住,直至索引创建完, insert into tbl_index select * from tbl_index where rownum=1; 无响应直至索引创建完成 出现hang是现象,原理则是锁等待。 从V$LOCKED_OBJECT视图可以了解锁等待信息,使用DBA_OBJECTS视图可以知道,OBJECT_ID是18和168111代表的对象是什么, 在线方式创建索引期间,允许任何DML语句的执行,不会阻塞。但实际从V$LOCKED_OBJECT看,是有一些锁等待信息的, 167111知道是TBL_INDEX表,我们看下168114(此处截图问题请忽略,默认168141就是168114),他代表的对象是SYS_JOURNAL_168113,查看168113代表的对象则是我们创建TBL_INDEX表的索引IDX_TBL_INDEX_01, 我们看下SYS_JOURNAL_168113,他是一张表, 表有四个字段, 记录为空(此处截图问题请忽略,默认168112是168114), 表大小为0, SELECT SUM(bytes)/1024/1024 FROM dba_segments WHERE segment_name='SYS_JOURNAL_168113'; 3.执行逻辑的不同 我们对这两种方法执行10046,看下Oracle执行了什么, (1) 非在线方式的trace主要内容, 首先,我们看见了以SHARE NOWAIT模式LOCK了TBL_INDEX整张表, 向obj$、seg$、icol$、ind$这些数据字典中维护索引相关信息, 完成非唯一索引的创建, (2) 在线方式的trace主要内容, 首先,以ROW SHARE模式LOCK表TBL_INDEX,这是和非在线方式一点不同, 另外的不同,就是会创建一张叫”SYS_JOURNAL_92450”的表,索引创建用的是这张“临时表”,因此不会直接影响原表的DML语句, 这张表用完了,会被drop purge,因此回收站找不着痕迹, 删除一些con$、seg$数据字典的记录, 我们从这两种创建索引生成的trace文件大小也可以得出一些结论,online方式创建索引的trace文件大小是非online方式创建索引的trace文件大小的10倍,说明online方式创建索引要执行更多的工作,尽管不会影响原表的DML语句,因此用时要久一些, 总结: (1) online和非online方式创建索引,效果相同。 (2) online方式创建索引,由于使用了一张临时表,以ROW SHARE锁表,不会阻塞原表DML的语句,非online方式创建索引,则会以SHARE NOWAIT锁表,阻塞原表DML语句。 (3) 由于online方式创建索引,Oracle执行工作复杂,因此比非online方式创建索引用时要久。 (4) 一句话“不能什么便宜均占着”,要么选择可以快速创建索引的非online方式但创建期间会锁表阻塞DML语句,要么选择不会阻塞原表DML语句的online方式创建索引但用时较久。从实际来看,我理解,若小表选择任何一种均可,大表,尤其是生产系统,找不着非高峰时间,选择online更合理一些,若不关注是否影响DML操作,则两种方式均可以了。
我们有一个重要的旧系统,最近夜维出现了一些问题,夜间执行5小时未完成,为了不影响业务,只能早上高峰期之前,DBA手工kill夜维进程。 这一个夜维程序采用了PLSQL写的存储过程,通过数据库job定时启动执行。存储过程我很少使用,借着这次机会,补习了下,这个存储过程中的逻辑比较简单,依次删除若干张业务表,每张表删除的逻辑相同,为了便于说明,模拟了下删除一张表的逻辑,示例如下, TBL_CUSS表三个字段,第一个字段是NUMBER类型,第二个字段是VARCHAR2类型,第三个字段是DATE类型, 这个存储过程接受一个参数,表示删除几天前的数据,删除DELETE语句按照一个时间字段和这个参数比较得出符合条件的记录集,同时使用rownum限制每次执行DELETE-COMMIT事务的条数,循环执行,直至出现ORA-1403无记录的提示,退出此逻辑。这个存储过程最优的地方,是使用了批量提交,不是执行一次DELETE删除所有记录COMMIT,如果这么执行,可能会占用大量的UNDO回滚段,进而可能出现回滚段空间不足的报错,也可能出现ORA-1555的错误。毕竟UNDO中记录的是SQL语句的逆向,对于DELETE语句,逆向就是INSERT,即会存储删除的整条记录。 可能有朋友一眼就看出这个存储过程的逻辑有一些问题,比如对于这种批量删除,未使用游标,相当于每次要检索tbl_cuss表符合insert_time < trunc(SYSDATE)-:1条件的记录,可每次仅删除其中的rownum限制的条数,如果使用游标,检索只需要一次执行,不考虑是否有索引,执行语句次数的降低,可以带来性能的一定提升。 针对此问题,写了第二个存储过程, 接受删除天数的参数,使用了游标,执行一次SELECT,读取出的则是符合insert_time < trunc(SYSDATE)-:1条件的所有结果集记录的rowid信息,遍历游标的时候使用BULK批量的方式,设置了一次性执行的条数限制MAX_ROW_SIZE,并且删除语句是根据上面游标获取的rowid为条件进行的DELETE,如果各位了解rowid,可以知道他代表了这条记录的物理位置,通过换算可以得出这条记录存储于的文件、块和行上,即可以快速定位这条记录的物理位置,在RBO模式下,他的成本优先级是最优的,高于索引。 继续写了第三个存储过程, 这和第二个存储过程,基本一致,唯一不同就是第二个存储过程中使用了for循环,第三个存储过程则用forall循环。for循环会执行其中的每条SQL语句,forall则会将其中所有SQL批量发送SQL引擎执行。当然可能有其他的写法,比如使用游标,但不使用BULK,按照rowid删除,这种写法执行SQL语句的次数和结果集数据量一致,效率可能还不如原始procedure。 从原理上说,使用BULK比单条语句执行,减少PLSQL和SQL引擎之间的切换频率,也可以减少redo和undo的产生量。针对循环内执行的DELETE,适合于使用集合,放入forall。 这篇文章的实验说明了forall的一些适用场景, http://blog.csdn.net/renfengjun/article/details/8334733# delete和insert都可以从forall上面得到巨大的性能提升。但是对于update来说opcode没有相关操作,提升应该不会那么明显。 接下来我们会对这三个存储过程进行一些比对实验,通过一些数据,说明各自的适用场景,首先创建测试数据集,制造了1300万测试数据, 每天50万数据,一共26天, 第一个存储过程名称为clear_without_fetch。 第二个存储过程名称为clear_all_fetch。 第三个存储过程名称为clear_fetch。 实验有以下几类,(以下执行基本采用第二次执行的结果避免第一次刷缓存) (1) 一次性删除1万条记录,insert_time不是索引,删除两天的数据(即100万), clear_without_fetch用时01:16.31 clear_all_fetch用时00:40.50 clear_fetch用时00:21.73 clear_fetch胜出,clear_without_fetch最慢,说明TABLE ACCESS FULL下的SQL语句,一次性删除1万条记录,使用游标和BULK效率要高些,使用forall比for效率要高些。 (2) 一次性删除5万条记录,insert_time不是索引,删除两天的数据(即100万), clear_without_fetch用时00:26.98 clear_all_fetch用时00:39.80 clear_fetch用时00:22.24 clear_fetch胜出,但一次性删除5万条记录,TABLE ACCESS FULL下的SQL语句由于执行次数减小为原来的5倍,效率上有提升,clear_all_fetch基本一致,是因为无论什么方式,其执行SQL语句的次数,和结果集一致,即100万次SQL。 (3) 一次性删除100万条记录,insert_time不是索引,删除两天的数据(即100万), clear_without_fetch用时00:21.92 clear_all_fetch用时01:22.00 clear_fetch用时00:25.95 clear_without_fetch胜出,TABLE ACCESS FULL下的SQL语句执行一次,clear_fetch虽然仍是批量一次发送SQL,性能上的优势不很明显。 (4) 一次性删除1万条记录,insert_time是索引,删除两天的数据(即100万), clear_without_fetch用时00:31.01 clear_all_fetch用时01:09.02 clear_fetch用时00:37.39 clear_without_fetch胜出,索引扫描执行效率提升,相比TABLE ACCESS FULL要明显一些。 (5) 一次性删除5万条记录,insert_time是索引,删除两天的数据(即100万), clear_without_fetch用时00:35.35 clear_all_fetch用时01:03.26 clear_fetch用时00:37.40 clear_without_fetch胜出,clear_all_fetch和clear_fetch基本保持和1万一致。 (6) 一次性删除100万条记录,insert_time是索引,删除两天的数据(即100万), clear_without_fetch用时00:23.33 clear_all_fetch用时01:27.80 clear_fetch用时00:33.68 clear_without_fetch胜出,相比1万和5万,效率提升一些,我理解主要是SQL执行次数从100次(1万)->20次(5万)->1次(100万)。 上面的实验中,数据接近的未必是绝对,和环境因素(例如机器配置、数据质量等)可能有关,因此大体方向上可以参考,不同次的实验可能会略有不同,但方向应该比较接近,毕竟原理摆着。 如下是上面六个实验,三个存储过程SQL,各自执行的10046的trace,从中可以看出一些端倪, clear_without_fetch存储过程各场景trace, (1) 一次性删除1万条记录,insert_time不是索引,删除两天的数据(即100万), 可以看出由于使用了绑定变量,解析一次,由于循环逻辑的问题,执行了100+1次。 (2) 一次性删除5万条记录,insert_time不是索引,删除两天的数据(即100万), (3) 一次性删除100万条记录,insert_time不是索引,删除两天的数据(即100万), 从elapsed以及query可以比较(1)-(3)。 (4) 一次性删除1万条记录,insert_time是索引,删除两天的数据(即100万), (5) 一次性删除5万条记录,insert_time是索引,删除两天的数据(即100万), (6) 一次性删除100万条记录,insert_time是索引,删除两天的数据(即100万), 由于需要维护索引,相比TABLE ACCESS FULL,会有些消耗。 clear_all_fetch存储过程各场景trace, (1) 一次性删除1万条记录,insert_time不是索引,删除两天的数据(即100万), (2) 一次性删除5万条记录,insert_time不是索引,删除两天的数据(即100万), (3) 一次性删除100万条记录,insert_time不是索引,删除两天的数据(即100万), 无论(1)、(2)、(3),DELETE语句均执行了100万次,唯一的区别就是SELECT语句和执行的次数。 (4) 一次性删除1万条记录,insert_time是索引,删除两天的数据(即100万), (5) 一次性删除5万条记录,insert_time是索引,删除两天的数据(即100万), (6) 一次性删除100万条记录,insert_time是索引,删除两天的数据(即100万), 有索引和无索引相比,有一些需要维护索引的消耗。 clear_fetch存储过程各场景trace, (SELECT和clear_all_fetch存储过程相近,此处忽略) (1) 一次性删除1万条记录,insert_time不是索引,删除两天的数据(即100万), (2) 一次性删除5万条记录,insert_time不是索引,删除两天的数据(即100万), (3) 一次性删除100万条记录,insert_time不是索引,删除两天的数据(即100万), (4) 一次性删除1万条记录,insert_time是索引,删除两天的数据(即100万), (5) 一次性删除5万条记录,insert_time是索引,删除两天的数据(即100万), (6) 一次性删除100万条记录,insert_time是索引,删除两天的数据(即100万), 可以看见clear_fetch和clear_all_fetch唯一区别就是DELETE语句执行次数,clear_fetch中执行次数和循环次数一样,说明是批量发送的,单条DELETE相同,但执行次数的不同,影响了资源消耗和执行时间。 从实验中可以得出的结论, (1) SQL使用TABLE ACCESS FULL的执行计划,若SQL执行次数较多时,则BULK+forall的方式,效率较高;若SQL执行次数较少时,很可能使用TABLE ACCESS FULL的执行计划的SQL,效率和BULK+forall接近,甚至有更优的可能。 (2) SQL使用INDEX RANGE SCAN的执行计划,效率会比BULK+forall略高,若SQL执行次数较少时,使用INDEX RANGE SCAN的执行计划的SQL,效率较高;SQL执行次数对于BULK+forall的方式基本一致。 (3) 无论是否用索引,BULK+forall的方式均优于BULK+for。可以使用索引,则用游标和不用游标,效率比较接近,从实验上看,不用游标反而可能略高一些,这和使用游标需要一些解析类的消耗可能有关,但游标可以带来便捷性,比如方便控制结果集,可以更灵活地编辑逻辑,既然效率比较接近,若时间均是可接受范围内,可以根据实际来考虑,选择什么方式。无论什么方式,大表数据的批量删除,这是首要原则。
这几天开发同学反映了一个问题,有一个Java写的夜维程序,用于每天定时删除历史过期数据,3月10日之前经过了内测,但这两天再次执行的时候,有一条SQL语句一直报ORA-01752的错误,由于近期做过一次开发库的迁移,从一个机房搬迁至另一个机房,而且开发同学确认这期间未变代码逻辑,所以怀疑是否和数据迁移有关,这个错误被测试同学提为了bug,卡在版本测试中,有可能造成进度延误,所以属于比较紧急的问题。 再来捋一下这问题的信息, (1).报错的SQL delete FROM (select * from TBL_A a inner join TBL_B b on a.a_id = b.id inner join TBL_C c on b.b_a = c.c_a and b.b_b = c.c_b where c.c_date <= trunc(sysdate)-1) where ROWNUM <= 10; (2).3月10日之前这条SQL可以执行,现在再执行就会报错, ORA-01752: cannot delete from view without exactly one key-preserved table (3).3月10日左右做过数据迁移,exp/imp导出导入数据。 首先,我们看下ORA-01752错误是什么意思, 01752, 00000, “cannot delete from view without exactly one key-preserved table” // *Cause: The deleted table had // - no key-preserved tables, // - more than one key-preserved table, or // - the key-preserved table was an unmerged view. // *Action: Redefine the view or delete it from the underlying base tables. 含义是不能从一个没有明确key-preserved表的视图中执行delete操作。 原因有三个,没有key-preserved表,多于一张key-preserved表,或者key-preserved表是一个非合并视图。 解决方法是重新定义视图,或者从基表中执行delete操作。 这解释比较懵,什么叫key-preserved表?为什么执行delete语句删除这些select多表关联形成的view需要有一个key-preserved表? 看看Oracle官方文档对key-preserved表是如何定义的, (http://docs.oracle.com/cd/E11882_01/server.112/e25494/views.htm#i1006318) key-preserved表是理解限制修改连接视图join view的基础。如果一张表的主键/唯一键是join连接结果集的主键/唯一键,那么这张表就叫做key-preserved表。因此key-preserved表的主键/唯一键在join连接过程中会被一直保留。 并非这张表的主键/唯一键一定要出现在select子句中,但若其出现在join连接的结果集中,则必须要满足作为这个结果集主键/唯一键的要求。 表的key-preserving属性不依赖于表中的实际数据。例如,如果emp表最多有一个雇员位于每一个部门,那么deptno字段在emp和dept的连接结果集中将会是一个唯一值,但是不会因为若存在这样的数据就定义dept是一张key-preserved表。 在这张视图中,emp是一张key-preserved表,因为empno是emp表的主键,也可以看出其是join连接结果集的主键。dept不是一张key-preserved表,因为尽管deptno是dept表中的主键,但他不是join连接结果集中的主键。 (http://docs.oracle.com/cd/E11882_01/server.112/e40540/schemaob.htm#CNCPT1514) 如果定义视图的FROM子句中有多张表或视图,那么这张视图就叫做join view连接视图。 可更新的join view连接视图,也叫做可修改的join view连接视图,包括两张或更多张基表或视图,允许执行DML操作。可更新视图的FROM子句中会有SELECT语句包含多张表,并且不会有WITH READ ONLY子句限制。 为了继承可更新,视图必须满足一些标准。例如,一条通用的规则就是,INSERT、UPDATE或DELETE操作一次只能影响一张基表。 USER_UPDATABLE_COLUMNS数据字典会返回上面创建的这张join view连接视图是可更新的,join view连接视图中所有可更新的列必须映射至key-preserved表的列上。key-preserved表是每行数据最多在结果集中出现一次的基表。department_id是departments表的主键,所以employees表中每行数据在结果集中最多只会出现一次,因此employees表是一张key-preserved表。departments表不是key-preserved表,因为其每行数据可能多次出现于结果集中。 简单用一个实验说明下, 实验一-假设emp表有以下数据, employee_id, department_id 1,1 2,1 3,2 假设dept表有以下数据,且department_name字段不是主键, department_id,department_name 1,'a' 1,'b' 2,'a' 那么,emp和dept组成的视图包含如下记录, employee_id,department_id,department_name 1,1,'a' 1,1,'b' 2,1,'a' 2,1,'b' 3,2,'a' 可以看出emp表每条数据是可能重复的,因此emp表不是key-preserved表。 实验二-假设emp表有如下数据, employee_id, department_id 1,1 2,1 3,2 假设dept表有如下数据,且department_name字段是主键, department_id,department_name 1,'a' 2,'a' 那么,emp和dept表组成的视图有如下记录, employee_id,department_id,department_name 1,1,'a' 2,1,'a' 3,2,'a' 可以看出emp表每条数据是不会重复的,因此emp表是一张key-preserved表。 (http://docs.oracle.com/cd/E11882_01/server.112/e41084/statements_8004.htm#SQLRF01504) A key-preserved table is one for which every primary key or unique key value in the base table is also unique in the join view. If you want a join view to be updatable, then all of the following conditions must be true: ● The DML statement must affect only one table underlying the join. ● For an INSERT statement, the view must not be created WITH CHECK OPTION, and all columns into which values are inserted must come from a key-preserved table. A key-preserved table is one for which every primary key or unique key value in the base table is also unique in the join view. ● For an UPDATE statement, the view must not be created WITH CHECK OPTION, and all columns updated must be extracted from a key-preserved table. ● For a DELETE statement, if the join results in more than one key-preserved table, then Oracle Database deletes from the first table named in the FROM clause, whether or not the view was created WITH CHECK OPTION. DELETE语句,如果join结果集中有多张key-preserved表,则Oracle会删除FROM子句第一张表,不管其视图是否用WITH CHECK OPTION创建。 DML语句必须仅会影响一张基表。 从以上文档,总结下主要观点,多表关联的一个updatable join view视图,如果语法上允许删除,则Oracle只会删除其中一张基表,这张表就是key-preserved表,如果一张表的主键/唯一键是updatable join view视图连接结果集的主键/唯一键,那么这张表就叫做key-preserved表。 用实验来说明, SQL> create table t_a (x number primary key); Table created. SQL> create table t_b (x number primary key); Table created. SQL> insert into t_a values(1); 1 row created. SQL> insert into t_a values(2); 1 row created. SQL> insert into t_b values(1); 1 row created. SQL> commit; Commit complete. SQL> select * from t_a; X ---------- 1 2 SQL> select * from t_b; X ---------- 1 SQL> delete from 2 (select a.x, b.x from t_a a, t_b b where a.x = b.x); 1 row deleted. SQL> select * from t_a; X ---------- 2 SQL> select * from t_b; X ---------- 1 此时可以执行删除语句,删除的是第一张表t_a的记录。 SQL> rollback; Rollback complete. SQL> delete from 2 (select a.x, b.x from t_b b, t_a a where a.x = b.x); 1 row deleted. SQL> select * from t_a; X ---------- 1 2 SQL> select * from t_b; no rows selected 此时删除语句可以执行,删除的是第一张表t_b的记录。 以上实验说明了, (1).不像ORA-01752错误提示中所说的,“more than one key-preserved table”会导致这种错误,delete可以删除两张不同的key-preserved表。 (2).delete会删除select … from子句跟着的第一张表,如上例中,t_a或t_b谁是第一张表,则谁会被删除。 实验和ORA-01752的描述自相矛盾了,Tom其实在这篇文章中提到了这个问题(https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:184812348071),指出这是从9i之后的DELETE行为,为此记录了bug, that is the behavior of DELETE since version 9i, I filed a documentation bug against that a while ago and it is updated in the current doc set: http://docs.oracle.com/docs/cd/E11882_01/server.112/e10595/views001.htm#ADMIN11786 这个链接已经失效了,但通过检索,其来源就是《Database Administrator’s Guide》的DELETE Statements and Join Views章节。 再看11.2、12.1和12.2三个版本这一章节的说明,叙述上略不同。 11.2 http://docs.oracle.com/cd/E11882_01/server.112/e25494/views.htm#ADMIN11781 You can delete from a join view provided there is one and only one key-preserved table in the join. 12.1 http://docs.oracle.com/database/121/ADMIN/views.htm#ADMIN-GUID-367FAA9B-269C-41AD-A429-631D144CF36F For most join views, a delete is successful only if there is one and only one key-preserved table in the join. 12.2 http://docs.oracle.com/database/122/ADMIN/managing-views-sequences-and-synonyms.htm#ADMIN11781 For most join views, a delete is successful only if there is one and only one key-preserved table in the join 11.2指出join连接中有且仅有一张key-preserved表,才能执行连接视图的delete删除。 12.1和12.2叙述相同,对于大多数连接视图,join连接中有且仅有一张key-preserved表,才能执行连接视图的delete删除。 如下是11.2的原文, HuangYong大师的一段解释很形象, “Indeed the documentation is not complete. It does talk about two key-preserved tables. There’s a Note box saying “If the DELETE statement uses the same column in its WHERE clause that was used to create the view as a join condition, then the delete operation can be successful when there are different key-preserved tables in the join.” But that doesn’t explain your case, which I still have trouble completely understanding. Here’s my thinking. The join condition in the in-line view, “a.a_id = b.id”, causes tbl_b to act as the parent and tbl_a as a child because tbl_b.id is PK. I know you didn’t explicitly define the foreign key on tbl_a, so I say “act as”. Then you have the join conditions “b.b_a = c.c_a and b.b_b = c.c_b”. But these don’t establish a parent-child relation between tbl_b and tbl_c because there’s no key involved. As soon as you create the unique key on (c_a,c_b) of tbl_c, tbl_c acts like the parent and tbl_b the child. Now the relationship among all 3 tables are clear: tbl_c acts like the parent of tbl_b, which acts like the parent of tbl_a. The bottom table (tbl_a) is the most detailed and is the key-preserved table.“ 这个bug则记录了此问题,“Bug 24921723 : DOCUMENTATION BUG - DELETE STATEMENTS AND JOIN VIEWS” bug进一步说明了这个问题,文档中应该反映出这种行为。 For the first point above, it is not true that you can delete from a join view provided there is one and only one key-preserved table in the join. The delete will work even though there are two different key preserved tables in the join. we can see that the delete is working even though there are two different key-preserved tables and its not necessarily same table should be repeated. The documentation should be changed to reflect this behavior. Again as said in point 1) this should be changed to reflect that the delete table operates on the first table in the list and the tables in the from list doesn’t necessarily to be same key-preserved table and it can be different tables. If the DELETE statement uses the same column in its WHERE clause that was used to create the view as a join condition, then the delete operation can be successful when there are different key-preserved tables in the join. In this case, the DELETE statement operates on the first table in the FROM list, and the tables in the FROM list can be different from the tables in the WHERE clause. 这个bug在11.2.0.3提出,12.1.0.2修复,就是在文档中增加了“For most join views”这一句,说明是有例外。 既然上面介绍了什么是ORA-01752错误,什么是key-preserved表,接下来我们模拟下开始的问题, (1) 创建三张测试表, SQL> create table tbl_a (id number primary key, a_id number); Table created. SQL> create table tbl_b (id number primary key, b_a varchar2(1), b_b varchar2(1)); Table created. SQL> create table tbl_c (id number primary key, c_a varchar2(1), c_b varchar2(1), insert_time timestamp); Table created. (2) 创建模拟数据, SQL> insert into tbl_a values(1, 1); 1 row created. SQL> insert into tbl_a values(2, 2); 1 row created. SQL> insert into tbl_b values(1, 'a', 'a'); 1 row created. SQL> insert into tbl_b values(2, 'b', 'b'); 1 row created. SQL> insert into tbl_b values(3, 'c', 'c'); 1 row created. SQL> insert into tbl_c values(1, 'a', 'a', sysdate-1); 1 row created. SQL> insert into tbl_c values(2, 'b', 'b', sysdate); 1 row created. SQL> insert into tbl_c values(3, 'c', 'c', sysdate); 1 row created. SQL> commit; Commit complete. SQL> select * from tbl_a; ID A_ID ---------- ---------- 1 1 2 2 SQL> select * from tbl_b; ID B B ---------- - - 1 a a 2 b b 3 c c SQL> select * from tbl_c; ID C C INSERT_TIME ---------- - - ----------------------------------------- 1 a a 27-MAR-17 05.11.58.000000 PM 2 b b 28-MAR-17 05.12.11.000000 PM 3 c c 28-MAR-17 05.12.27.000000 PM 单独执行select … from子句,可以知道会返回一条记录, SQL> select * from tbl_a a inner join tbl_b b on a.a_id = b.id inner join tbl_c c on b.b_a = c.c_a and b.b_b = c.c_b where c.insert_time <= trunc(sysdate); ID A_ID ID B B ID C C INSERT_TIME ---------- ---------- ---------- - - ---------- - - ------------ 1 1 1 a a 1 a a 27-MAR-17 05.11.58.000000 PM 执行DELETE语句会报错ORA-01752, SQL> delete from 2 (select * from tbl_a a inner join tbl_b b on a.a_id = b.id inner join tbl_c c 3 on b.b_a = c.c_a and b.b_b = c.c_b where c.insert_time <= trunc(sysdate)) 4 where rownum <=2; (select * from tbl_a a inner join tbl_b b on a.a_id = b.id inner join tbl_c c * ERROR at line 2: ORA-01752: cannot delete from view without exactly one key-preserved table 此时为表TBL_B增加唯一约束,执行DELETE语句,继续报错, SQL> alter table tbl_b add constraint unique_tbl_b_01 unique (b_a, b_b); Table altered. SQL> delete from 2 (select * from tbl_a a inner join tbl_b b on a.a_id = b.id inner join tbl_c c 3 on b.b_a = c.c_a and b.b_b = c.c_b where c.insert_time <= trunc(sysdate)) 4 where rownum <=2; (select * from tbl_a a inner join tbl_b b on a.a_id = b.id inner join tbl_c c * ERROR at line 2: ORA-01752: cannot delete from view without exactly one key-preserved table 此时为表TBL_C增加唯一约束,执行DELETE语句,可正常删除,并且可知删除的是表TBL_A数据, SQL> alter table tbl_c add constraint unique_tbl_c_01 unique (c_a, c_b); Table altered. SQL> delete from (select * from tbl_a a inner join tbl_b b on a.a_id = b.id inner join tbl_c c on b.b_a = c.c_a and b.b_b = c.c_b where c.insert_time <= trunc(sysdate)) where rownum <=2; 1 row deleted. SQL> select * from tbl_a; ID A_ID ---------- ---------- 2 2 SQL> select * from tbl_b; ID B B ---------- - - 1 a a 2 b b 3 c c SQL> select * from tbl_c; ID C C INSERT_TIME ---------- - - --------------------------------------------------------------------------- 1 a a 27-MAR-17 05.11.58.000000 PM 2 b b 28-MAR-17 05.12.11.000000 PM 3 c c 28-MAR-17 05.12.27.000000 PM 此时为表TBL_C增加唯一约束,不同的是唯一约束包含了id字段,执行DELETE语句(未包含id字段)报错ORA-01752,说明不能确定key-preserved表, SQL> alter table tbl_c add constraint unique_tbl_c_01 unique (c_a, c_b, id); Table altered. SQL> delete from (select * from tbl_a a inner join tbl_b b on a.a_id = b.id inner join tbl_c c on b.b_a = c.c_a and b.b_b = c.c_b where c.insert_time <= trunc(sysdate)) where rownum <=2; (select * from tbl_a a inner join tbl_b b on a.a_id = b.id inner join tbl_c c * ERROR at line 2: ORA-01752: cannot delete from view without exactly one key-preserved table 换一种方式,创建和上面DELETE相同语义的视图, SQL> create view v_abc as select a.a_id, b.id, b.b_a, b.b_b, c.c_a, c.c_b, c.insert_time from tbl_a a inner join tbl_b b on a.a_id = b.id inner join tbl_c c on b.b_a = c.c_a and b.b_b = c.c_b where c.insert_time <= trunc(sysdate); View created. 从user_updatable_columns视图中可以看出,原始视图所有列均不可增删改, SQL> select * from user_updatable_columns where table_name='V_ABC'; OWNER TABLE_NAME COLUMN_NAME UPD INS DEL BISAL V_ABC A_ID NO NO NO BISAL V_ABC ID NO NO NO BISAL V_ABC B_A NO NO NO BISAL V_ABC B_B NO NO NO BISAL V_ABC C_A NO NO NO BISAL V_ABC C_B NO NO NO BISAL V_ABC INSERT_TIME NO NO NO 7 rows selected. 增加TBL_C唯一约束,创建视图, SQL> alter table tbl_c drop constraint unique_tbl_c_01; Table altered. SQL> alter table tbl_c add constraint unique_tbl_c_01 unique(c_a, c_b); Table altered. SQL> create or replace view v_abc as select a.a_id, b.id, b.b_a, b.b_b, c.c_a, c.c_b, c.insert_time from tbl_a a inner join tbl_b b on a.a_id = b.id inner join tbl_c c on b.b_a = c.c_a and b.b_b = c.c_b where c.insert_time <= trunc(sysdate); View created. 此时user_updatable_columns视图中显示TBL_A表的a_id字段允许增删改了, SQL> select * from user_updatable_columns where table_name='V_ABC'; OWNER TABLE_NAME COLUMN_NAME UPD INS DEL BISAL V_ABC A_ID YES YES YES BISAL V_ABC ID NO NO NO BISAL V_ABC B_A NO NO NO BISAL V_ABC B_B NO NO NO BISAL V_ABC C_A NO NO NO BISAL V_ABC C_B NO NO NO BISAL V_ABC INSERT_TIME NO NO NO 7 rows selected. 通过以上实验,可以推测出这条DELETE语句是否执行成功,取决于TBL_B和TBL_C表是否有主键或者唯一键,进而取决于要删除的TBL_A表记录是否可以唯一确定结果集中的记录,是否是key-preserved表。 我们对开发库的每次变更,都是使用自己开发的一套数据库变更工具执行的,会自动记录变更历史,而且变更的细节,结合Confluence会有记录,TBL_C表3月10日之前,曾经创建过一个唯一约束,字段是(C_A, C_B),3月10日,开发提出了一次变更需求,向这个唯一约束中增加了另外一个字段C_C,此时(C_A, C_B, C_C)可以唯一确定一条记录,(C_A, C_B)不能唯一确定一条记录。 举个例子, SQL> select * from tbl_a; ID A_ID 1 1 2 2 SQL> select * from tbl_b; ID B B 1 a a 2 b b 3 c c SQL> select * from tbl_c; ID C C INSERT_TIME 1 a a 27-MAR-17 06.46.09.000000 PM 2 b b 27-MAR-17 06.46.09.000000 PM 3 c c 27-MAR-17 06.46.09.000000 PM 4 a a 27-MAR-17 06.46.09.000000 PM 5 a a 27-MAR-17 06.46.09.000000 PM SQL> select * from tbl_a a inner join tbl_b b on a.a_id = b.id inner join tbl_c c on b.b_a = c.c_a and b.b_b = c.c_b where c.insert_time <= trunc(sysdate); ID A_ID ID B B ID C C INSERT_TIME 1 1 1 a a 1 a a 27-MAR-17 06.46.09.000000 PM 2 2 2 b b 2 b b 27-MAR-17 06.46.09.000000 PM 1 1 1 a a 4 a a 27-MAR-17 06.46.09.000000 PM 1 1 1 a a 5 a a 27-MAR-17 06.46.09.000000 PM SQL> insert into tbl_a values(3, 1); SQL> select * from tbl_a a inner join tbl_b b on a.a_id = b.id inner join tbl_c c on b.b_a = c.c_a and b.b_b = c.c_b where c.insert_time <= trunc(sysdate); ID A_ID ID B B ID C C INSERT_TIME 1 1 1 a a 1 a a 27-MAR-17 06.46.09.000000 PM 3 1 1 a a 1 a a 27-MAR-17 06.46.09.000000 PM 2 2 2 b b 2 b b 27-MAR-17 06.46.09.000000 PM 1 1 1 a a 4 a a 27-MAR-17 06.46.09.000000 PM 3 1 1 a a 4 a a 27-MAR-17 06.46.09.000000 PM 1 1 1 a a 5 a a 27-MAR-17 06.46.09.000000 PM 3 1 1 a a 5 a a 27-MAR-17 06.46.09.000000 PM 7 rows selected. 此时不能根据TBL_A、TBL_B或TBL_C的主键来确定join连接结果集的主键,因此无key-preserved表,Oracle不能明确需要删除的基表,所以报错ORA-01752。 解决方案: (1). 为了删除TBL_A的数据,需要让其成为key-preserved表,因此除了TBL_B有主键限定唯一值,还需要让TBL_C的条件限定唯一值,例如可以再次修改唯一约束为(C_A,C_B)这两个字段,但这可能和业务需求不一致。 (2). 改写SQL, delete from (select * from tbl_a a where a.a_id in (select b.id from tbl_b b inner join tbl_c c on b.b_a = c.c_a and b.b_b = c.c_b where c.insert_time <= trunc(sysdate))) where rownum <= 2; 或者 delete from (select * from tbl_a a where exists (select 1 from t_b b inner join tbl_c c on b.b_a = c.c_a and b.b_b = c.c_b where c.insert_time <= trunc(sysdate))) where rownum <= 2; 至于使用IN还是EXISTS,需要结合子表可能返回的结果集记录大小再选择。 然而,开发同学提出了一种建议, 请将约束改为(C_A, C_B, C_C),SQL则改为 delete FROM (select * from TBL_A a inner join TBL_B b on a.a_id = b.id inner join TBL_C c on b.b_a = c.c_a and b.b_b = c.c_b and c.c_c is null where c.c_date <= trunc(sysdate)-1) where ROWNUM <= 10; 其中c_c列允许为空。 这条SQL报错ORA-01752,原因就是因为null是一个特殊的值,我们使用条件的时候,会用is null/is not null,不会用=null,换句话说,null和null不是等价的,因此允许这样的数据, create table tbl (a varchar2(1), b varchar2(1), c varchar2(1)); insert into tbl values('a', 'b', ''); insert into tbl values('a', 'b', ''); select * from tbl; a b a b 这种写法符合ORA-01752错误的范围。 总结: (1) ORA-01752错误从描述看会有些晦涩,主要是能理解key-preserved表的含义,才能逐步理解错误的原因。 (2) Oracle官方文档中任何一处细节的变化,可能蕴含着一些改进,只看文档是不能理解的,唯有实际操作才能理解含义。 (3) 认为对的就要坚持,即使是文档,辩证地看待问题。 要谢谢lastwinner和HuangYong两位大师的指点迷津,透过现象看本质,是这次学习到的一点经验。 欢迎关注我的个人微信公众号:bisal的个人杂货铺
使用示例数据库用户HR登陆需要打印执行计划等信息的时候,提示了错误, 提示很清楚了,PLUSTRACE角色未赋给HR。 查询HR所有的角色, 确实缺少PLUSTRACE角色, 通过oerr工具,可以进一步查看这个错误, AUTOTRACE Option in SQL*Plus (文档 ID 43214.1)中说明了要使用AUTOTRACE就必须有PLUSTRACE角色。 You can automatically get a report on the execution path used by the SQL optimizer and the statement execution statistics. The report is generated after successful SQL DML (Data Manipulation Language - that is, SELECT, DELETE, UPDATE and INSERT) statements. It is useful for monitoring and tuning the performance of these statements. You can control the report by setting the AUTOTRACE system variable. SET AUTOTRACE OFF - No AUTOTRACE report is generated. This is the default. SET AUTOTRACE ON EXPLAIN - The AUTOTRACE report shows only the optimizer execution path. SET AUTOTRACE ON STATISTICS - The AUTOTRACE report shows only the SQL statement execution statistics. SET AUTOTRACE ON - The AUTOTRACE report includes both the optimizer execution path and the SQL statement execution statistics. SET AUTOTRACE TRACEONLY - Like SET AUTOTRACE ON, but suppresses the printing of the user’s query output, if any. To use this feature, you must have the PLUSTRACE role granted to you and a PLAN_TABLE table created in your schema. For more information on the PLUSTRACE role and PLAN_TABLE table, see the AUTOTRACE variable of the SET command in Chapter 6 of the SQL*Plus Guide. 解决方法: 1.这个脚本会创建PLUSTRACE角色, $ORACLE_HOME/sqlplus/admin/plustrce.sql 其内容如下, -- -- Copyright (c) Oracle Corporation 1995, 2002. All Rights Reserved. -- -- NAME -- plustrce.sql -- -- DESCRIPTION -- Creates a role with access to Dynamic Performance Tables -- for the SQL*Plus SET AUTOTRACE ... STATISTICS command. -- After this script has been run, each user requiring access to -- the AUTOTRACE feature should be granted the PLUSTRACE role by -- the DBA. -- -- USAGE -- sqlplus "sys/knl_test7 as sysdba" @plustrce -- -- Catalog.sql must have been run before this file is run. -- This file must be run while connected to a DBA schema. set echo on drop role plustrace; create role plustrace; grant select on v_$sesstat to plustrace; grant select on v_$statname to plustrace; grant select on v_$mystat to plustrace; grant plustrace to dba with admin option; set echo off 创建了PLUSTRACE角色,将三张v_$的基表访问权限赋予PLUSTRACE角色,然后将PLUSTRACE授权DBA,并且有admin option属性。 2.执行脚本, 3.将PLUSTRACE授予HR, SQL> grant plustrace to hr; Grant succeeded. 4.再次执行SET AUTOTTRACE, SQL> show user USER is "HR" SQL> set autot trace 欢迎关注我的个人微信公众号:bisal的个人杂货铺
上次分享中曾使用了SYS_CONTEXT函数获取ip地址,但返回值为空,当时认为其是返回ipv6的地址,所以为空,但其实这是错误的结论。虽然是一个小小的知识点,但从中可以看出Oracle对于这种内置函数的考虑非常周到,我们先看如下是返回空的ip地址的一个示例, 我们首先看看SYS_CONTEXT函数的定义, 这个函数有两个入参,第一个值是命名空间,取值可以是“USERENV”或“SYS_SESSION_ROLES”,第二个值是一些列属性,例如我们使用的IP_ADDRESS, 含义是, IP address of the machine from which the client is connected. If the client and server are on the same machine and the connection uses IPv6 addressing, then ::1 is returned. 返回的是客户端连接的机器IP,如果客户端和服务器是同机,则连接会使用IPv6地址,::1会返回。 MOS这篇文章说明了这个问题, SYS_CONTEXT(‘USERENV’, ‘IP_ADDRESS’) Function Returns A Null Value (文档ID 470504.1) 主要意思就是如果客户端没用使用TCP协议(也就是使用tnsnames.ora定义的连接串)连接,不会返回IP地址, IP address is not available if client is not connecting through TCP (@SID). 可以看出使用了@SID,则返回了本机IP地址, 和SYS_CONTEXT函数有联系的还有这两篇文章, SYS_CONTEXT (‘USERENV’, ‘IP_ADDRESS’) Returns Hostname Rather Than Client IP (文档 ID 1267855.1) 若使用了11.2.0.x的监听器访问10.2.0.4,则返回的是主机名,不是IP地址。解决方案就是, 1) Use IP address and not hostname in the Oracle 11 listener address. 2) When using a hostname contain the listen protocol address by using (IP=FIRST), (IP=V4_ONLY) or (IP=V6_ONLY). Working listener address examples: (ADDRESS = (PROTOCOL = TCP)(HOST = hostname)(PORT = 1521)(IP=FIRST)) (ADDRESS = (PROTOCOL = TCP)(HOST = hostname)(PORT = 1521)(IP=V4_ONLY)) (ADDRESS = (PROTOCOL = TCP)(HOST = IP_address)(PORT = 1521)) 另一篇文章是, SYS_CONTEXT(‘USERENV’,’IP_ADDRESS’) Doesn’T Return Ipv4 Address When Ipv6 is Enabled (文档 ID 1175504.1) 使用11.1.0.7的监听器查询的时候,例如IPv4是10.16.120.165,则返回是”::ffff:10.16.120.165”,原因就是11.1版本不支持IPv6,无法做转换,解决方案是, 1) Disable IPV6 at OS level 2) Upgrade the database from 11.1.0.7.0 to 11.2.0.1.0 总结: Oracle中几乎所有细节,其实都有可能蕴含着一些特殊含义或用法,足以见其设计的精妙,唯有持续积累,才能收获更多。 欢迎关注我的个人微信公众号:bisal的个人杂货铺
这两天碰见一个比较紧急的生产问题,由于还在处理中,所以暂时不能给出整体描述,但其中涉及的一个问题就是删除一张大表中的过期历史数据,针对不同的类型的表可能有不同的解决方法,比如若是按照时间做的分区表,drop partition删除分区的操作可能是效率最快的、最简单的,若是一张普通表则需要有一些索引键值为删除条件,但需要注意的是最好做批量删除,且一次删除量不要太多,因为delete操作会将数据前镜像保存在UNDO回滚表空间,由于占用过多、事务过大、执行时间过长、UNDO空间过小等一系列问题存在,就有可能会影响正常的交易操作,这话题不是今天的主题。 删除历史数据可以使用存储过程,也可以写一个程序来做,区别是存储过程是直接在数据库中操作,少了客户端和数据库交互的环节,若是需要一些复杂的校验逻辑,可能写程序要更方便一些,但也不是绝对的,可能有人认为存储过程更好,无论什么方式,能解决问题才是最重要。 eygle大神曾经提供过一个用于批量删除数据的存储过程,在这引用下,版权还是eygle的:),(http://www.eygle.com/archives/2005/04/oracleoeouaeeae.html),强调的就是:分批删除,逐次提交。 create or replace procedure delBigTab ( p_TableName in varchar2, p_Condition in varchar2, p_Count in varchar2 ) as pragma autonomous_transaction; n_delete number:=0; begin while 1=1 loop EXECUTE IMMEDIATE 'delete from '||p_TableName||' where '||p_Condition||' and rownum <= :rn' USING p_Count; if SQL%NOTFOUND then exit; else n_delete:=n_delete + SQL%ROWCOUNT; end if; commit; end loop; commit; DBMS_OUTPUT.PUT_LINE('Finished!'); DBMS_OUTPUT.PUT_LINE('Totally '||to_char(n_delete)||' records deleted!'); end; / 这是一可以有参数输入的存储过程,分别是: p_TableName:待删除表的表名, p_Condition:删除条件, p_Count:一次删除的记录条数,rownum, 而且用了自治事务pragma autonomous_transaction,存储过程使用commit结尾。 整个逻辑很清晰和透彻,想必各位稍微看看都能明白。 这篇文章中(http://blog.csdn.net/xyjnzy/article/details/6194177)还介绍了另一种更精细的方法,判断日志是否已经归档了,避免数据删除快于日志归档的速度,如果发现尚未完成切换,则sleep一下,等待切换完成,再做下一次删除。 针对我这个需求,有一些可以改动的地方,由于这张表是一个按照NUMBER值做hash的哈希分区表,所以从效率上看,还可以精确至每个hash分区来做删除,这点是建荣给的建议,另外例子中自治事务我觉得也是可以不用的,因此针对SQL语句可以改为如下: delete from table partition (p1) where insert_time<sysdate and rownum <= :rn; 即指定分区名称(这可以作为另一个参数),然后可以通过手工执行,依次用rn=100、1000、5000、10000等几个值来选择从时间和删除量可接受的范围。总结一下, 1.如果使用存储过程,或许可以不用自治事务。 2.可以将partition作为另一个参数。 3.由于这张表数据量太大,即使使用索引条件做count(*)操作时间都很久,因此暂时未知符合条件需要删除的记录条数,因此需要根据测试和时间需求,明确rownum使用的可行条数,选择小值则可能循环次数要多,选择大值则可能循环次数少,总之务必要分批删除、批量提交,避免delete子句对UNDO表空间的过大影响。 以上只是提供了删除历史记录的一种存储过程操作的方法,以及针对我的需求做的一些改进,至于会采用何种方法,可能还会根据得到的信息,有其他需要改进的地方,可能还会使用程序的方法,可能会使用这种存储过程,待完成后会再做总结了。 欢迎关注我的个人微信公众号:bisal的个人杂货铺
我手上一份新的虚拟机环境是用Windows下的VMWare Workstation 12这版本做的,对应于Mac下的VMware Fushion我的版本是7.0,加载虚机后提示错误: 说明VMware Fushion 7.0版本不能匹配上VMWare Workstation 12版本。 网上搜了一下,针对VMWare Workstation 12,需要VMware Fushion 8.5版本。下载、安装再次启动可还是报错: 机锋网上这篇文章可以解决这个问题(http://bbs.feng.com/read-htm-tid-10881039.html), 1.打开终端; 2.执行命令:sudo rm -rf /System/Library/Extensions/vmmon.kext ,根据提示输入管理员密码; 3.执行命令:sudo cp -pR /Applications/VMware\ Fusion.app/Contents/Library/kexts/vmmon.kext /System/Library/Extensions/ 4.执行命令:sudo kextutil /System/Library/Extensions/vmmon.kext 如果出现执行报错,可以尝试先执行下面的命令: sudo kextunload /System/Library/Extensions/vmmon.kext 如果不出现Failed to unload com.vmware.kext.vmx86 - (libkern/kext) kext (kmod) start/stop routine failed,恭喜你可以执行第五步命令了,如果出现了,可以先尝试重启机器后再执行上述步骤。我尝试了第一次确实出现了这个报错,重启机器后再次执行就可以了。 加载、启动,一起正常了, 总结说一句,就是还是要支持正版:) 欢迎关注我的个人微信公众号:bisal的个人杂货铺
前几天由于单位断电了,导致一台BK*应用的开发数据库环境无法open打开,本以为借助于advise/repair failure就可以实现恢复,中间还是费了不少周折。 这算是自己第一次处理稍微比较复杂的恢复问题,以前碰见的问题都是比较常规简单的,对于备份恢复来说,一直想找一个时间更系统的学习一下,这个问题处理的过程当中才发现这些基础理论的重要性,因此一些处理步骤上还是比较混乱的,思路并不是很清晰,胡子眉毛一把装,尝试了所有会用或能用的方法,虽然最后拉起了库,但按照惜分飞大师的说法,还是有一些幸运的成分,不得不承认,差距还是很大,因此下面叙述中要有不到位的,还请各位指正。 环境信息: 版本:12.1.0.2.0 用途:开发环境 其他信息:未开启归档模式 现象:上班后开发人员发现start数据库错误,根据网上的信息,做了重建控制文件等操作,但依旧无法启动,系统此时已经有些混乱了,可能都记不太清楚还做了什么。 1.使用LIST/ADVISE/REPAIR FAILURE 尝试使用LIST FAILURE,发现有几个HIGH、CRITICAL的错误,由于未截图,所以只能描述,记得其中一个错误是某个数据文件出现了坏块,另一个错误是控制文件不是最新状态,好像还有个错误是系统表空间SYSTEM出现坏块(印象已经不深了)。 使用ADVISE FAILURE,指出了一些修复的方法和脚本。 然后执行了REPAIR FAILURE,执行了自动修复,发现一直刷屏,等了许久未结束,强制结束后,再次执行LIST FAILURE,发现仍旧存在数据文件坏块的错误。 2.查询有坏块的数据文件信息 使用dbv检测这一个有问题的数据文件, 从V$DATABASE_BLOCK_CORRUPTION视图查看坏块信息, 说明是10号文件的1678871和1678883块,各自有一个坏块。 其中,CORRUPTION_TYPE都是FRACTURED,表示块头看起来是正常,但是块中存在不一致的版本。 使用如下SQL可以查看这些坏块中具体存在什么信息, 说明坏块中存在的一张表使用的索引。 3.尝试修复坏块 尝试重建索引看看, 提示数据文件块损坏,显然这种方式行不通了。 说到修复坏块,江湖上还是有一些神器的,查了下, (1) ODU:ORACLE DATABASE UNLOADER,老熊和dbsnake现在负责维护。 (2) DUL:DATA UNLOADER,Oracle内部的一个非商业化产品。 (3) AUL:也称MyDUL,d.b.c.a(楼方鑫)大神负责维护。 (4) 刘大的PRM-DUL。 (5) bbed,可以做一些数据块修改的工作。 我之前没有用过任何一款,现学起来还是需要些时间。以上软件大部分有免费版,但对数据文件大小有限制,只能做很小的数据恢复,要想全部恢复,就要买license,虽然我和dbsnake是同事,但为了这么个开发库,而且是这么一个我认为在大神看来其实可能很简单的问题,还是别来麻烦别人了,自己选择继续扛下来,也算让自己锻炼一把。 4.尝试屏蔽坏块数据文件 既然是索引,不是数据,尝试下是不是可以屏蔽这个存在坏块的数据文件。 重建控制文件,可以参考eygle的文章《如何获得创建控制文件的脚本并重建控制文件》(http://www.eygle.com/faq/How.To.Backup.and.Recreate.Controlfile.htm) 生成的控制文件模版中有RESETLOGS/NORESETLOGS两种模式,采用noresetlogs脚本的控制文件,执行, 提示open的时候出现了ORA-00600的错误。 再查看alert日志, … … … 出现了一系列ORA-00600的错误,最后由PMON进程结束了数据库实例的操作。我们知道ORA-600除了是我们李老师的网名:)之外,是Oracle中比较著名的一个错误号。可以参考戴老师这篇文章,对600错误有一个比较详细的解释(http://blog.csdn.net/tianlesoftware/article/details/6645809/)。 从报错看,ORA-00600跟着的参数,第一个是4194、4137,从定义看,都是和交易相关, Primarily the transaction layer is involved with maintaining structures associated with the management of transactions. As with the cache layer , problems encountered in this layer may indicate some kind of issue at a physical level. Thus it is important to try and repeat the same steps to see if the problem recurs. 彭小波大师推荐了一篇MOS文章(Step by step to resolve ORA-600 4194 4193 4197 on database crash (文档 ID 1428786.1)),惜分飞博客文章中有译文(《ORA-600 4194/ORA-600 4193/ORA-600 4137故障解决》http://www.xifenfei.com/2016/08/ora-600-4194-ora-600-4193-ora-600-4137.html)。 描述的就是alert中出现ORA-00600 4xxx错误的原因, This error indicates that a mismatch has been detected between redo records and rollback (undo) records. 同时给出了解决方案, Create pfile from spfile to edit create pfile from spfile; Shutdown the instance set the following parameters in the pfile undo_management = manual event = ‘10513 trace name context forever, level 2’ startup restrict pfile=initsid.ora select tablespace_name, status, segment_name from dba_rollback_segs where status != ‘OFFLINE’; This is critical - we are looking for all undo segments to be offline - System will always be online. If any are ‘PARTLY AVAILABLE’ or ‘NEEDS RECOVERY’ - Please open an issue with Oracle Support or update the current SR. There are many options from this moment and Oracle Support Analyst can offer different solutions for the bad undo segments. If all offline then continue to the next step Create new undo tablespace - example create undo tablespace datafile size 2000M; Drop old undo tablespace drop tablespace including contents and datafiles; shutdown immediate; startup nomount; – Using your Original spfile modify the spfile with the new undo tablespace name Alter system set undo_tablespace = ‘’ scope=spfile; shutdown immediate; startup; – Using spfile 之所以创建新回滚表空间的原因,就是因为他的回滚段序号要高于目前正使用的段,这样在一个交易需要参考已经不存在的回滚段做块清除操作的时候,才会继续完成这项操作。 The reason we create a new undo tablespace first is to use new undo segment numbers that are higher then the current segments being used. This way when a transaction goes to do block clean-out the reference to that undo segment does not exist and continues with the block clean-out. 尝试下操作。 (1) 根据spfile创建pfile, create pfile from spfile; (2) 停止实例,编辑文件,增加: *.undo_management=manual *.event='10513 trace name context forever, level 2' 使用文件pfile以restrict启动, SQL> startup restrict pfile=/u01/app/oracle/product/12.1.0/dbhome_1/dbs/initXXXXX.ora (3) 查看dba_rollback_segs视图, 发现并不是像上面文章说的OFFLINE状态,是PARTLY AVAILABLE,换句话说需要SR?先创建了新的回滚表空间UBDOTBS2再说。 (4) 删除旧回滚表空间错误, (5) 参考http://www.linuxidc.com/Linux/2014-06/103780.htm,屏蔽这些SYSSMU表空间,pfile文件增加, (6) 再次重启删除, SQL> drop tablespace undotbs1 including contents and datafiles; 正常, (7) 再次重启,切换表空间, (8) 数据库可以打开了,查询一些应用表,可是报错了, 开始想出的方法是使用 select dbms_metadata.get_ddl('TABLE','XXX','XXX') from dual; select dbms_metadata.get_ddl('INDEX','XXX','XXX') from dual; 得出原始建表和索引的语句,但数据无法恢复。 (9) 参考http://blog.csdn.net/sdulsj/article/details/52993052,设置10231事件后使用CTAS方式重建表, 再使用表所属用户执行rename创建原表, 记得关闭事件, 此时,表数据可以恢复使用了, 总结: 1.备份恢复的基础,还是需要理解数据库运转的工作原理,出现任何报错,都是有原因,提示的信息非常重要,要能透过现象看出本质。这不是一朝一夕就能掌握的技能,但需要一朝一夕的积累,这块还需要自己多努力学习和实践。 2.还是需要有备份,如果开启了归档,或者有一些冷备,恢复起来就会方便一些。即使这是一套开发库。 3.整个过程还要感谢白鳝、惜分飞、彭小波以及道长的支持。 欢迎关注我的个人微信公众号:bisal的个人杂货铺
因为单位机房搬迁,涉及到之前为运维开发搭建的GitLab环境也需要做迁移。旧环境中安装的时候很顺畅基本没有碰见什么问题(参考:http://blog.csdn.net/bisal/article/details/52515327),但这次新环境的安装着实费了一些功夫,碰见了一些棘手的问题,记录于此,对碰见这些问题的朋友们有所帮助。 注:以下问题和解决方案援引自我的同事兼同门师弟之手,版权归他:) 问题一:Gitlab安装碰见硬编码路径 首先是安装环境准备,需要装一些rpm包, sudo yum install openssh-server sudo yum install postfix sudo yum install cronie sudo service postfix start sudo chkconfig postfix on sudo lokkit -s http -s ssh 查看包内容 rpm -qp l gitlab-ce-8.17.0-ce.0.el7.x86_64.rpm > watch.txt 发现包内容如下: /opt /opt/gitlab /opt/gitlab/LICENSE /opt/gitlab/LICENSES .... 而在新服务器上/opt路径下空间很小,让用户使用的是/DATA路径。 查看安装包内容是否可重定向 rpm -qpi gitlab-ce-8.11.5-ce.0.el6.x86_64.rpm | grep Relocations Name : gitlab-ce Relocations: / 可以看出目录/可重定向。 尝试一:重定向安装 sudo rpm -ivh --relocate /=/DATA/app gitlab-ce-8.11.5-ce.0.el6.x86_64.rpm 但是安装过程报错 cp: cannot stat `/opt/gitlab/etc/gitlab.rb.template': No such file or directory sed: can't read /etc/gitlab/gitlab.rb: No such file or directory ... /var/tmp/rpm-tmp.xPMDUv: line 10: /opt/gitlab/bin/gitlab-ctl: No such file or directory warning: %posttrans(gitlab-ce-8.11.5-ce.0.el6.x86_64) scriptlet failed, exit status 127 然后执行: sudo gitlab-ctl reconfigure 依旧报错,路径问题,查看gitlab-ctl文件,发现其中的路径都是写的/opt/gitlab类型的硬编码,尝试修改,可是涉及文件太多而且没有参照物,无果。 尝试二:使用软链接,重定向安装 在一次的尝试中,到饭点了,本不想吃饭,但波哥说没准睡个觉或吃个饭,就有思路了。。。于是乎。。。神奇的事情发生了,在去食堂的路上,我们想到既然是路径的问题,能否采用软链接,定向到要安装的目录。按照这个思路进行尝试,首先卸载已安装的程序。 查看已安装的程序 sudo rpm -qa | grep gitlab gitlab-ce-8.11.5-ce.0.el6.x86_64 卸载程序 sudo rpm -e gitlab-ce-8.11.5-ce.0.el6.x86_64 创建软链接文件 sudo ln /opt/gitlab /DATA/app/opt/gitlab 再尝试重定向安装,无报错,安装成功。 问题二:Gitlab控制台网页无法访问 Gitlab安装成功后,修改/etc/gitlab/gitlab.rb external_url 'http://xx.xx.xx.xx'(当前服务器IP) 修改完成后重新配置,在gitlab/bin目录下执行 sudo ./gitlab-ctl reconfigure 在本地访问Gitlab,发现无法访问,telnet IP 8080端口不通。 偶然的机会,在服务器同网段机器wget IP:80发现是可以正常访问的,而且发现Gitlab默认的端口为80端口,而在服务器和本地之间80端口的策略没有开通,只开通有8080端口,所以这问题很有可能就是和GitLab默认端口有关了。 既然80端口未开通,就尝试使用8080端口,修改端口策略,按照Gitlab官方说明,修改/etc/gitlab/gitlab.rb nginx['listen_addresses'] = ['*'] nginx['listen_port'] = 8080 修改后重新配置,在gitlab/bin目录下执行 sudo ./gitlab-ctl reconfigure HTTP访问,提示502 后再阅读http://blog.csdn.net/wangxicoding/article/details/43738137文章时想到,是否因为unicorn服务默认占用8080端口,将nginx端口修改为8080会造成影响?于是选择为unicorn重新配置端口,修改/etc/gitlab/gitlab.rb unicorn['listen'] = '127.0.0.1' unicorn['port'] = 8082 修改后重新配置,在gitlab/bin目录下执行 sudo ./gitlab-ctl reconfigure 修改后HTTP访问尝试,可以正常访问。 问题三:Gitlab备份及恢复 旧环境中已经有了一些代码,迁移环境可以选择重新上传代码这种方式,可这么做实在是有些LOW,Gitlab其实为我们提供了一些备份恢复的手段和方法。 首先创建备份 sudo ./gitlab-rake gitlab:backup:create 使用以上命令会在/var/opt/gitlab/backups目录下创建一个名称类似为1448938055_gitlab_backup的压缩包, 这个压缩包就是Gitlab整个的完整部分, 其中开头的1448938055是备份创建的日期。修改备份文件默认目录,可以通过修改/etc/gitlab/gitlab.rb来修改默认存放备份文件的目录: gitlab_rails['backup_path'] = '/mnt/backups' Gitlab数据恢复 停止相关数据连接服务 sudo ./gitlab-ctl stop unicorn sudo ./gitlab-ctl stop sidekiq 从1448938055编号备份中恢复 sudo ./gitlab-rake gitlab:backup:restore BACKUP=1448938055 启动Gitlab sudo ./gitlab-ctl start 完成。 拓展知识:Unicorn是什么? 参考:https://about.gitlab.com/2015/06/05/how-gitlab-uses-unicorn-and-unicorn-worker-killer/ Gitlab使用Unicorn(预分叉的Ruby web服务),来处理web请求(web浏览和Git Http Clients) Understanding Unicorn and unicorn-worker-killer Unicorn GitLab uses Unicorn, a pre-forking Ruby web server, to handle web requests (web browsers and Git HTTP clients). Unicorn is a daemon written in Ruby and C that can load and run a Ruby on Rails application; in our case the Rails application is GitLab Community Edition or GitLab Enterprise Edition. Unicorn has a multi-process architecture to make better use of available CPU cores (processes can run on different cores) and to have stronger fault tolerance (most failures stay isolated in only one process and cannot take down GitLab entirely). On startup, the Unicorn ‘master’ process loads a clean Ruby environment with the GitLab application code, and then spawns ‘workers’ which inherit this clean initial environment. The ‘master’ never handles any requests, that is left to the workers. The operating system network stack queues incoming requests and distributes them among the workers. In a perfect world, the master would spawn its pool of workers once, and then the workers handle incoming web requests one after another until the end of time. In reality, worker processes can crash or time out: if the master notices that a worker takes too long to handle a request it will terminate the worker process with SIGKILL (‘kill -9’). No matter how the worker process ended, the master process will replace it with a new ‘clean’ process again. Unicorn is designed to be able to replace ‘crashed’ workers without dropping user requests 总结: 1.实在是很不理解为何gitlab-ce-8.17.0-ce.0.el7.x86_64.rpm定义了这么多硬编码路径,而不是支持变量替换,或许有其他方法可以更好地解决这个问题,还请指教。 2.软链接这个特性很小,但是确实很好用、很实用,尤其在这个安装过程中起到了至关重要的作用。 3.一个Gitlab的安装其实涉及了很多的技术知识,例如Redis、PG等,这个gitlab-ce-8.17.0-ce.0.el7.x86_64.rpm安装包做了统一的封装,否则就需要一个组件一个组件地安装配置。 4.碰见这种很难一时解决的问题,可以吃下饭、睡个觉,兴许新思路就涌现出来了:) 欢迎关注我的个人微信公众号:bisal的个人杂货铺
之前的几篇文章: 《一个执行计划异常变更的案例 - 前传》 《一个执行计划异常变更的案例 - 外传之绑定变量窥探》 《一个执行计划异常变更的案例 - 外传之查看绑定变量值的几种方法》 《一个执行计划异常变更的案例 - 外传之rolling invalidation》 《一个执行计划异常变更的案例 - 外传之聚簇因子(Clustering Factor)》 《一个执行计划异常变更的案例 - 外传之查询执行计划的几种方法》 《一个执行计划异常变更的案例 - 外传之AWR》 《一个执行计划异常变更的案例 - 外传之ASH》 《一个执行计划异常变更的案例 - 外传之SQL AWR》 《一个执行计划异常变更的案例 - 外传之直方图》 《一个执行计划异常变更的案例 - 外传之SQL Profile(上)》 《一个执行计划异常变更的案例 - 外传之SQL Profile(下)》 从这个系列第一篇开始,一共写了12篇小文,其中涉及的知识点都是这案例实际使用的,一路写来,也让自己对这些知识做了一次梳理,有了新认识,也要感谢罗大师,以及运行同事的分享。这篇文章会介绍下这个问题是如何定位和处理的,其中涉及的一些具体操作可以参考之前的各篇外传。 简单再来回顾下这个问题,有一个在线交易库,突然出现性能问题,DBA发现有一些delete语句执行时间骤长,消耗大量系统资源,导致应用响应时间变长进而造成应用系统积Q。辅助信息: (1) 应用已经很久未做过更新上线了。 (2) 据开发人员反馈,从之前的应用日志看,未出现处理时间逐步变长的现象。 (3) 这是一套RAC+DG的环境,11g的版本。 (4) 这次突然出现大量执行时间超长的SQL语句,是一条删除语句, delete from table where key1=:1 and key2=:2 and ...(省略此案例不会用到的其他条件) 应用正常的处理逻辑中都会使用这条语句,因此并发较高,使用了绑定变量,key1和key2字段不是主键,但有索引,存在直方图。 出现性能问题的SQL消耗信息和执行计划如下(生成方式可参见《一个执行计划异常变更的案例 - 外传之查询执行计划的几种方法》、《一个执行计划异常变更的案例 - 外传之AWR》、《一个执行计划异常变更的案例 - 外传之ASH》和《一个执行计划异常变更的案例 - 外传之SQL AWR》), 假设使用的是key1对应的索引进行索引范围扫描,平均每次执行需要32秒,消耗两万七千多次逻辑读。 查询历史执行计划,发现还有一个执行计划, 假设使用的是key2对应的索引进行索引范围扫描,平均每次执行之需要11秒,消耗216次逻辑读。 上面第一个执行计划成本值是4,第二个执行计划成本值是5,因此在CBO模式下,Oracle会选择这个成本值是4的执行计划,然而通过查询dba_tab_histograms视图(《一个执行计划异常变更的案例 - 外传之直方图》),发现key1值是0的记录数占据了绝大比重,超过一半,约1500万, ENDPOINT_VALUE ENDPOINT_NUMBER -------------- --------------- 0 149 16942622 150 ... ... 2180209419 253 2180643328 254 说明这字段数据出现严重倾斜,若查询条件是key1=0,则需要扫描1500万的记录,key2字段值分布不存在倾斜,查询一些历史执行SQL的记录(参见《一个执行计划异常变更的案例 - 外传之查看绑定变量值的几种方法》),发现确实key1值为0的语句是很多的,并发性较高,因此会消耗大量的系统资源,这是导致问题的直接原因。 根本原因又是什么呢?为什么消耗资源较多的执行计划会被Oracle选择?11g下默认的优化器模式是CBO,他会计算这条SQL所有可能的执行计划对应的成本值,选择成本值最低的执行计划。 从10053的trace中可以看出,这条SQL有4个索引可用,对应有索引树层级、聚簇因子(《一个执行计划异常变更的案例 - 外传之聚簇因子(Clustering Factor)》)等信息, 计算后第三个索引成本最低,其就是key1列对应的索引。优化器选择一个成本最低的执行计划,无可厚非。 但是,这库选择了禁止绑定变量窥探特性(更谈不上ACS,请参见《一个执行计划异常变更的案例 - 外传之绑定变量窥探》),因此当SQL带着绑定变量参数等待执行的时候,每次是无法知道具体值是什么,所以会选择默认的selectivity计算方式,千篇一律。又由于key1数据值分布严重倾斜,且并发性较高的SQL绑定变量值就是倾斜值0,因此才产生这个问题。 另外,查询dba_hist_active_sess_history视图发现Oracle选择这一个异常的执行计划时间是20161218 115136, select to_char(min(sample_time) ,'YYYYMMDD HH24MISS') from dba_hist_active_sess_history where snap_id = 26273 and sql_id = 'g1xv7657qv3bt' and SQL_PLAN_HASH_VALUE = '26263919' 查询dba_indexes和dba_tables发现当日20161218 070813左右统计信息均发生了改变, select blevel,index_name,NUM_ROWS,DISTINCT_KEYS, to_char(last_analyzed,'YYYYMMDD HH24MISS') from dba_indexes where table_name = 'XXX' select num_rows,to_char(last_analyzed,'YYYYMMDD HH24MISS') from dba_tables where table_name = 'XXX'; 两者相差时间大约17000秒,根据《一个执行计划异常变更的案例 - 外传之rolling invalidation》可知是Oracle一个特性,为了避免对共享池的风暴冲击,Oracle随机选择了20161218 115136这个时间点重新解析SQL,由于统计信息变化,导致成本计算后,差的执行计划成本值变为了4,成为成本值最低的执行计划,高并发执行后,系统开始出现资源紧张。 根据上面的信息,第二种执行计划(成本值为5)消耗的资源要远小于第一种执行计划(成本值为4)消耗的资源,为了快速解决问题,针对这种不能立即修改应用代码,但又需要调整执行计划的场景,就可以选择SQL Profile技术,让SQL选择成本值为5,使用key2索引的执行计划,具体操作可参见《一个执行计划异常变更的案例 - 外传之SQL Profile(上)》和《一个执行计划异常变更的案例 - 外传之SQL Profile(下)》。 作为这问题的后续改进方法,首先需要业务上确认key1字段值为0的倾斜情况是否正常,若不正常则规避之,就不会有这个问题,若就是正常情况,则可以考虑应用判断此次执行的key1是否为0,若为0,则不使用绑定变量,直接用key1=0为条件,让Oracle根据直方图信息以及表中其他查询条件的索引,来选择正确的执行计划,若不为0,则仍旧可用key1=:1的绑定变量,即使未开启绑定变量窥探,因为key1除0值外其他值可选择度平均且较好,因此仍可以选择正确的执行计划。 总结: 性能优化是一项技术活,因为往往需要很扎实的理论基础,还有成熟的问题排查思路和解决方案,外加一些运气,实践非常重要,毕竟“纸上得来终觉浅,绝知此事要躬行”,我作为一名初学者,还处在夯实理论基础的道路上,还要需要更多的学习和实践,才能追赶大师的步伐,但至少要保证方向正确,即使路程艰辛,不放弃,会有所成,朋友们一起努力! 欢迎关注我的个人微信公众号:bisal的个人杂货铺
下班路上看见网上有人问一个问题: oracle 10g以后count(*)和count(非空列)性能方面有什么区别? 乍一看,确实有些含糊,Oracle中往往小问题蕴含着大智慧,如何破云见日? 最直接的方法,我想就是通过10053事件,来看下不同SQL对应的执行计划和资源消耗等情况,进而看看是否有些信息可以为我们所用。 首先,准备测试数据,11g库表bisal的id1列是主键(确保id1列为非空),id2列包含空值, 我们分别用10053打印如下4组SQL的trace, SQL1:select count(*) from bisal; SQL2:select count(1) from bisal; SQL3:select count(id1) from bisal; SQL4:select count(id2) from bisal; 我们来看下这四个SQL的执行结果, 前三个均为表数据总量,第四个SQL结果是99999,仅包含非空记录数据量,说明若使用count(允许空值的列),则统计的是非空记录的总数,空值记录不会统计,这可能和业务上的用意不同。 我们在看下这四个SQL对应的执行计划,前三个SQL执行计划相同,均为对主键索引的快速索引全扫描, 第四个SQL执行计划,则是全表扫描, 其实这无论id2是否包含空值,使用count(id2)均会使用全表扫描,因此即使语义上使用count(id2)和前三个SQL一致,这种执行计划的效率也是最低的,这张测试表的字段设置和数据量不很夸张,因此不很明显,如果数据表字段多、数据量大,显然主键索引占用的数据块要比数据表占用的数据块少,因此仅索引扫描,而且是全索引快速扫描(多块读),消耗的资源会更少些了。 再看前三个SQL对应的trace,第1个SQL, 第二个SQL, 第三个SQL, 可以看出一个问题,就是这三个SQL经过Oracle转换,执行的SQL其实都是select count(*) from bisal,因此对应的执行计划成本选择,这三个SQL相同, 比较了全表扫描、索引快速全扫描以及全索引扫描这三种扫描方式的成本,都选择了主键索引的FFS扫描方式。 总结: 11g下,通过实验结论,说明了count()、count(1)和count(主键索引字段)其实都是执行的count(),而且会选择索引的FFS扫描方式,count(包含空值的列)这种方式一方面会使用全表扫描,另一方面不会统计空值,因此有可能和业务上的需求就会有冲突,因此使用count统计总量的时候,要根据实际业务需求,来选择合适的方法,避免语义不同。 欢迎关注我的个人微信公众号:bisal的个人杂货铺
之前的几篇文章: 《一个执行计划异常变更的案例 - 前传》 《一个执行计划异常变更的案例 - 外传之绑定变量窥探》 《一个执行计划异常变更的案例 - 外传之查看绑定变量值的几种方法》 《一个执行计划异常变更的案例 - 外传之rolling invalidation》 《一个执行计划异常变更的案例 - 外传之聚簇因子(Clustering Factor)》 《一个执行计划异常变更的案例 - 外传之查询执行计划的几种方法》 《一个执行计划异常变更的案例 - 外传之AWR》 《一个执行计划异常变更的案例 - 外传之ASH》 《一个执行计划异常变更的案例 - 外传之SQL AWR》 《一个执行计划异常变更的案例 - 外传之直方图》 《一个执行计划异常变更的案例 - 外传之SQL Profile(上)》 上篇文章介绍了Automatic类型的SQL Profile,这种类型的SQL Profile隐患就是未锁定执行计划,只是对统计信息进行了一些修正,一旦表统计信息出现了一些波动,就可能出现错误的修正。 为了解决这种问题就可以尝试Manual类型的SQL Profile,我们来看下他是如何不变更原文的情况下,调整执行计划,并做到可以稳定执行计划的目的。 为了创建Manual类型的SQL Profile,我们需要使用MOS(All About the SQLT Diagnostic Tool (文档 ID 215187.1))中可下载的一个脚本coe_xfr_sql_profile.sql。 我们依旧采用上篇文章中使用的测试表t1和t2,数据量、索引和统计信息收集均相同。使用如下SQL执行计划不是最优的, 通过上篇文章的分析,我们知道这才是最优的执行计划, 首先查询这两条SQL对应的sql_id, 查询这两个sql_id对应的plan_hash_value, 执行coe_xfr_sql_profile.sql脚本,输入参数为上面第一次执行的SQL语句(即需要优化的)对应的sql_id和plan_hash_value, 输出结果中含有一个脚本,命名格式就是“coe_xfr_sql_profile_(sql_id)_(plan_hash_value).sql, 打开脚本可以看见其注释,说明他可以创建一个自定义的SQL Profile, 接着我们对使用正确执行计划的SQL执行脚本, 同样生成了一个脚本, 我们用正确的执行计划对应的脚本中HINT部分, 替换错误执行计划对应的脚本中HINT部分, 同时将下面这个参数force_match的默认值FALSE改为TRUE,意思是针对不同文本值的SQL,可以重用此SQL Profile, 然后执行此脚本, 此时就创建了一个Manual类型的SQL Profile。 我们看下效果,重新执行SQL, 可以看出执行计划已经是最优的,而且Note部分说明已经使用了SQL Profile。 此时我们再次将t1表优化器认知的数量改为500万, Automatic类型的SQL Profile此时就会由于缩放错误,再次选择错误的执行计划,我们看下这种Manual类型的SQL Profile, 看出仍旧使用的正确执行计划,证明了这种类型的SQL Profile是可以锁定正确执行计划。 上面我们将force_match参数值设为了TRUE,看下有什么作用, 我们将%ABC%换为了%ZZZ%,仍旧采用了正确的执行计划。 当然,如果SQL语句变了,意味着上述手工创建的SQL Profile就不能用了,除非再次创建对应的SQL Profile, 总结: 和上篇文章介绍的Automatic类型的SQL Profile相比,Manual类型的SQL Profile的创建过程要复杂一些,但其可以不改SQL的前提下,调整执行计划,最重要的是他能稳定执行计划,不会因为统计信息波动等问题,导致选择错误的执行计划,对于一些短期内不能改应用调整SQL的场景,我们可以选择合适的SQL Profile类型进行执行计划的调整操作。 欢迎关注我的个人微信公众号:bisal的个人杂货铺
之前的几篇文章: 《一个执行计划异常变更的案例 - 前传》 《一个执行计划异常变更的案例 - 外传之绑定变量窥探》 《一个执行计划异常变更的案例 - 外传之查看绑定变量值的几种方法》 《一个执行计划异常变更的案例 - 外传之rolling invalidation》 《一个执行计划异常变更的案例 - 外传之聚簇因子(Clustering Factor)》 《一个执行计划异常变更的案例 - 外传之查询执行计划的几种方法》 《一个执行计划异常变更的案例 - 外传之AWR》 《一个执行计划异常变更的案例 - 外传之ASH》 《一个执行计划异常变更的案例 - 外传之SQL AWR》 《一个执行计划异常变更的案例 - 外传之直方图》 这个系列文章已经连载了多篇,节后这几天工作生活都比较忙,只能利用碎片时间继续,接下来的两篇博文都是关于SQL Profile的,然后就可以进入正传了:) 首先,老熊的两篇SQL Profile的博文,以及dbsnake书中第二章关于SQL Profile的介绍,是我认为中文介绍SQL Profile最好的两个学习来源,本文中的一些观点和案例会从中借鉴一些。 从MOS上我们看下什么是SQL Profile, SQL Profile is a collection of information stored in the data dictionary that enables the query optimizer to create an optimal execution plan for a SQL statement.The SQL profile contains corrections for poor optimizer estimates discovered during Automatic SQL Tuning. This information can improve optimizer cardinality and selectivity estimates, which in turn leads the optimizer to select better plans.. SQL Profile是一组存储在数据字典中的信息,目的就是为了创建最优的SQL执行计划,通过使用自动SQL调优工具,提高优化器对cardinality和selectivity的预估,以纠正错误的执行计划。 说白了,SQL Profile是一组数据字典信息,可以提高优化器对cardinality和selectivity的预估进而影响执行计划的成本计算,达到选择正确执行计划的目的。 SQL Profile是从10g开始引入的,SQL Profile相比于Outline的优点: 1.SQL Profiles更容易生成、更改和控制。 2.SQL Profiles在对SQL语句的支持上做得更好,也就是适用范围更广。 使用SQL Profiles的两个目的: 1.锁定或者说是稳定执行计划。 2.在不能修改应用中的SQL的情况下使SQL语句按指定的执行计划运行。 SQL Profile有两种类型,一种是Automatic类型,另一种是Manual类型,本篇文章会介绍前者,下一篇会介绍后者。 创建测试表,t1表10000条记录,t2表50000条记录。 t2表的id列创建非唯一索引,收集t1和t2表和索引的统计信息,不收集直方图, 执行这条SQL语句,Oracle选择了两表全表扫描,再做Hash Join的执行计划,逻辑读221, 实际t1表符合t1.name like ‘%ABC%’条件的记录只有3条,优化器预估则是500条,即500/10000*100%=5%,应该是Oracle的默认选择率,但关于这点,略有疑问,按照MOS(68992.1)的说法 无直方图情况下,选择率计算方法如下: c1 = ‘4076’ 1/NDV c1 > ‘4076’ 1 - (High - Value / High - Low) c1 >= ‘4076’ 1 - (High - Value / High - Low) + 1/NDV c1 like ‘4076’ 1/NDV 有绑定变量情况下,选择率计算方法如下: c1 = :bind1 1/NDV c1 > :bind1 Default of 5% c1 >= :bind1 Default of 5% c1 like :bind1 Default of 25% 这有一个清晰的总结(http://www.ordba.net/Articles/PredicateSel.htm): 如果按照这个说法,上面SQL未使用绑定变量,没有直方图,like选择率应该是1/NDV,这里就是1/10000*100%=0.01%,11g的库,可能还是我什么地方没有理解正确,还请各位指教。 因为实际t1使用like的结果集很小,一个大表和一个小的结果集关联,而且大表关联字段有索引,比较适合于Nested Loop连接。我们看下使用这种连接的执行计划, 使用use_nl和index两个hint,强制SQL使用t2表的索引检索,并让t1和t2做nested loop连接,成本值确实要大于上面HASH JOIN连接的成本,但看其逻辑读只有40,远小于Hash Join的221,再看t1表的返回行数预估是500,远高于实际like过滤后其实际的结果集3,因此会对nested loop连接产生了一个过高的成本预估,导致优化器选择了Hash Join的执行计划,所以这条SQL目前的执行计划并不是最优的,不改动SQL,如何才能correct这条SQL的执行计划? 接下来我们用STA(SQL Tuning Advisor)查找是否有更好的执行计划,主要会用到dbms_sqltune包,相应方法的参数很多,这里举出两种用法, 方法1:利用sql id来做STA, 首先查找要纠正的SQL对应的sql id值, 接下来执行如下PLSQL, 可以看出这次STA执行的任务名称是TASK_4516。使用dbms_sqltune.report_tuning_task来查看执行结果, 方法2:使用SQL文本来做STA, 由于SQL原文中有引号字符,直接使用会报错, 使用q’[…]’,使用create_tuning_task更多的参数,命名这次任务为my_sql_tuning_task, 以上使用了sql id和SQL原文两种方法创建STA任务,其实create_tuning_task还支持很多种参数, 无论什么方法,结果是一样的,都可以直接执行结果集指出的存储过程,第一条是方法一的存储过程,第二条是方法二的存储过程, 唯一不同的是都增加了force_match=>true的参数,这个参数类似于cursor_sharing,true则会将文本值自动转换为绑定变量(不包括文本值和绑定变量混用的SQL),目的就是可以重用SQL Profile, 接着我们再来执行上面的SQL语句, 发现此时选择的就是之前使用hint得到的执行计划,即t2表索引扫描t2表,t1和t2表使用nested loop连接。 从10053的trace可以看出对t1表的返回行数预估做了修正,从500调整为3, 另外,需要注意一下执行计划中,就是在下面Note部分提示, SQL profile “SYS_SQLPROF_015a06ed92930000” used for this statement 说明这条SQL使用了名称为SYS_SQLPROF_015a06ed92930000的SQL PROFILE,查询DBA_SQL_PROFILES视图可以得到其他信息, 这里只是指出了SQL Profile的一些属性,SQL Profile究竟是什么东东?老熊的文章中曾经指出可以用sys.sqlprof$attr数据字典查询其定义,但只有10g才有定义这个数据字典,11g下没有这个名称的数据字典,这块在网上(MOS、百度)搜了半天也没找到任何线索,后来经野花总指点,从bing上搜到了非常对应的线索,可以对比下百度和bing使用相同关键词进行搜索的结果,只能说… 1.百度搜索, 2.bing搜索(建议仅英文) 11g下,以下这两个数据字典接管了sys.sqlprof$attr的信息了, sys.sqlobj$ sys.sqlobj$data 使用如下SQL, 或者使用这条更精妙的SQL, 可以看出这儿有一个词(这和10g中sys.sqlprof$attr的ATTR_VAL等价): SCALE_ROWS=0.006 他的含义是针对T1表评估返回行数与原始评估返回行数的放大缩小倍数是0.006。这条SQL使用nested loop执行计划中T1表原始其预估行数是500,计算500*0.006=3,这就是为什么10053的trace以及执行计划中表t1的预估行数是3的原因,我们没有对SQL做任何一些改动,而是让优化器得到一些更准确的信息,以让其选择正确的执行计划,amazing? 但这有一个问题,就是其并未锁定执行计划,我们强制让Oracle知道T1表有500万记录(实际并未如此), 此时再次执行SQL,发现依旧使用了SQL Profile,但执行计划变为了全表扫描后做Hash Join, T1表预估行数是1500,计算方式是5000000*0.006*5%=1500。说明虽然使用了SQL Profile,但执行计划并未锁定,因此对于一些统计信息可能会有较大变化的表来说,使用这种Automatic类型的SQL Profile还是可能有隐患。 注:5%的默认可选择率,和上面的疑问相同。 总结: 1.本文主要介绍了Automatic类型的SQL Profile创建过程,以及其未改变SQL却会让优化器选择最优执行计划的原理,同时对于一些统计信息可能会有较大变化的表来说,这种类型的SQL Profile未锁定执行计划,因此还是可能有隐患。 2.下篇会介绍Manual类型的SQL Profile,看看他是如何锁定执行计划,解决这种Automatic类型不能解决的问题。 3.对于未使用绑定变量未有直方图的SQL,like谓词对应表其默认可选择率是5%的说法,我还是有疑问,还请各位能指教一二。 另外,欢迎关注我的个人微信公众号:bisal的个人杂货铺 共同学习,共同进步:)
今天单位值班,有一些时间可以继续完成这篇连载文章。首先祝所有朋友新年快乐!感谢你们在这一年当中对我文章的关注和指点,来年我们共同继续努力! 之前的几篇文章: 《一个执行计划异常变更的案例 - 前传》 《一个执行计划异常变更的案例 - 外传之绑定变量窥探》 《一个执行计划异常变更的案例 - 外传之查看绑定变量值的几种方法》 《一个执行计划异常变更的案例 - 外传之rolling invalidation》 《一个执行计划异常变更的案例 - 外传之聚簇因子(Clustering Factor)》 《一个执行计划异常变更的案例 - 外传之查询执行计划的几种方法》 《一个执行计划异常变更的案例 - 外传之AWR》 《一个执行计划异常变更的案例 - 外传之ASH》 《一个执行计划异常变更的案例 - 外传之SQL AWR》 这篇文章我们聊聊直方图。 首先我们看下统计学中对直方图的定义: 直方图(Histogram)又称质量分布图。是一种统计报告图,由一系列高度不等的纵向条纹或线段表示数据分布的情况。 一般用横轴表示数据类型,纵轴表示分布情况。 可以看出,直方图可以用来描述数据分布的情况。Oracle中也是如此,直方图可以准确预测列数据的分布,尤其在出现数据分布倾斜的情况下,通过直方图信息,可以选择最优的执行计划。 P.S. 关于直方图的介绍,推荐dbsnake的书,其中第五章详细介绍了11g的直方图,非常详细受用。 11g下有两种类型的直方图(12c又多了其他类型的直方图): Height-Balanced Histograms Frequency Histograms 查询USER/DBA_TAB_COL_STATISTICS视图的HISTGRAM列可以知道存储的是何种类型的直方图(取值为HEIGHT BALANCED,FREQUENCY,NONE)。 创建测试表,name列有100000行值为A,1行值为B,数据出现了倾斜,name列存在非唯一二叉树索引,采集统计信息时不收集直方图, 从HISTGRAM列可以看出未有任何直方图统计, 根据name=’A’检索,选择了全表扫描的执行计划, 根据name=’B’检索,同样选择了全表扫描的执行计划, 从数据分布看,A的记录有100000条,B的记录有1条,该列有索引,按说A为条件的SQL应该选择全表扫描采用多块读的方式最高效,B为条件的SQL应该使用索引采用索引扫描的方式最高效,但实际情况是两者均采用了全表扫描的执行计划。 原因就是此时Oracle认为name列值是均匀分布的,根据Cardinality的计算, Computed Cardinality = Original Cardinality * Selectivity Selectivity= 1 / NUM_DISTINCT 计算如下: Computed Cardinality = 100001 * 1 / 2 约等于50001,可以从上面两个执行计划中Rows预估行看出两个SQL的预估行均为50001。 接着我们收集name列的直方图,此处未指定method_opt会由Oracle自行来判断收集的直方图信息和类型, 可以看出name列采集了FREQUENCY类型的直方图信息, 我们再执行刚才的两条SQL,name=’A’的仍选择了全表扫描,我们要重点看下B的SQL,此时选择了索引范围扫描,不是全表扫描了,说明Oracle知道了这列的数据分布,CBO认为索引扫描成本值更低,从10053事件可以查看具体计算值, 但有一处要注意,就是Rows这是18,我们之前知道name=’B’只有1条记录,怀疑这和采用默认的统计信息收集比率有关,默认未必采用了100%的数据作为样本,重新以100%的比例采集统计信息, 可以看出Rows是1了,说明Oracle此时已经知道了数据的分布,CBO计算时知道使用索引扫描成本值更低了。 Oracle直方图使用一种称为Bucket(桶)的方式来描述列的数据分布,每个Bucket就是一组,其中会存储一个或多个列数据,Bucket使用ENDPOINT NUMBER和ENDPOINT VALUE两个维度来描述,其中ENDPOINT VALUE记录列的distinct值,ENDPOINT NUMBER表示到此distinct值为止总计有多少条记录(即这条distinct值对应的ENDPOINT NUMBER减上条记录distinct值对应的ENDPOINT NUMBER就会是这条distinct值的记录数),上面示例中name列是FREQUENCY类型的直方图,对于这种类型的直方图,Bucket的数量就是列distinct值的数量,从NUM_DISTINCT知道有2个distinct值, 因此user_tab_histograms中列name对应的记录(Bucket)应该是2条, 我们看出第一条记录: ENDPOINT VALUE:337499295804764000000000000000000000 ENDPOINT NUMBER:100000 第二条记录: ENDPOINT VALUE:342691592663299000000000000000000000 ENDPOINT NUMBER:100001 上面说ENDPOINT VALUE是distinct值,我们看下如何推导出,以A为例,A对应的十六进制是0x41,将0x41右补至15个字节长度的0,再将其转换为十进制,即3.3750E+35,正如上面对应的第一条记录ENDPOINT VALUE值, 第一条记录的ENDPOINT NUMBER是100000,说明有100000条记录值是A,第二条记录的ENDPOINT NUMBER是100001,说明有(100001-10000=1)条记录值是B。 对于这种FREQUENCY的直方图,dbsnake书中明确说明了其缺点,就是适合于一些distinct值少的情况,因为11g的FREQUENCY直方图对应的Bucket数量不能超过254(12c不受此限制),如果列值distinct值超过254,则不能使用这种类型的直方图。而且若列值类型是文本型,采集直方图时只会采集文本值头32个字节,换句话说,若多个列值distinct的头32个字节相同,则Oracle可能会将他们作为一个值来采集,就会对采集结果产生影响,这是错误。 对于列值distinct超过254的情况,Oracle会采集HEIGHT BALANCED类型的直方图。这种类型的直方图首先会根据列的所有记录按从小到大的顺序排序,用总记录数处于需要使用的Bucket数量,决定每一个Bucket中要存储的记录数,对于相邻Bucket的仅ENDPOINT NUMBER不同,ENDPOINT VALUE值相同记录数做合并存储,ENDPOINT VALUE存储的是到此记录所描述的Bucket为止之前所有Bucket描述的记录中列的最大值,通过实验我们体会下,创建测试数据, name列有301个distinct值,其中值为201有700条记录,采集统计信息时指定Bucket数量是10,此时查看user_tab_col_statistics的HISTOGRAM值变为了HEIGHT BALANCED, select 1000/10 from dual; 知道每一个Bucket应该存储100条记录数, 0号Bucket存储的是列最小值,即1,1-10号Bucket存储的是到此记录所描述的Bucket位置之前所有Bucket描述的记录中列的最大值,每个Bucket存储100条记录数,因此这可以推测出1号Bucket的ENDPOINT VALUE是之前存储的最大值100,ENDPOINT NUMBER是1-0=1,因为每一个distinct这只有一条,值为201的记录有700条,一个Bucket不足以存储,需要7个Bucket,从顺序上看,是2号至9号,由于这几个Bucket的ENDPOINT NUMBER不同,ENDPOINT VALUE值相同,因此做了合并,这种合并后的ENDPOINT VALUE称为popular value,该值记录的ENDPOINT NUMBER和上一记录的ENDPOINT NUMBER差值越大,则意味着这个popular value在表中所占比例也就越大,对应的Cardinality就越大了,进而影响执行计划的成本计算。 此时我们根据name=201执行,选择了全表扫描的执行计划, 根据name=1执行,此时选择了索引扫描的执行计划, 对于这种distinctr超过254的情况,HEIGHT BALANCED用这种方式存储了直方图信息,计算成本时参考,因此选择了正确的执行计划。 总结: 直方图描述了列的数据分布情况,对于列值数据分布倾斜的表,使用直方图可以帮助选择正确的执行计划,11g有两种直方图类型,FREQUENCY和HEIGHT BALANCED,其中FREQUENCY适合于distinct不超过254的表,而且有错误预测的可能。HEIGHT BALANCED采用这种popular value的合并方式来存储直方图信息且对执行计划Cardinality的预测提供参考依据。