
数据库相关技术专家
我的博客即将入驻“云栖社区”,诚邀技术同仁一同入驻。
一 前言 在编写Python 程序或者工具脚本时,需要完成某个功能,可以选择编写一个具体的函数达到目的,当然也可以通过匿名/Python 内建函数来完成。本问讲述常见的Python匿名、内建函数---lambda,map,filter,reduce .二 例子2.1 Lambda 是一个匿名函数, 其语法为:lambda parameters:express parameters:可选,通常是逗号分隔的变量表达式形式,即位置参数。 expression:不能包含分支或循环(但允许条件表达式),也不能包含return(或yield)函数。如果为元组,则应用圆括号将其包含起来。 调用lambda函数,返回的结果是对表达式计算产生的结果。 根据参数是否为2的倍数 返回yes or no In [21]: s = lambda x:"yes" if x%2 == 0 else "no" In [22]: s(3) Out[22]: 'no' In [24]: s(4) Out[24]: 'yes' 上面例子中,将if...else语句缩减为单一的条件表达式,语法为: expression1 if A else expression2 如果A为True,条件表达式的结果为expression1,否则为expression2 。 使用匿名函数的优点是代码简洁 ,但是不宜读 ,推荐 for ..in ...if 语法进行遍历迭代。2.2 filter 过滤 其语法为 filter(function, sequence) 意义是:对sequence中的item 依次迭代调用function(item),根据执行结果为True或者False来保留或者丢弃该元素。并将保留的item组成一个List/String/Tuple(取决于sequence的类型 ) filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。 In [15]: filter(lambda x:True if x % 3 == 0 else False, range(10)) Out[15]: [0, 3, 6, 9] #返回list In [17]: filter(lambda x:True if x % 3 == 0 else False, (1,2,3,5,6,7,8,9,10)) Out[17]: (3, 6, 9) #返回Tuple 元组 2.3 map 映射 其语法为map(function, sequence): map()的用法和filter()类似,也是将序列放入函数进行运算,但是,不论运算结果为什么,map()将返回结果也即返回的结果元素个数和sequence一样多,filter是返回符合条件的值。这是map()和filter()的主要区别。请注意,filter()和map()中的function都必要有一个返回值。同样获取能否被3整除的值,map 返回的是True/False 列表,filter返回的是符合条件的元素结果集。 In [18]: list(map(lambda x:True if x % 3 == 0 else False, range(10))) Out[18]: [True, False, False, True, False, False, True, False, False, True] 2.4 reduce 其语法为 reduce(function, sequence, starting_value) 意义是:对sequence中的item 依次调用function。reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4) 如果有starting_value,还可以作为初始值调用,比如1-100的list求和: In [11]: def add(x,y): ...: return x + y ...: In [12]: reduce(add, range(1, 100)) Out[12]: 4950 In [13]: reduce(add, range(1, 10)) Out[13]: 45 # 带上 starting_value 参数,相当于 1+2+3+4+5+6+7+8++9+10+20 In [14]: reduce(add, range(1, 10),20) Out[14]: 65 三 小结 本文总结了python内置函数和匿名函数,在编程时合理的运用能够极大的提高开发效率。 推荐 https://www.python.org/dev/peps/pep-0289/
一 前言 工欲善其事必先利其器,前面分析了很多死锁案例,并没有详细的介绍如何通过死锁日志来诊断死锁的成因。本文将介绍如何读懂死锁日志,尽可能的获取信息来辅助我们解决死锁问题。二 日志分析 2.1 场景 为了更好的学习死锁日志,我们需要提前了解死锁场景MySQL 5.6 事务隔离级别为RR CREATE TABLE `ty` ( `id` int(11) NOT NULL AUTO_INCREMENT, `a` int(11) DEFAULT NULL, `b` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idxa` (`a`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 insert into ty(a,b) values(2,3),(5,4),(6,7); 2.2 测试用例 T2 T1 begin; delete from ty where a=5; begin; delete from ty where a=5; insert into ty(a,b) values(2,10); delete from ty where a=5; ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction 2.3 我们通过show engine innodb status 查看的日志是最新一次记录死锁的日志。 ------------------------ LATEST DETECTED DEADLOCK ------------------------ 2017-09-09 22:34:13 7f78eab82700 *** (1) TRANSACTION: #事务1 TRANSACTION 462308399, ACTIVE 33 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s) MySQL thread id 3525577, OS thread handle 0x7f896cc4b700, query id 780039657 localhost root updating delete from ty where a=5 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 219 page no 4 n bits 72 index `idxa` of table `test`.`ty` trx id 462308399 lock_mode X waiting *** (2) TRANSACTION: TRANSACTION 462308398, ACTIVE 61 sec inserting, thread declared inside InnoDB 5000 mysql tables in use 1, locked 1 5 lock struct(s), heap size 1184, 4 row lock(s), undo log entries 2 MySQL thread id 3525490, OS thread handle 0x7f78eab82700, query id 780039714 localhost root update insert into ty(a,b) values(2,10) *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 219 page no 4 n bits 72 index `idxa` of table `test`.`ty` trx id 462308398 lock_mode X *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 219 page no 4 n bits 72 index `idxa` of table `test`.`ty` trx id 462308398 lock_mode X locks gap before rec insert intention waiting *** WE ROLL BACK TRANSACTION (1) 2.4 日志分析 *** (1) TRANSACTION: #事务1 TRANSACTION 462308399, ACTIVE 33 sec starting index read 事务编号为 462308399 ,活跃33秒,starting index read 表示事务状态为根据索引读取数据。常见的其他状态: fetching rows 表示事务状态在row_search_for_mysql中被设置,表示正在查找记录。 updating or deleting 表示事务已经真正进入了Update/delete的函数逻辑(row_update_for_mysql) thread declared inside InnoDB 说明事务已经进入innodb层。通常而言 不在innodb层的事务大部分是会被回滚的。 mysql tables in use 1, 说明当前的事务使用一个表。locked 1 表示表上有一个表锁,对于DML语句为LOCK_IX LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s) LOCK WAIT表示正在等待锁, 2 lock struct(s) 表示trx->trx_locks锁链表的长度为2,每个链表节点代表该事务持有的一个锁结构,包括表锁,记录锁以及auto_inc锁等。本案例中2locks 表示IX锁和lock_mode X(Next-key lock) heap size 360 表示事务分配的锁堆内存大小,一般没有什么具体的用处。 1 row lock(s)表示当前事务持有的行记录锁/gap 锁的个数。 delete from ty where a=5 表示事务1在执行的sql ,不过比较悲伤的事情是show engine innodb status 是查看不到完整的事务的sql 的,通常显示当前正在等待锁的sql。 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 219 page no 4 n bits 72 index `idxa` of table `test`.`ty` trx id 462308399 lock_mode X waiting RECORD LOCKS 表示记录锁,space id为219,page号4 ,n bits 72表示这个聚集索引记录锁结构上留有72个Bit位 表示事务1 正在等待表 ty 上的 idxa 的 X 锁本案例中其实是Next-Key lock 事务2的log 和上面分析类似, *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 219 page no 4 n bits 72 index `idxa` of table `test`.`ty` trx id 462308398 lock_mode X 显示了事务2 insert into ty(a,b) values(2,10)持有了a=5 的Lock mode X |LOCK_GAP ,不过我们从日志里面看不到 事务2 执行的 delete from ty where a=5;这点也是造成DBA 仅仅根据日志难以分析死锁的问题的根本原因。 *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 219 page no 4 n bits 72 index `idxa` of table `test`.`ty` trx id 462308398 lock_mode X locks gap before rec insert intention waiting 表示事务2的insert 语句正在等待插入意向锁 lock_mode X locks gap before rec insert intention waiting (LOCK_X + LOCK_REC_GAP )这里需要各位注意的是锁组合,类似lock_mode X waiting ,lock_mode X,lock_mode X locks gap before rec insert intention waiting 是我们分析死锁的核心重点。如何理解锁组合呢? 首先我们要知道对于MySQL有两种常规锁模式 LOCK_S(读锁,共享锁) LOCK_X(写锁,排它锁) 最容易理解的锁模式,读加共享锁,写加排它锁. 有如下几种锁的属性 LOCK_REC_NOT_GAP (锁记录) LOCK_GAP (锁记录前的GAP) LOCK_ORDINARY (同时锁记录+记录前的GAP 。传说中的Next Key锁) LOCK_INSERT_INTENTION(插入意向锁,其实是特殊的GAP锁) 锁的属性可以与锁模式任意组合。例如. lock->type_mode 可以是Lock_X 或者Lock_S locks gap before rec 表示为gap锁:lock->type_mode & LOCK_GAP locks rec but not gap 表示为记录锁,非gap锁:lock->type_mode & LOCK_REC_NOT_GAP insert intention 表示为插入意向锁:lock->type_mode & LOCK_INSERT_INTENTION waiting 表示锁等待:lock->type_mode & LOCK_WAIT三 小结 本文算是简单的死锁分析入门,能够提供部分死锁分析的所需要的技术知识。死锁分析确是一门技术活儿,想要透彻的分析死锁的成因,我们必须要了解造成死锁的业务逻辑sql 的执行场景,MySQL的锁机制 ,各种锁之间的兼容性,必要时还需要透彻的理解源码。
一 前言 死锁,其实是一个很有意思,也很有挑战的技术问题,大概每个DBA和部分开发同学都会在工作过程中遇见过 。关于死锁我会持续写一个系列的案例分析,希望能够对想了解死锁的朋友有所帮助。本文介绍一例三个并发insert 导致的死锁,根本原因还是在于insert 唯一键申请插入意向锁这个特殊的GAP锁。其实称呼插入意向锁 为 Insert Intention Gap Lock 更为合理。二 案例分析2.1 环境准备 Percona server 5.6 RR模式 CREATE TABLE `t6` ( `id` int(11) NOT NULL AUTO_INCREMENT, `a` int(11) DEFAULT NULL, PRIMARY KEY (`id`), unique KEY `idx_a` (`a`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4; insert into t6 values(1,2),(2,8),(3,9),(4,11),(5,19) sess1 sess2 sess3 begin; insert into t6(id,a) values(6,15); begin; insert into t6(id,a) values(7,15); begin; insert into t6(id,a) values(8,15); rollback; ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction 2.2 死锁日志 ------------------------ LATEST DETECTED DEADLOCK ------------------------ 2017-09-18 10:03:50 7f78eae30700 *** (1) TRANSACTION: TRANSACTION 462308725, ACTIVE 18 sec inserting, thread declared inside InnoDB 1 mysql tables in use 1, locked 1 LOCK WAIT 4 lock struct(s), heap size 1184, 2 row lock(s), undo log entries 1 MySQL thread id 3825465, OS thread handle 0x7f78eaef4700, query id 781148519 localhost root update insert into t6(id,a) values(7,15) *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308725 lock_mode X insert intention waiting *** (2) TRANSACTION: TRANSACTION 462308726, ACTIVE 10 sec inserting, thread declared inside InnoDB 1 mysql tables in use 1, locked 1 4 lock struct(s), heap size 1184, 2 row lock(s), undo log entries 1 MySQL thread id 3825581, OS thread handle 0x7f78eae30700, query id 781148528 localhost root update insert into t6(id,a) values(8,15) *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308726 lock mode S *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308726 lock_mode X insert intention waiting *** WE ROLL BACK TRANSACTION (2) 2.3 死锁分析首先依然要再次强调insert 插入操作的加锁逻辑。第一阶段: 唯一性约束检查,先申请LOCK_S + LOCK_ORDINARY第二阶段: 获取阶段一的锁并且insert成功之后,插入的位置有Gap锁:LOCK_INSERT_INTENTION,为了防止其他insert唯一键冲突。 新数据插入:LOCK_X + LOCK_REC_NOT_GAP对于insert操作来说,若发生唯一约束冲突,则需要对冲突的唯一索引加上S Next-key Lock。从这里会发现,即使是RC事务隔离级别,也同样会存在Next-Key Lock锁,从而阻塞并发。然而,文档没有说明的是,对于检测到冲突的唯一索引,等待线程在获得S Lock之后,还需要对下一个记录进行加锁,在源码中由函数row_ins_scan_sec_index_for_duplicate进行判断.其次 我们需要了解 锁的兼容性矩阵。 从兼容性矩阵我们可以得到如下结论: INSERT操作之间不会有冲突。 GAP,Next-Key会阻止Insert。 GAP和Record,Next-Key不会冲突 Record和Record、Next-Key之间相互冲突。 已有的Insert锁不阻止任何准备加的锁。 这个案例是三个会话并发执行的,我打算一步一步来分析每个步骤执行完之后的事务日志。第一步 sess1 执行插入操作 insert into t6(id,a) values(6,15); ---TRANSACTION 462308737, ACTIVE 5 sec 1 lock struct(s), heap size 360, 0 row lock(s), undo log entries 1 MySQL thread id 3825779, OS thread handle 0x7f78eacd9700, query id 781149440 localhost root init show engine innodb status TABLE LOCK table `test`.`t6` trx id 462308737 lock mode IX 因为第一个插入的语句,所以唯一性冲突检查通过,成功插入(6,15). 此时sess1 会话持有(6,15)的LOCK_X|LOCK_REC_NOT_GAP锁。参考"INSERT sets an exclusive lock on the inserted row. This lock is an index-record lock, not a next-key lock (that is, there is no gap lock) and does not prevent other sessions from inserting into the gap before the inserted row."第二步 sess2 执行插入操作 insert into t6(id,a) values(7,15); ---TRANSACTION 462308738, ACTIVE 4 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1 MySQL thread id 3825768, OS thread handle 0x7f78ea9c9700, query id 781149521 localhost root update insert into t6(id,a) values(7,15) ------- TRX HAS BEEN WAITING 4 SEC FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S waiting ------------------ TABLE LOCK table `test`.`t6` trx id 462308738 lock mode IX RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S waiting ---TRANSACTION 462308737, ACTIVE 66 sec 2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1 MySQL thread id 3825779, OS thread handle 0x7f78eacd9700, query id 781149526 localhost root init show engine innodb status TABLE LOCK table `test`.`t6` trx id 462308737 lock mode IX RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308737 lock_mode X locks rec but not gap 首先sess2的insert 申请了IX锁,因为sess1 会话已经插入成功并且持有唯一键 a=15的X 行锁 ,故而sess2 insert 进行唯一性检查,先申请LOCK_S + LOCK_ORDINARY ,事务日志列表中提示lock mode S waiting第三部 sess3 执行插入操作 insert into t6(id,a) values(8,15); ---TRANSACTION 462308739, ACTIVE 3 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1 MySQL thread id 3825764, OS thread handle 0x7f78ea593700, query id 781149555 localhost root update insert into t6(id,a) values(8,15) ------- TRX HAS BEEN WAITING 3 SEC FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308739 lock mode S waiting ------------------ TABLE LOCK table `test`.`t6` trx id 462308739 lock mode IX RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308739 lock mode S waiting ---TRANSACTION 462308738, ACTIVE 35 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1 MySQL thread id 3825768, OS thread handle 0x7f78ea9c9700, query id 781149521 localhost root update insert into t6(id,a) values(7,15) ------- TRX HAS BEEN WAITING 35 SEC FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S waiting ------------------ TABLE LOCK table `test`.`t6` trx id 462308738 lock mode IX RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S waiting ---TRANSACTION 462308737, ACTIVE 97 sec 2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1 MySQL thread id 3825779, OS thread handle 0x7f78eacd9700, query id 781149560 localhost root init show engine innodb status TABLE LOCK table `test`.`t6` trx id 462308737 lock mode IX RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308737 lock_mode X locks rec but not gap 与会话sess2 的加锁申请流程一致,都在等待sess1释放锁资源。第四步 sess1 执行回滚操作,sess2 不提交 sess1 rollback; 此时sess2 插入成功,sess3出现死锁,此时sess2 insert插入成功,还未提交,事务列表如下: ------------ TRANSACTIONS ------------ Trx id counter 462308744 Purge done for trx s n:o < 462308744 undo n:o < 0 state: running but idle History list length 1866 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 462308737, not started MySQL thread id 3825779, OS thread handle 0x7f78eacd9700, query id 781149626 localhost root init show engine innodb status ---TRANSACTION 462308739, not started MySQL thread id 3825764, OS thread handle 0x7f78ea593700, query id 781149555 localhost root cleaning up ---TRANSACTION 462308738, ACTIVE 75 sec 5 lock struct(s), heap size 1184, 3 row lock(s), undo log entries 1 MySQL thread id 3825768, OS thread handle 0x7f78eadce700, query id 781149608 localhost root cleaning up TABLE LOCK table `test`.`t6` trx id 462308738 lock mode IX RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock_mode X insert intention RECORD LOCKS space id 227 page no 4 n bits 80 index `idx_a` of table `test`.`t6` trx id 462308738 lock mode S locks gap before rec 死锁的原因 sess1 insert成功并针对a=15的唯一键加上X锁。 sess2 执行insert 插入(6,15), 在插入之前进行唯一性检查发现和sess1的已经插入的记录重复键需要申请LOCK_S|LOCK_ORDINARY, 但与sess1 的(LOCK_X | LOCK_REC_NOT_GAP)冲突,加入等待队列,等待sess1 释放锁。 sess3 执行insert 插入(7,15), 在插入之前进行唯一性检查发现和sess1的已经插入的记录重复键需要申请LOCK_S|LOCK_ORDINARY, 但与sess1 的(LOCK_X | LOCK_REC_NOT_GAP)冲突,加入等待队列,等待sess1 释放锁。 sess1 执行rollback, sess1 释放索引a=15 上的排他记录锁(LOCK_X | LOCK_REC_NOT_GAP),此后 sess2和sess3 获得S锁(LOCK_S|LOCK_ORDINARY)成功,sess2和sess3都要请求索引a=15上的排他记录锁(LOCK_X | LOCK_REC_NOT_GAP),日志中提示 lock_mode X insert intention。由于X锁与S锁互斥,sess2和sess3都等待对方释放S锁,于是出现死锁,MySQL 选择回滚其中之一。四 总结 死锁分析是已经很有挑战的事情,尤其对于insert 唯一键冲突,要分多个阶段去申请,也要理解锁的兼容矩阵。对于这块我还有需要在学习了解的知识点,本文算是抛砖引玉,如有分析理解不正确的地方,望大家指正。 推荐文章 《MySQL insert 锁机制》 《insert into 加锁机制》
一 前言 死锁,其实是一个很有意思,也很有挑战的技术问题,大概每个DBA和部分开发同学都会在工作过程中遇见过 。关于死锁我会持续写一个系列的案例分析,希望能够对想了解死锁的朋友有所帮助。二 案例分析2.1 环境说明MySQL 5.6 事务隔离级别为RR CREATE TABLE `ty` ( `id` int(11) NOT NULL AUTO_INCREMENT, `a` int(11) DEFAULT NULL, `b` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idxa` (`a`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 insert into ty(a,b) values(2,3),(5,4),(6,7); 2.2 测试用例 T2 T1 begin; delete from ty where a=5; begin; delete from ty where a=5; insert into ty(a,b) values(2,10); delete from ty where a=5; ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction 2.3 死锁日志 ------------------------ LATEST DETECTED DEADLOCK ------------------------ 2017-09-09 22:34:13 7f78eab82700 *** (1) TRANSACTION: TRANSACTION 462308399, ACTIVE 33 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s) MySQL thread id 3525577, OS thread handle 0x7f896cc4b700, query id 780039657 localhost root updating delete from ty where a=5 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 219 page no 4 n bits 72 index `idxa` of table `test`.`ty` trx id 462308399 lock_mode X waiting *** (2) TRANSACTION: TRANSACTION 462308398, ACTIVE 61 sec inserting, thread declared inside InnoDB 5000 mysql tables in use 1, locked 1 5 lock struct(s), heap size 1184, 4 row lock(s), undo log entries 2 MySQL thread id 3525490, OS thread handle 0x7f78eab82700, query id 780039714 localhost root update insert into ty(a,b) values(2,10) *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 219 page no 4 n bits 72 index `idxa` of table `test`.`ty` trx id 462308398 lock_mode X *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 219 page no 4 n bits 72 index `idxa` of table `test`.`ty` trx id 462308398 lock_mode X locks gap before rec insert intention waiting *** WE ROLL BACK TRANSACTION (1) 2.3分析死锁日志首先要理解的是 对同一个字段申请加锁是需要排队. S GAP 于其次表ty中a为普通索引字段,我们根据事务执行的时间顺序来解释,这样比较好理解。a 根据死锁日志显示 事务2 也即sess1执行的事务,根据 HOLDS THE LOCK(S)显示 sess1 先执行 delete from ty where a=5 ,该事务持有索引a=5 的行锁lock_mode X ,因为是RR隔离级别,所以sess1 还持有两个gap锁[1,2]-[2,5], [2,5]-[3,6] 。b 事务1的日志也即sess2执行的事务,申请对 a=5 加锁,一个rec lock 和两个gap锁,因为sess1中delete还没释放,故sess2的事务1等待sess1的事务2释放a=5的锁资源。c 然后根据WAITING FOR THIS LOCK TO BE GRANTED,提示事务2 insert语句正在等待 lock_mode X locks gap before rec insert intention waiting,因为insert语句 [4,2] 介于gap锁[1,2]-[2,5]之间,所以有了提示 "lock_mode X locks gap",insert语句必须等待前面 sess2中delete 获取锁并且释放锁。于是,sess2(delete) 等待sess1(delete) ,sess1(insert)等待sess2(delete),循环等待,造成死锁。问题 如果sess1 执行 insert into ty(a,b) values(5,10); sess2会遇到死锁吗?三 案例二3.1 索引为唯一键MySQL 5.6 事务隔离级别为RR CREATE TABLE `t2` ( `id` int(11) NOT NULL AUTO_INCREMENT, `a` int(11) DEFAULT NULL, `b` int(11) DEFAULT NULL, PRIMARY KEY (`id`), unique KEY `idxa` (`a`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4; insert into t2(a,b) values(2,3),(5,4),(6,7) 3.2 测试用例 T2 T1 begin; delete from ty where a=5; begin; delete from ty where a=5; insert into ty(a,b) values(2,10); delete from ty where a=5; ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction 3.3 死锁日志 ------------------------ LATEST DETECTED DEADLOCK ------------------------ 2017-09-10 00:03:31 7f78ea936700 *** (1) TRANSACTION: TRANSACTION 462308445, ACTIVE 9 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s) MySQL thread id 3526009, OS thread handle 0x7f896cc4b700, query id 780047877 localhost root updating delete from t2 where a=5 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 221 page no 4 n bits 72 index `idxa` of table `test`.`t2` trx id 462308445 lock_mode X waiting *** (2) TRANSACTION: TRANSACTION 462308444, ACTIVE 17 sec inserting, thread declared inside InnoDB 5000 mysql tables in use 1, locked 1 4 lock struct(s), heap size 1184, 3 row lock(s), undo log entries 2 MySQL thread id 3526051, OS thread handle 0x7f78ea936700, query id 780047890 localhost root update insert t2(a,b) values(5,10) *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 221 page no 4 n bits 72 index `idxa` of table `test`.`t2` trx id 462308444 lock_mode X locks rec but not gap *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 221 page no 4 n bits 72 index `idxa` of table `test`.`t2` trx id 462308444 lock mode S waiting *** WE ROLL BACK TRANSACTION (1) 3.4 分析死锁日志首先我们要特别说明delete的加锁逻辑 a 找到满足条件的记录,并且记录有效,则对记录加X锁,No Gap锁(lock_mode X locks rec but not gap); b 找到满足条件的记录,但是记录无效(标识为删除的记录),则对记录加next key锁(同时锁住记录本身,以及记录之前的Gap:lock_mode X); c 未找到满足条件的记录,则对第一个不满足条件的记录加Gap锁,保证没有满足条件的记录插入(locks gap before rec) 其次需要大家注意的是对比两个死锁案例会发现,sess1 事务持有的锁类型发生了变化 delete持有的锁变为lock_mode X locks rec but not gap 。 insert语句持有的锁变为 lock mode S waiting。原因是因为测试表结构发生了变化字段a由普通索引变为唯一键,RR模式下对唯一键操作是没有gap锁的,而且insert 写入含有唯一键的数据是会申请GAP锁的特殊情况 Insert Intention Lock.本例我们依然根据事务执行的时间顺序来解释,这样比较好理解。a 根据死锁日志显示 事务2 也即sess1执行的事务,根据 HOLDS THE LOCK(S)显示sess1 先执行 delete from ty where a=5 ,该事务持有索引a=5 的行锁lock_mode X locks rec but not gap。因为本例中a是唯一键,故没有gap锁。b 事务1的日志也即sess2执行的事务,申请对 a=5 加锁(X Next-key Lock),一个rec lock 但是因为sess1中delete 已经执行完成,记录无效没有被删除,锁还没释放,故sess2的事务1等待sess1的事务2释放a=5的锁资源,日志中提示 lock_mode X waiting. c 然后根据WAITING FOR THIS LOCK TO BE GRANTED,提示事务2 insert语句正在等待 lock mode S waiting,为什么这次是 S 锁呢?因为a字段是一个唯一索引,所以insert语句会在插入前进行一次duplicate key的检查,需要申请S锁防止其他事务对a字段进行重复插入。而插入意向锁与T1已经insert语句必须等待前面 sess2中delete 获取a=5的行锁并且释放锁。于是,sess2(delete) 等待sess1(delete) ,sess1(insert)等待sess2(delete),循环等待,造成死锁。四 小结 本文研究了RR事务隔离级别下,普通索引与唯一键两种情况的死锁场景。如何避免解决此类死锁?推荐使用RC隔离级别+ ROW BASE BINLOG . 但是对于RC/RR模式下 ,insert 遇到唯一键冲突的时候的死锁不可避免。需要开发在设计表结构的时候 减少unique 索引设计。推荐文章 《不同语句模式下的锁类型》如果您觉得能从本文收益,可以请北在南方一瓶饮料 ^_^
一 前言 死锁,其实是一个很有意思,也很有挑战的技术问题,大概每个DBA都会在工作过程中遇见过 。关于死锁我会持续写一个系列的案例分析,希望能够对想了解死锁的朋友有所帮助。本文源于我们的生产案例:并发申请gap锁导致的死锁案例,与之前的 死锁案例一不同,本案例是因为RR模式下两个事务中的sql可以获取同一个gap锁,导致对方事务的insert 相互等待,导致死锁的。二 案例分析测试环境准备Percona server 5.6.24 事务隔离级别为RR CREATE TABLE `t4` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT , `kdt_id` int(11) unsigned NOT NULL , `admin_id` int(11) unsigned NOT NULL , `biz` varchar(20) NOT NULL DEFAULT '1' , `role_id` int(11) unsigned NOT NULL , `shop_id` int(11) unsigned NOT NULL DEFAULT '0' , `operator` varchar(20) NOT NULL DEFAULT '0' , `operator_id` int(11) NOT NULL DEFAULT '0' , `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uniq_kid_aid_biz_rid` (`kdt_id`,`admin_id`,`role_id`,`biz`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; INSERT INTO `t4` (`id`, `kdt_id`, `admin_id`, `biz`, `role_id`, `shop_id`, `operator`, `operator_id`, `create_time`, `update_time`) VALUES (1,10,1,'retail',1,0,'0',0,'2017-05-09 15:55:26','2017-05-09 15:55:26'), (2,20,1,'retail',1,0,'0',0,'2017-05-09 15:55:40','2017-05-09 15:55:40'), (3,30,1,'retail',1,0,'0',0,'2017-05-09 15:55:55','2017-05-09 15:55:55'), (4,40,1,'retail',1,0,'0',0,'2017-05-09 15:56:06','2017-05-09 15:56:06'), (5,50,1,'retail',1,0,'0',0,'2017-05-09 15:56:16','2017-05-09 15:56:16'); 2.2 测试用例 本测试案例场景是两个事务删除不存的行,然后在insert记录。 T2 T1 test [RW] 02:50:27 >begin; Query OK, 0 rows affected (0.00 sec) test [RW] 02:50:27 >begin; Query OK, 0 rows affected (0.00 sec) test [RW] 02:50:34 >delete from t4 where kdt_id = 15 and admin_id = 1 and biz = 'retail' and role_id = '1'; test [RW] 02:50:41 >delete from t4 where kdt_id = 18 and admin_id = 2 and biz = 'retail' and role_id = '1'; test [RW] 02:50:43 >insert into t4(`kdt_id`, `admin_id`, `biz`, `role_id`, `shop_id`, `operator`, `operator_id`, `create_time`, `update_time`) -> VALUES('18', '2', 'retail', '2', '0', '0', '0', CURRENT_TIMESTAMP,CURRENT_TIMESTAMP); test [RW] 02:51:02 >INSERT INTO t4(`kdt_id`, `admin_id`, `biz`, `role_id`, `shop_id`, `operator`, `operator_id`, `create_time`, `update_time`) -> VALUES ('15', '1', 'retail', '2', '0', '0', '0', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction 2.3 死锁日志 ------------------------ LATEST DETECTED DEADLOCK ------------------------ 2017-09-11 14:51:03 7f78eaf25700 *** (1) TRANSACTION: TRANSACTION 462308535, ACTIVE 20 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 3 lock struct(s), heap size 360, 2 row lock(s), undo log entries 1 MySQL thread id 3584515, OS thread handle 0x7f78ea5f5700, query id 780258123 localhost root update insert into t4(`kdt_id`, `admin_id`, `biz`, `role_id`, `shop_id`, `operator`, `operator_id`, `create_time`, `update_time`) VALUES('18', '2', 'retail', '2', '0', '0', '0', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 225 page no 4 n bits 72 index `uniq_kid_aid_biz_rid` of table `test`.`t4` trx id 462308535 lock_mode X locks gap before rec insert intention waiting *** (2) TRANSACTION: TRANSACTION 462308534, ACTIVE 29 sec inserting, thread declared inside InnoDB 5000 mysql tables in use 1, locked 1 3 lock struct(s), heap size 360, 2 row lock(s), undo log entries 1 MySQL thread id 3584572, OS thread handle 0x7f78eaf25700, query id 780258153 localhost root update INSERT INTO t4(`kdt_id`, `admin_id`, `biz`, `role_id`, `shop_id`, `operator`, `operator_id`, `create_time`, `update_time`) VALUES ('15', '1', 'retail', '2', '0', '0', '0', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 225 page no 4 n bits 72 index `uniq_kid_aid_biz_rid` of table `test`.`t4` trx id 462308534 lock_mode X locks gap before rec *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 225 page no 4 n bits 72 index `uniq_kid_aid_biz_rid` of table `test`.`t4` trx id 462308534 lock_mode X locks gap before rec insert intention waiting *** WE ROLL BACK TRANSACTION (2) 2.4 死锁日志分析 首先根据《死锁案例一》 和《一个最不可思议的MySQL死锁分析》中强调 delete 不存在的记录是要加上GAP锁,事务日志中显示Lock_mode X wait .a T2 delete from t4 where kdt_id = 15 and admin_id = 1 and biz = 'retail' and role_id = '1'; 符合条件的记录不存在,导致T2 先持有了(lock_mode X locks gap before rec) 锁住[(2,20,1,'retail',1,0)-(3,30,1,'retail',1,0)]的区间 ,防止符合条件的记录插入。b T1的delete 于T2的delete一样 同样申请了 (lock_mode X locks gap before rec) 锁住[(2,20,1,'retail',1,0)-(3,30,1,'retail',1,0)]的区间 。 It is also worth noting here that conflicting locks can be held on a gap by different transactions. For example, transaction A can hold a shared gap lock (gap S-lock) on a gap while transaction B holds an exclusive gap lock (gap X-lock) on the same gap. The reason conflicting gap locks are allowed is that if a record is purged from an index, the gap locks held on the record by different transactions must be merged. c T1 的insert 语句申请插入意向锁,但是插入意向锁和T2持有的X GAP (lock_mode X locks gap before rec) 冲突,故等待T2中的GAP 锁释放。 Gap locks in InnoDB are “purely inhibitive”, which means they only stop other transactions from inserting to the gap. They do not prevent different transactions from taking gap locks on the same gap. Thus, a gap X-lock has the same effect as a gap S-lock. d T2 的insert 语句申请插入意向锁,但是插入意向锁和T1持有 X GAP (lock_mode X locks gap before rec) 冲突,故等待T1中的GAP 锁释放。T1(INSERT )等待T2(DELETE),T2(INSERT)等待T1(DELETE) 故而循环等待,出现死锁。有兴趣的读者朋友可以测试一下 delete 存在记录的场景。2.6 如何解决呢? a 先select 检查一下看看是否存在,然后在删除。这里也存在两个或者多个会话并发执行同一个select where条件的,这里需要开发同学做处理。 b insert into on deuplicate key .三 小结 RR事务隔离级别和GAP锁是导致死锁的常见原因,但是业务逻辑设计不合理也会出发死锁,本文的案例通过修改业务逻辑最终将死锁解决。如果您觉得能从本文收益,可以请北在南方一瓶饮料 ^_^
一 前言 MySQL 在不同的事务隔离级别下提供两种读模式 一致性读(非加锁), 当前读(加锁读)。当前读比较简单,本文主要研究一致性读取。 二 原理概念官方概念 "A consistent read means that InnoDB uses multi-versioning to present to a query a snapshot of the database at a point in time. The query sees the changes made by transactions that committed before that point of time, and no changes made by later or uncommitted transactions.The exception to this rule is that the query sees the changes made by earlier statements within the same transaction. " 一致性读是指使用MVCC机制读取到某个事务已经提交的数据,其实是从undo里面获取的数据快照。不过也有特例: 在本事务内如果修改某个表之后的select 可以读取到该表最新的数据,后面的例子可以验证。 三 不同事务隔离级别的一致性读 3.1 RR模式从官方文档 "If the transaction isolation level is REPEATABLE READ (the default level), all consistent reads within the same transaction read the snapshot established by the first such read in that transaction." 在RR模式下,同一个事务内的一致性读的快照都是基于第一次读取操作时所建立的。下面我们做测试进行对RR模式下一致性读进行解读。a)RR模式下事务的起始点是以执行的第一条语句为起始点的,而不是以begin作为事务的起始点的。 session 1 session2 test [RW] 10:01:33 >begin; Query OK, 0 rows affected (0.00 sec) test [RW] 10:02:12 >begin; Query OK, 0 rows affected (0.00 sec) test [RW] 10:02:22 >select * from ty; Empty set (0.00 sec) test [RW] 10:02:36 >insert into ty(a,b) values(1,2); Query OK, 1 row affected (0.00 sec) test [RW] 10:02:51 >commit; Query OK, 0 rows affected (0.00 sec) test [RW] 10:02:33 >select * from ty; +----+------+------+ | id | a | b | +----+------+------+ | 1 | 1 | 2 | +----+------+------+ 1 row in set (0.00 sec) b)RR模式下的一致性读,是以第一条select语句的执行时间点作为snapshot建立的时间点的,即使是访问不同的表。 test [RW] 10:35:11 >begin; Query OK, 0 rows affected (0.00 sec) test [RW] 10:35:13 >select * from x; +----+ | id | +----+ | 1 | | 2 | +----+ 2 rows in set (0.00 sec) test [RW] 10:34:32 >begin; Query OK, 0 rows affected (0.00 sec) test [RW] 10:34:51 >insert into ty(a,b) values(2,4); Query OK, 1 row affected (0.00 sec) test [RW] 10:35:39 >select * from ty; +----+------+------+ | id | a | b | +----+------+------+ | 1 | 1 | 2 | +----+------+------+ 1 row in set (0.00 sec) c)RR模式下,在本事务内如果修改某个表之后的对该表的select语句可以读取到该表最新的数据。 test [RW] 10:42:56 >begin; Query OK, 0 rows affected (0.00 sec) test [RW] 10:43:07 >select * from ty; +----+------+------+ | id | a | b | +----+------+------+ | 1 | 1 | 2 | | 2 | 2 | 4 | +----+------+------+ 2 rows in set (0.00 sec) test [RW] 10:35:34 >begin; Query OK, 0 rows affected (0.00 sec) test [RW] 10:43:25 >insert into ty(a,b) values(3,5); Query OK, 1 row affected (0.00 sec) test [RW] 10:43:38 >select * from ty; +----+------+------+ | id | a | b | +----+------+------+ | 1 | 1 | 2 | | 2 | 2 | 4 | | 3 | 3 | 5 | +----+------+------+ 3 rows in set (0.00 sec) test [RW] 10:43:14 >update ty set a = 5 where id=3; Query OK, 1 row affected (4.23 sec) Rows matched: 1 Changed: 1 Warnings: 0 test [RW] 10:44:30 >select * from ty; +----+------+------+ | id | a | b | +----+------+------+ | 1 | 1 | 2 | | 2 | 2 | 4 | | 3 | 5 | 5 | +----+------+------+ 3 rows in set (0.00 sec) d)RR模式下同一个事务内,第一次查询是当前读操作则后续查询可以查看最新的数据。 test [RW] 11:07:23 >begin; Query OK, 0 rows affected (0.00 sec) test [RW] 11:07:26 >update ty set a=5 where id=2; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 test [RW] 11:07:31 >begin; Query OK, 0 rows affected (0.00 sec) test [RW] 11:07:33 >select * from ty where id=2 for update; +----+------+------+ | id | a | b | +----+------+------+ | 2 | 5 | 4 | +----+------+------+ 1 row in set (10.73 sec) test [RW] 11:07:36 >insert into ty(a,b) values(6,7); Query OK, 1 row affected (0.00 sec) test [RW] 11:07:55 >commit; Query OK, 0 rows affected (0.00 sec) test [RW] 11:07:58 >select * from ty; +----+------+------+ | id | a | b | +----+------+------+ | 1 | 2 | 3 | | 2 | 5 | 4 | | 3 | 6 | 7 | <-- 本事务还未commit,已经可以查看其他会话最新插入的数据 +----+------+------+ 3 rows in set (0.00 sec) 这个例子要特别说明一下,从上面的实验结果上来看RR事务隔离级别下,一致性快照的建立仅仅和select语句第一次执行有关,不管是不是相关表。而且与其他 DML(insert,update,delete) 语句、for update 语句无关,在事务中第一次执行DML,后面的其他select 查询会是当前读即可获取到最新的数据。 3.2 RC模式 RC 支持在本事务内读取到最新提交的数据,所以RC 事务隔离级别下的一致性读取比RR模式下的要简单很多。每个事务构建自己的快照,不相互干扰,除非其他事务已经提交,有兴趣的朋友自己测试吧。 With READ COMMITTED isolation level, each consistent read within a transaction sets and reads its own fresh snapshot. 四 当前读 和一致性读不太一样 ,当前读需要使用select xx for update,或者 lock in share mode ,读取最新的数据并且锁定被访问的行,(RC 加行锁,RR加gap锁 唯一键除外) 不管另外一个事务是否提交,如果另外的事务已经获取了相关的锁,则 for update,lock in share mode 语句则继续等待直到其他事务释放锁,并且获取到最新的数据。 五 小结 从上面的测试来看,RR模式下的一致性快照读会有比较多的特性(姑且叫做特性吧) 。RC模式本身支持不可重复读,能够查询到最新的其他事务最新提交的数据。基于上面的测试,还是比较推荐业务使用RC模式作为事务隔离级别的。参考文章[1] 一致性读深入研究[2] 官方文档
一 前言 Percona公司发布 innobackup 2.4 版本已经很久了,增加了新的特性比如xtrabackup支持非Innodb表备份,指定 --safe-slave-backup,增加备份的一致性,最重要的一点是支持5.7的备份,2.2是不能备份5.7 版本的。 因此我们决定将我们的percona的pt工具和备份软件更新到最新版本。本文主要记录我们使用 2.4 版本过程中遇到的问题和之前的一些改变。二 问题和差异2.1 backup-my.cnf 文件innobackup 2.4版本比 之前的版本多了几个参数 2.2版本的内容 [mysqld] innodb_checksum_algorithm=innodb innodb_log_checksum_algorithm=innodb innodb_data_file_path=ibdata1:12M:autoextend innodb_log_files_in_group=2 innodb_log_file_size=1073741824 innodb_page_size=16384 innodb_log_block_size=512 innodb_undo_directory=. innodb_undo_tablespaces=0 2.4 版本的内容 [mysqld] innodb_checksum_algorithm=innodb innodb_log_checksum_algorithm=innodb innodb_data_file_path=ibdata1:12M:autoextend innodb_log_files_in_group=2 innodb_log_file_size=1073741824 innodb_page_size=16384 innodb_log_block_size=512 innodb_undo_directory=. innodb_undo_tablespaces=0 server_id=0 # 2.4 新增参数 redo_log_version=0 # 2.4 新增参数 innodb_fast_checksum=false # 2.4 新增参数 这里强调一下 innodb_fast_checksum ,在applay log 之后依赖backup-my.cnf 启动MySQL的时候 5.6 是不能识别该参数的,导致启动失败。[ERROR] mysqld: unknown variable 'innodb_fast_checksum=0' 来看看2014年 相关的bug 说法 “Or maybe a separate feature request should be opened to copy the whole my.cnf to the backup directory as well. I will leave that up to others to decide.” 都3年了,都没有得出什么有效的结果。。 其他地方的讨论,其实可以直接关闭。 https://dba.stackexchange.com/questions/6386/is-there-any-reason-not-to-use-percona-innodb-fast-checksum2.2 场景 由于历史原因,我们还有部分数据库是是基于 mysqld_multi 做单机多实例的。这种单机多实例的配置文件有两种 /etc/my.cnf 和 /path/my.multi.cnf 两个配置文件。my.multi.cnf 文件里面配置了实例级别的个性参数。比如 [mysqld_multi] mysqld=/usr/bin/mysqld_safe mysqladmin=/usr/bin/mysqladmin user=mysql log=/data/multi.log [3306] port = 3306 datadir=/data/my3306 socket=/data/my3306/mysql.sock user=mysql pid-file=/data/my3306/mysql.pid log=/data/my3306/mysqld.log [3307] port = 3307 datadir=/data/my3307 socket=/data/my3307/mysql.sock user=mysql pid-file=/data/my3307/mysql.pid log=/data/my3307/mysqld.log innobackup 2.4 在备份时会去读 /etc/my.cnf ,如果该文件中没有配置server_id 则系统报错失败。如果没有/etc/my.cnf 则会去获取数据库实例配置的my.cnf 而不是 my.multi.cnf .. innobackupex: [ERROR] /usr/bin/innobackupex: Empty value for 'server-id' specified 解决方法回退到老的版本。2.3 备份集文件内容的变化 我们的备份命令如下: /usr/bin/innobackupex --socket=/srv/my_3344/mysqld.sock --user=root --password= --no-timestamp --slave-info --rsync --compress --compress-threads=2 --parallel=1 /data/backup/rac1_3344/full/bk20170827105656 >/data/logs/zandb_agent/backup/rac1_3344_bk20170827105656.log 2>&1 使用了 compress 功能, 2.2版本的备份集压缩了数据库相关的数据文件 2.4版本的备份集文件 对自动化备份系统的影响是需要调整读取backup-my.cnf的步骤,必须在解压缩之后读取。三 小结 这里例举了我们在使用新版本的备份软件遇到的问题,给其他准备使用的同行一些借鉴,也欢迎大家补充其他我们还没遇到的问题。
一 前言 这篇文章源于自己一个无知的提问,作为一个DBA 老鸟,实在汗颜 。如图,修改wait_timeout参数之后 并没有及时生效,于是乎去跑到技术支持群里问了。。ps 应该去查g.cn 才对。。 本文通过测试我们要弄清楚两个问题 a 继承关系 wait_timeout在session和global级别分别继承那个参数? b 生效参数 在会话中到底哪个参数决定了会话的存活时间?二 参数介绍 首先说明两个关键词 通过MySQL 客户端连接db的是交互会话,通过jdbc等程序连接db的是非交互会话。 interactive_timeout: MySQL服务器关闭交互式连接前等待的秒数。交互式客户端定义为在mysql_real_connect()中使用CLIENT_INTERACTIVE选项的客户端。参数默认值:28800秒(8小时) wait_timeout: MySQL服务器关闭非交互连接之前等待的秒数。在会话启动时,根据全局wait_timeout值或全局interactive_timeout值初始化会话wait_timeout值,取决于客户端类型--由mysql_real_connect()的连接选项CLIENT_INTERACTIVE定义。参数默认值:28800秒(8小时)2.1 继承关系 1) 单独设置global级别的interactive_timeout set global interactive_timeout = 300 session1 [RO] 09:34:20 >set global interactive_timeout=300; Query OK, 0 rows affected (0.00 sec) session1 [RO] 09:39:15 >select variable_name,variable_value from information_schema.global_variables where variable_name in ('interactive_timeout','wait_timeout'); +---------------------+----------------+ | variable_name | variable_value | +---------------------+----------------+ | INTERACTIVE_TIMEOUT | 300 | | WAIT_TIMEOUT | 28800 | +---------------------+----------------+ 2 rows in set (0.00 sec) session1 [RO] 09:39:21 >select variable_name,variable_value from information_schema.session_variables where variable_name in ('interactive_timeout','wait_timeout'); +---------------------+----------------+ | variable_name | variable_value | +---------------------+----------------+ | INTERACTIVE_TIMEOUT | 300 | | WAIT_TIMEOUT | 28800 | +---------------------+----------------+ 2 rows in set (0.00 sec) 登陆另外一个会话 session2 [RO] 09:39:35 >select variable_name,variable_value from information_schema.global_variables where variable_name in ('interactive_timeout','wait_timeout'); +---------------------+----------------+ | variable_name | variable_value | +---------------------+----------------+ | INTERACTIVE_TIMEOUT | 300 | | WAIT_TIMEOUT | 28800 | +---------------------+----------------+ 2 rows in set (0.00 sec) session2 [RO] 09:39:51 >select variable_name,variable_value from information_schema.session_variables where variable_name in ('interactive_timeout','wait_timeout'); +---------------------+----------------+ | variable_name | variable_value | +---------------------+----------------+ | INTERACTIVE_TIMEOUT | 300 | | WAIT_TIMEOUT | 300 | +---------------------+----------------+ 2 rows in set (0.00 sec) 分析 在交互模式下,session和global级别的 interactive_timeout 继承了 interactive_timeout global的值。而 wait_timeout 的值,session级别继承了interactive_timeout。global级别的wait_timeout 则不受影响 。2) 设置session级别的 interactive_timeout session1 [RO] 09:44:07 >set session interactive_timeout=300; Query OK, 0 rows affected (0.00 sec) session1 [RO] 09:44:27 >select variable_name,variable_value from information_schema.global_variables where variable_name in ('interactive_timeout','wait_timeout'); +---------------------+----------------+ | variable_name | variable_value | +---------------------+----------------+ | INTERACTIVE_TIMEOUT | 28800 | | WAIT_TIMEOUT | 28800 | +---------------------+----------------+ 2 rows in set (0.00 sec) session1 [RO] 09:44:31 >select variable_name,variable_value from information_schema.session_variables where variable_name in ('interactive_timeout','wait_timeout'); +---------------------+----------------+ | variable_name | variable_value | +---------------------+----------------+ | INTERACTIVE_TIMEOUT | 300 | | WAIT_TIMEOUT | 28800 | +---------------------+----------------+ 2 rows in set (0.00 sec) 另外开启一个会话 session2 [RO] 09:44:41 >select variable_name,variable_value from information_schema.session_variables where variable_name in ('interactive_timeout','wait_timeout'); +---------------------+----------------+ | variable_name | variable_value | +---------------------+----------------+ | INTERACTIVE_TIMEOUT | 28800 | | WAIT_TIMEOUT | 28800 | +---------------------+----------------+ 2 rows in set (0.01 sec) session2 [RO] 09:44:44 >select variable_name,variable_value from information_schema.global_variables where variable_name in ('interactive_timeout','wait_timeout'); +---------------------+----------------+ | variable_name | variable_value | +---------------------+----------------+ | INTERACTIVE_TIMEOUT | 28800 | | WAIT_TIMEOUT | 28800 | +---------------------+----------------+ 2 rows in set (0.00 sec) 分析 从上面的例子来看 wait_timeout 并不受session级别的interactive_timeout的值的影响。 3) 同时设置两者的值,且不同。 session1 [RO] 09:46:42 > (none) [RO] 09:46:42 >set global interactive_timeout=300; Query OK, 0 rows affected (0.00 sec) session1 [RO] 09:46:55 >set global wait_timeout=360; Query OK, 0 rows affected (0.00 sec) 另开启一个会话 session2 [RO] 09:47:20 >select variable_name,variable_value from information_schema.session_variables where variable_name in ('interactive_timeout','wait_timeout'); +---------------------+----------------+ | variable_name | variable_value | +---------------------+----------------+ | INTERACTIVE_TIMEOUT | 300 | | WAIT_TIMEOUT | 300 | +---------------------+----------------+ 2 rows in set (0.00 sec) session2 [RO] 09:47:22 >select variable_name,variable_value from information_schema.global_variables where variable_name in ('interactive_timeout','wait_timeout'); +---------------------+----------------+ | variable_name | variable_value | +---------------------+----------------+ | INTERACTIVE_TIMEOUT | 300 | | WAIT_TIMEOUT | 360 | +---------------------+----------------+ 2 rows in set (0.00 sec) 分析 从案例1 2中可以得出session级别的wait_timeout 继承global 级别的 interactive_timeout 的值 global级别的session则不受影响。在没有改变 interactive_timeout的值的情况下,去修改wait_timeout的值 结果无效。就会出现前言中我遇到的情况。2.2 有效参数 通过一个例子检测影响会话的参数是哪个?验证方式通过设置全局的timeout时间(注意两者时间不同),另外起一个会话 session1 [RO] 10:20:56 >set global interactive_timeout=20; Query OK, 0 rows affected (0.00 sec) session1 [RO] 10:23:32 >set global wait_timeout=10; Query OK, 0 rows affected (0.00 sec) 会话2进行查询 mysql> select sleep(5); +----------+ | sleep(5) | +----------+ | 0 | +----------+ 1 row in set (5.01 sec) 然后在session1 中 查看show processlist;分析 交互式 timeout时间受global级别的interactive_timeout影响。2)非交互模式 目前的测试并没有达到预期,测试模型如下设置 mysql> select variable_name,variable_value from information_schema.session_variables where variable_name in ('interactive_timeout','wait_timeout'); +---------------------+----------------+ | variable_name | variable_value | +---------------------+----------------+ | INTERACTIVE_TIMEOUT | 35 | | WAIT_TIMEOUT | 35 | +---------------------+----------------+ 2 rows in set (0.00 sec) mysql> select variable_name,variable_value from information_schema.global_variables where variable_name in ('interactive_timeout','wait_timeout'); +---------------------+----------------+ | variable_name | variable_value | +---------------------+----------------+ | INTERACTIVE_TIMEOUT | 35 | | WAIT_TIMEOUT | 25 | +---------------------+----------------+ 2 rows in set (0.00 sec) 在python命令行中模拟非交互数据来访问数据库,查看数据库timeout参数。同时在数据库中执行show processlist 查看python的连接多久会被关闭。 查看session级别的参数 查看show processlist,通过python程序连接数据库的会话等待了25s之后,被中断。分析1 通过python 命令行获取的timeout 参数和交互方式获取的并不一致,在交互命令行中获取session级别的wait_timeout 的值为35,使用非交互命令获取的值为25,说明wait_timeout继承全局的wait_timeout。2 交互模式下会话空闲时间超过wait_timeout立即会被断开。 3) 思考题 session1 通过非交互命令连接到db,此时全局的wait_timeout的值是28800,session 2 修改全局的wait_timeout 为30s ,问题 session1的会话会受到影响吗?三 总结 1 timeout 只是针对空闲会话有影响。 2 session级别的wait_timeout继承global级别的interactive_timeout的值。而global级别的session则不受interactive_timeout的影响。 3 交互式会话的timeout时间受global级别的interactive_timeout影响。因此要修改非交互模式下的timeout,必须同时修改interactive_timeout的值。 4 非交互模式下,wait_timeout参数继承global级别的wait_timeout。四 参考资料 注意 本文测试的和参考资料并不完全相符,需要各位读者亲自测试,得到自己的结论。 [1]MySQL timeout相关参数解析和测试 [2]MySQL中interactive_timeout和wait_timeout的区别 [3]官方文档
一 前言 为什么是再说呢?因为前面已经写过一篇blog,介绍order by 的基本原理以及优化。如果觉得对order by原理了解不透彻可以参考其他同行的文章《MySQL排序内部原理探秘》.本文是基于官网文档的二刷(基本翻译+测试验证),看完本文可以了解到什么样的select + order by 语句可以使用索引,什么样的不能利用到索引排序。二 分析 2.1 官方标准介绍 对于select order by语句如何能够利用到索引,官方表述如下: "The index can also be used even if the ORDER BY does not match the index exactly, as long as all of the unused portions of the index and all the extra ORDER BY columns are constants in the WHERE clause." 翻译一下就是即使ORDER BY语句不能精确匹配(组合)索引列也能使用索引,只要WHERE条件中的所有未使用的索引部分和所有额外的ORDER BY列为常数就行。 如何理解这句话呢?我们通过具体用例来解释。2.2 准备工作 CREATE TABLE `tx` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '记录ID', `shid` int(11) NOT NULL COMMENT '商店ID', `gid` int(11) NOT NULL COMMENT '物品ID', `type` tinyint(1) NOT NULL COMMENT '支付方式', `price` int(10) NOT NULL COMMENT '物品价格', `comment` varchar(200) NOT NULL COMMENT '备注', PRIMARY KEY (`id`), UNIQUE KEY `uniq_shid_gid` (`shid`,`gid`), KEY `idx_price` (`price`), KEY `idx_type` (`type`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ; INSERT INTO `tx` (`shid`, `gid`, `type`, `price`, `comment`) VALUES (6, 2, 0, '399', '2'),(6, 5, 0, '288', '2'),(6, 11, 0, '10', '2'); (1, 1, 0, '10', 'sd'), (2, 55, 0, '210', 'sa'), (2, 33, 1, '999', 'a'), (3, 17, 0, '198', 'b'), (3, 22, 1, '800', 'e'), (4, 12, 0, '120', 'f'), (4, 73, 0, '250', 'd'), (5, 61, 0, '10', 'c'), (6, 1, 0, '210', '2'), (7, 9, 1, '999', '44'), (7, 2, 0, '198', '45'), (8, 3, 1, '800', 'rt'), (9, 4, 0, '120', 'pr'), (9, 6, 0, '250', 'x'), (10, 8, 0, '10', 'w'), (12, 9, 0, '210', 'w'), (12, 10, 1, '999', 'q'), (13, 11, 0, '198', ''), (13, 12, 1, '800', ''), (14, 13, 0, '120', ''), (14, 19, 0, '250', ''); CREATE TABLE `goods_type` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID', `type` int NOT NULL COMMENT '类型', `name` varchar(20) NOT NULL COMMENT '名称', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; INSERT INTO `goods_type` (`id`, `type`, `name`) VALUES (1, 1, 'hw手机'), (2, 0, 'xiaomi'), (3, 1, 'apple') 2.3 能够利用索引的例子分析官方的文档 中介绍有7个例子可以使用索引进行排序。如果使用explain/desc工具查看执行计划中的extra中出现了Using filesort则说明sql没有用到排序优化。 案例一 文档: SELECT * FROM t1 ORDER BY key_part1,key_part2,...; test [RW] 06:03:52 >desc select * from tx order by shid,gid; +----+-------------+-------+------+---------------+------+---------+------+------+----------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+----------------+ | 1 | SIMPLE | tx | ALL | NULL | NULL | NULL | NULL | 24 | Using filesort | +----+-------------+-------+------+---------------+------+---------+------+------+----------------+ 1 row in set (0.00 sec) 分析: 显然上述sql没有利用到索引排序. type=ALL Extra=Using filesort,因为where字句没有条件,优化器选择全表扫描和内存排序。 test [RW] 06:04:39 >desc select gid from tx order by shid,gid; +----+-------------+-------+-------+---------------+---------------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------------+---------+------+------+-------------+ | 1 | SIMPLE | tx | index | NULL | uniq_shid_gid | 8 | NULL | 24 | Using index | +----+-------------+-------+-------+---------------+---------------+---------+------+------+-------------+ 1 row in set (0.00 sec) test [RW] 06:04:47 >desc select shid,gid from tx order by shid,gid; +----+-------------+-------+-------+---------------+---------------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------------+---------+------+------+-------------+ | 1 | SIMPLE | tx | index | NULL | uniq_shid_gid | 8 | NULL | 24 | Using index | +----+-------------+-------+-------+---------------+---------------+---------+------+------+-------------+ 1 row in set (0.00 sec) test [RW] 06:04:54 >desc select id,shid,gid from tx order by shid,gid; +----+-------------+-------+-------+---------------+---------------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------------+---------+------+------+-------------+ | 1 | SIMPLE | tx | index | NULL | uniq_shid_gid | 8 | NULL | 24 | Using index | +----+-------------+-------+-------+---------------+---------------+---------+------+------+-------------+ 1 row in set (0.00 sec) 分析 从type=index,extra=Using index 可以看出当select 的字段包含在索引中时,能利用到索引排序功能,进行覆盖索引扫描。 使用select * 则不能利用覆盖索引扫描且由于where语句没有具体条件MySQL选择了全表扫描且进行了排序操作。案例二 SELECT * FROM t1 WHERE key_part1 = constant ORDER BY key_part2; 使用组合索引中的一部分做等值查询 ,另一部分作为排序字段。更严谨的说法是where条件使用组合索引的左前缀等值查询,使用剩余字段进行order by排序。 test [RW] 06:05:41 >desc select * from tx where shid= 2 order by gid; +----+-------------+-------+------+---------------+---------------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra| +----+-------------+-------+------+---------------+---------------+---------+-------+------+-------------+ | 1 | SIMPLE | tx | ref | uniq_shid_gid | uniq_shid_gid | 4 | const | 2 | Using where | +----+-------------+-------+------+---------------+---------------+---------+-------+------+-------------+ 1 row in set (0.00 sec) test [RW] 11:30:13 >desc select * from tx where shid= 2 order by gid desc; +----+-------------+-------+------+---------------+---------------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra| +----+-------------+-------+------+---------------+---------------+---------+-------+------+-------------+ | 1 | SIMPLE | tx | ref | uniq_shid_gid | uniq_shid_gid | 4 | const | 2 | Using where | +----+-------------+-------+------+---------------+---------------+---------+-------+------+-------------+ 1 row in set (0.00 sec) 分析: where 条件字句可以基于 shid 进行索引查找 并且利用(shid,gid)中gid的有序性避免额外的排序工作. 我们基于本例解释"即使ORDER BY语句不能精确匹配(组合)索引列也能使用索引,只要WHERE条件中的所有未使用的索引部分和所有额外的ORDER BY列为常数就行。" 该语句的order by gid 并未精确匹配到组合索引(shid,gid),where条件 shid利用了组合索引的最左前缀且为等值常量查询,对order by 而言shid就是额外的字段,没有出现在order by子句中却是组合索引的一部分。这样的条件既可以使用索引来排序。案例三 SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 DESC; 其实和案例一 类似,只是选择了倒序。该sql不能利用索引的有序性,需要server层进行排序。 test [RW] 06:06:30 >desc select * from tx order by shid desc,gid desc; +----+-------------+-------+------+---------------+------+---------+------+------+----------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+----------------+ | 1 | SIMPLE | tx | ALL | NULL | NULL | NULL | NULL | 24 | Using filesort | +----+-------------+-------+------+---------------+------+---------+------+------+----------------+ 1 row in set (0.00 sec) 如果select 中选择索引字段,可以利用覆盖索引扫描则可以利用索引进行排序。 test [RW] 06:06:31 >desc select shid,gid from tx order by shid desc,gid desc; +----+-------------+-------+-------+---------------+---------------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------------+---------+------+------+-------------+ | 1 | SIMPLE | tx | index | NULL | uniq_shid_gid | 8 | NULL | 24 | Using index | +----+-------------+-------+-------+---------------+---------------+---------+------+------+-------------+ 1 row in set (0.00 sec) 案例四SELECT * FROM t1 WHERE key_part1 = 1 ORDER BY key_part1 DESC, key_part2 DESC; 本例和案例二类似,只是order by 字句中包含所有的组合索引列。 test [RW] 06:06:55 >desc select * from tx where shid=4 order by shid desc ,gid desc; +----+-------------+-------+------+---------------+---------------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+---------------+---------+-------+------+-------------+ | 1 | SIMPLE | tx | ref | uniq_shid_gid | uniq_shid_gid | 4 | const | 2 | Using where | +----+-------------+-------+------+---------------+---------------+---------+-------+------+-------------+ 1 row in set (0.00 sec) 分析: where shid=4 可以利用shid的索引定位数据记录,select * 有不在索引里面的字段,所以回表访问组合索引列之外的数据,利用了gid索引的有序性避免了排序工作。案例五SELECT * FROM t1 WHERE key_part1 > constant ORDER BY key_part1 ASC; SELECT * FROM t1 WHERE key_part1 < constant ORDER BY key_part1 DESC; test [RW] 11:40:48 >desc select * from tx where shid>5 order by shid desc ; +----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------+ | 1 | SIMPLE | tx | ALL | uniq_shid_gid | NULL | NULL | NULL | 24 | Using where; Using filesort | +----+-------------+-------+------+---------------+------+---------+------+------+-----------------------------+ 1 row in set (0.00 sec) test [RW] 11:47:25 >desc select * from tx where shid>13 order by shid desc ; +----+-------------+-------+-------+---------------+---------------+---------+------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------------+---------+------+------+-----------------------+ | 1 | SIMPLE | tx | range | uniq_shid_gid | uniq_shid_gid | 4 | NULL | 2 | Using index condition | +----+-------------+-------+-------+---------------+---------------+---------+------+------+-----------------------+ 1 row in set (0.00 sec) 分析 表总共24行,其中大于5的有16行,大于13的2行,导致MySQL优化器选择了不同的执行计划。这个测试说明和shid的区分度有关。案例六SELECT * FROM t1 WHERE key_part1 = constant1 AND key_part2 > constant2 ORDER BY key_part2; 利用组合索引前缀索引进行ref等值查询,其他字段进行范围查询,order by 非等值的字段 test [RW] 06:10:41 >desc select * from tx where shid=6 and gid>1 order by gid; +----+-------------+-------+-------+---------------+---------------+---------+------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------------+---------+------+------+-----------------------+ | 1 | SIMPLE | tx | range | uniq_shid_gid | uniq_shid_gid | 8 | NULL | 3 | Using index condition | +----+-------------+-------+-------+---------------+---------------+---------+------+------+-----------------------+ 1 row in set (0.02 sec) 分析: 利用shid=6的进行索引查询记录到了MySQL的ICP特性,无排序操作。为啥使用ICP 这个待确认。2.4 不能利用索引排序的分析 案例一 order by语句使用了多个不同的索引 SELECT * FROM t1 ORDER BY key1, key2; test [RW] 09:44:03 >desc select * from tx order by price, type; +----+-------------+-------+------+---------------+------+---------+------+------+----------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+----------------+ | 1 | SIMPLE | tx | ALL | NULL | NULL | NULL | NULL | 24 | Using filesort | +----+-------------+-------+------+---------------+------+---------+------+------+----------------+ 1 row in set (0.00 sec) 因为sql使用了不同的索引列,在存储上顺序存在不一致的可能性,MySQL会选择排序操作。 特例 因为所有的辅助索引里面都包含主键id,当where 字段加上order by字段沟通完整的索引时 ,可以避免filesort的 test [RW] 11:20:10 >desc select * from tx where type=1 order by id; +----+-------------+-------+------+---------------+----------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+----------+---------+-------+------+-------------+ | 1 | SIMPLE | tx | ref | idx_type | idx_type | 1 | const | 6 | Using where | +----+-------------+-------+------+---------------+----------+---------+-------+------+-------------+ 1 row in set (0.00 sec) 案例二 当查询条件使用了与order by不同的其他的索引,且值为常量,但排序字段是另一个联合索引的非连续部分时 SELECT * FROM t1 WHERE key2=constant ORDER BY key_part1, key_part3; test [RW] 11:19:17 >desc select * from tx where type=1 order by gid; +----+-------------+-------+------+---------------+----------+---------+-------+------+-----------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+----------+---------+-------+------+-----------------------------+ | 1 | SIMPLE | tx | ref | idx_type | idx_type | 1 | const | 6 | Using where; Using filesort | +----+-------------+-------+------+---------------+----------+---------+-------+------+-----------------------------+ 1 row in set (0.00 sec) test [RW] 11:21:08 >desc select * from tx where type=1 order by shid; +----+-------------+-------+------+---------------+----------+---------+-------+------+-----------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+----------+---------+-------+------+-----------------------------+ | 1 | SIMPLE | tx | ref | idx_type | idx_type | 1 | const | 6 | Using where; Using filesort | +----+-------------+-------+------+---------------+----------+---------+-------+------+-----------------------------+ 1 row in set (0.00 sec) 分析 与案例一一致,key2 的顺序语句key1(key_part1)存储排序不一样的情况下,MySQL 都会选择filesort 。案例三 order by 语句使用了和组合索引默认不同的排序规则 SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 ASC; 官方文档中提示使用混合索引排序规则会导致额外排序,其实我们创建索引的时候可以做 (key_part1 DESC, key_part2 ASC)案例四 当where 条件中利用的索引与order by 索引不同时,与案例二有相似性。SELECT * FROM t1 WHERE key2=constant ORDER BY key1; test [RW] 11:19:44 >desc select * from tx where type=1 order by shid; +----+-------------+-------+------+---------------+----------+---------+-------+------+-----------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+----------+---------+-------+------+-----------------------------+ | 1 | SIMPLE | tx | ref | idx_type | idx_type | 1 | const | 6 | Using where; Using filesort | +----+-------------+-------+------+---------------+----------+---------+-------+------+-----------------------------+ 1 row in set (0.00 sec) test [RW] 11:20:07 >desc select * from tx where type=1 order by shid,gid; +----+-------------+-------+------+---------------+----------+---------+-------+------+-----------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+----------+---------+-------+------+-----------------------------+ | 1 | SIMPLE | tx | ref | idx_type | idx_type | 1 | const | 6 | Using where; Using filesort | +----+-------------+-------+------+---------------+----------+---------+-------+------+-----------------------------+ 1 row in set (0.00 sec) 案例五 order by 字段使用了表达式SELECT * FROM t1 ORDER BY ABS(key); SELECT * FROM t1 ORDER BY -key; test [RW] 11:53:39 >desc select * from tx where shid=3 order by -shid; +----+-------------+-------+------+---------------+---------------+---------+-------+------+-----------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+---------------+---------+-------+------+-----------------------------+ | 1 | SIMPLE | tx | ref | uniq_shid_gid | uniq_shid_gid | 4 | const | 2 | Using where; Using filesort | +----+-------------+-------+------+---------------+---------------+---------+-------+------+-----------------------------+ 1 row in set (0.00 sec) test [RW] 11:56:26 >desc select * from tx where shid=3 order by shid; +----+-------------+-------+------+---------------+---------------+---------+-------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+---------------+---------+-------+------+-------+ | 1 | SIMPLE | tx | ref | uniq_shid_gid | uniq_shid_gid | 4 | const | 2 | NULL | +----+-------------+-------+------+---------------+---------------+---------+-------+------+-------+ 分析 order by 的字段使用函数,和在where条件中使用函数索引一样 ,MySQL都无法利用到索引。案例六 The query joins many tables, and the columns in the ORDER BY are not all from the first nonconstant table that is used to retrieve rows. (This is the first table in the EXPLAIN output that does not have a const join type.) 当查询语句是多表连接,并且ORDER BY中的列并不是全部来自第1个用于搜索行的非常量表.(这是EXPLAIN输出中的没有使用const联接类型的第1个表) test [RW] 12:32:43 >explain select shid,gid from tx a left join goods_type b on a.shid=b.id where a.shid=2 order by a.gid,b.id; +----+-------------+-------+-------+---------------+---------------+---------+-------+------+-------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------------+---------+-------+------+-------------------------------+ | 1 | SIMPLE | a | ref | uniq_shid_gid | uniq_shid_gid | 4 | const | 2 | Using index; Using temporary; Using filesort | | 1 | SIMPLE | b | const | PRIMARY | PRIMARY | 4 | const | 1 | Using index | +----+-------------+-------+-------+---------------+---------------+---------+-------+------+----------------------------------------------+ 2 rows in set (0.00 sec) test [RW] 12:32:44 >explain select shid,gid from tx a left join goods_type b on a.shid=b.id where a.shid=2 order by a.gid; +----+-------------+-------+-------+---------------+---------------+---------+-------+------+--------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------------+---------+-------+------+--------------------------+ | 1 | SIMPLE | a | ref | uniq_shid_gid | uniq_shid_gid | 4 | const | 2 | Using where; Using index | | 1 | SIMPLE | b | const | PRIMARY | PRIMARY | 4 | const | 1 | Using index | +----+-------------+-------+-------+---------------+---------------+---------+-------+------+--------------------------+ 2 rows in set (0.00 sec) 分析 出现join的情况下不能利用索引其实有很多种,只要对a的访问不满足上面说的可以利用索引排序的情况都会导致额外的排序动作。但是当where + order 复合要求,order by 有包含了其他表的列就会导致额外的排序动作。案例七 sql中包含的order by 列与group by 列不一致 test [RW] 11:26:54 >desc select * from tx group by shid order by gid; +----+-------------+-------+-------+---------------+---------------+---------+------+------+---------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------------+---------+------+------+--------------------------------+ | 1 | SIMPLE | tx | index | uniq_shid_gid | uniq_shid_gid | 8 | NULL | 24 | Using temporary; Using filesor | +----+-------------+-------+-------+---------------+---------------+---------+------+------+--------------------------------+ 1 row in set (0.00 sec) group by 本身会进行排序的操作,我们可以显示的注让group by不进行额外的排序动作。 test [RW] 12:09:52 >desc select * from tx group by shid order by null; +----+-------------+-------+-------+---------------+---------------+---------+------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------------+---------+------+------+-------+ | 1 | SIMPLE | tx | index | uniq_shid_gid | uniq_shid_gid | 8 | NULL | 24 | NULL | +----+-------------+-------+-------+---------------+---------------+---------+------+------+-------+ 1 row in set (0.00 sec) 案例八 索引本身不支持排序存储 比如,hash索引。 CREATE TABLE `hash_test` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT , `name` varchar(20) NOT NULL COMMENT '名称', PRIMARY KEY (`id`), KEY `name` (`name`) ) ENGINE=MEMORY ; INSERT INTO `hash_test` (`id`, `name`) VALUES (1, '张三'), (2, '李四'); test [RW] 12:07:27 >explain select * from hash_test force index(name) order by name; +----+-------------+-----------+------+---------------+------+---------+------+------+----------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+------+---------------+------+---------+------+------+----------------+ | 1 | SIMPLE | hash_test | ALL | NULL | NULL | NULL | NULL | 2 | Using filesort | +----+-------------+-----------+------+---------------+------+---------+------+------+----------------+ 1 row in set (0.00 sec) test [RW] 12:07:48 >explain select * from hash_test order by name; +----+-------------+-----------+------+---------------+------+---------+------+------+----------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+------+---------------+------+---------+------+------+----------------+ | 1 | SIMPLE | hash_test | ALL | NULL | NULL | NULL | NULL | 2 | Using filesort | +----+-------------+-----------+------+---------------+------+---------+------+------+----------------+ 1 row in set (0.00 sec) test [RW] 12:07:53 >alter table hash_test ENGINE=innodb; Query OK, 2 rows affected (0.45 sec) Records: 2 Duplicates: 0 Warnings: 0 test [RW] 12:08:33 >explain select * from hash_test order by name; +----+-------------+-----------+-------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+-------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | hash_test | index | NULL | name | 82 | NULL | 1 | Using index | +----+-------------+-----------+-------+---------------+------+---------+------+------+-------------+ 1 row in set (0.00 sec) 分析 hash 索引本身不支持排序存储,故不能利用到排序特性,将表转化为innodb再次查询,避免了filesort 案例九 order by的索引使用部分字符串 比如 key idx_name(name(2)) test [RW] 12:08:37 >alter table hash_test drop key name ,add key idx_name(name(2)); Query OK, 0 rows affected (0.03 sec) Records: 0 Duplicates: 0 Warnings: 0 test [RW] 12:09:50 >explain select * from hash_test order by name; +----+-------------+-----------+------+---------------+------+---------+------+------+----------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+------+---------------+------+---------+------+------+----------------+ | 1 | SIMPLE | hash_test | ALL | NULL | NULL | NULL | NULL | 1 | Using filesort | +----+-------------+-----------+------+---------------+------+---------+------+------+----------------+ 1 row in set (0.00 sec) 三 老生常谈的优化策略 为了提高order by 查询的速度,尽可能的利用索引的有序性进行排序,如果不能利用索引排序的功能,那么我们只能退而求其次优化order by相关的缓存参数 1 增加 sort_buffer_size 大小,建议sort_buffer_size要足够大能够避免磁盘排序和合并排序次数。 2 增加 read_rnd_buffer_size 大小。 3 使用合适的列大小存储具体的内容,比如对于city字段 varchar(20)比varchar(200)能获取更好的性能。 4 将tmpdir 目录指定到os上面有足够空间的具有比较高iops能力的存储上。四 推荐文章 [1] MySQL order by 优化的那些事儿 [2] 官方文档 [3] order by 结果不准确的问题及解决 [4] MySQL排序原理与案例分析 [5] order by 原理以及优化 看完本文 如果您觉得有所收获 ,可以请 北在南方 一瓶饮料 ^_^
一 简介 fileinput 是python 提供的一个可以快速遍历,修改一个或者多个文件的模块。我们可以使用该模块进行文本替换 并做文件备份。二 使用2.1 使用方法 fileinput.input (files=None, inplace=False, backup='', bufsize=0, mode='r', openhook=None) files 为要读入的文件 或者文件列表 files=1.txt files=['1.txt','2.txt'] inplace 是原地替换与否,为True时表示重写原文件。默认为False。 backup 备份要替换的文件的后缀 backup='.bak' files='1.txt' 备份的文件名称为 1.txt.bak mode 读写模式,默认为只读 bufsize 读写文件的缓冲区大小,默认为0 ,如果文件比较大 则需要调整该值。 import fileinput def process(line): do_something for line in fileinput.input(): process(line) #处理文件的行 然后重新写入源文件 2.2 常用的函数: fileinput.input() # 读取文件的内容 fileinput.filename() # 文件的名称 fileinput.lineno() # 当前读取行的数量 fileinput.filelineno() # 读取行的行号 fileinput.isfirstline() # 当前行是否是文件第一行 fileinput.isstdin() # 判断最后一行是否从stdin中读取 fileinput.close() # 关闭队列 2.3 例子 In [4]: for line in fileinput.input('1.txt',inplace=1,backup='.bak'): ...: print line.rstrip() + ' line' ...: In [5]: pwd Out[5]: u'/Users/yangyi' In [6]: ls 1.txt* 1.txt 1.txt.bak 查看备份文件 In [7]: cat 1.txt.bak AA:BB:CC aa:30:6 cc:50:3 dd:20:7 bb:10:2 ee:40:4 ee:60:1 查看源文件,已经被修改了 In [8]: cat 1.txt AA:BB:CC line aa:30:6 line cc:50:3 line dd:20:7 line bb:10:2 line ee:40:4 line ee:60:1 line 三 小结 本文是简单介绍了fileinput 的使用方式 ,具体更详细的内容可以参考 官方文档
一 简介 python 字符串输出格式化有两种方式 %[s,d,] ,python 2.6 版本提供了string.format(),其功能也相当强大。talk is cheap,show me the code .二 使用2.1 参数映射 str.format 通过 {} 替换 字符串的 %,我们可以使用基于位置映射参数,基于下表,基于参数 比如 In [23]: print 'i am a %s,work at %s !' %('dba','youzan') i am a dba,work at youzan ! In [24]: print 'i am a {0},work at {1} !'.format('dba','youzan') i am a dba,work at youzan ! In [26]: print 'i am a {arg},work at {company} !'.format(arg='dba',company='youzan') i am a dba,work at youzan ! format 不限制参数的调用次数 In [28]: print 'i am a {0},work at {1},and {1} is good at SAAS service !'.format('dba','youzan') i am a dba,work at youzan,and youzan is good at SAAS service ! 2.2 格式化输出 % 提供丰富的格式化输出,format当然也有同样的功能。 填充与对齐 ^ 居中< 左对齐> 右对齐 后面带宽度 :号后面带填充的字符,只能是一个字符,不指定的话默认是用空格填充 具体的使用方式如下 In [30]: fs='{:<8}' In [31]: fs.format('dba') Out[31]: 'dba ' In [32]: fs='{:1<8}' ##左对齐 In [33]: fs.format('dba') Out[33]: 'dba11111' #右对齐 In [34]: fs='{:1>8}' In [35]: fs.format('dba') Out[35]: '11111dba' #居中 In [36]: fs='{:1^8}' In [37]: fs.format('dba') Out[37]: '11dba111' 浮点数精度 In [40]: fs='{:.3f}' In [41]: fs.format(3.14159265358) Out[41]: '3.142' 数字的进制 b 分别是二进制 d 十进制 o 八进制 x 十六进制。 In [42]: ':b'.format(29) Out[42]: ':b' In [43]: '{:b}'.format(29) Out[43]: '11101' In [44]: '{:d}'.format(29) Out[44]: '29' In [45]: '{:x}'.format(29) Out[45]: '1d' In [46]: '{:o}'.format(29) Out[46]: '35' 用逗号 还能用来做金额的千位分隔符。 In [47]: '{:,}'.format(2132323455) Out[47]: '2,132,323,455' 三 小结 理论知识就介绍到这里了,如何在实际中运用呢?就交给给位读者朋友了。
一 简介 *args 和 **kwargs 主要用于函数定义。 当我们需要定义的函数的传入参数个数不确定时,可以使用*args 和 **kwargs 代替不确定的参数个数。其实并不是必须写成*args 和**kwargs。 只有变量前面的 *(星号)才是必须的. 我们可以写成*var和**vars. 而写成*args 和**kwargs只是一个通俗的命名约定。二 使用2.1 *args 当函数的参数个数不确定且不需要指定参数名称时,*args的格式是常规的参数 val1[,val2,val3....] def func_arg(farg, *args): print "formal arg:", farg for arg in args: print "another arg:", arg func_arg(1,"youzan",'dba') 输出 In [10]: args(1,"youzan",'dba') formal arg: 1 another arg: youzan another arg: dba 2.2 **kwargs 当函数的参数是有名称且不确定个数的时候,可以使用**kwargs。**kwargs的参数格式是 key1=value1,[key2=value2,key3=value3,....],函数对**kwargs是以键值对类似字典的方式进行解析。 def func_kwargs(farg, **kwargs): print "formal arg:", farg for key in kwargs: print "keyword arg: %s: %s" % (key, kwargs[key]) 输出 In [15]: func_kwargs(1 ,id=1, name='youzan', city='hangzhou') formal arg: 1 keyword arg: city: hangzhou keyword arg: id: 1 keyword arg: name: youzan **kwargs还有一个功能就是转换参数为字典 In [1]: def kw_dict(**kwargs): ...: return kwargs ...: In [2]: print kw_dict(a=1,b=2,c=3) {'a': 1, 'c': 3, 'b': 2} 2.3 综合的例子 In [3]: def foo(*args, **kwargs): ...: print 'args = ', args ...: print 'kwargs = ', kwargs ...: print '---------------------------------------' ...: In [4]: foo(1,2,3,4) args = (1, 2, 3, 4) kwargs = {} --------------------------------------- In [5]: foo(a=1,b=2,c=3) args = () kwargs = {'a': 1, 'c': 3, 'b': 2} --------------------------------------- In [6]: foo(1,2,3,4, a=1,b=2,c=3) args = (1, 2, 3, 4) kwargs = {'a': 1, 'c': 3, 'b': 2} --------------------------------------- In [7]: foo('a', 1, None, a=1, b='2', c=3) args = ('a', 1, None) kwargs = {'a': 1, 'c': 3, 'b': '2'} --------------------------------------- 2.4 使用顺序 标准参数与*args、**kwargs在使用时的顺序,当我们想在函数里同时使用所有这三种参数, 顺序是这样的: func(fargs, *args, **kwargs)三 参考文档 [1] how-to-use-args-and-kwargs-in-python [2] https://eastlakeside.gitbooks.io/interpy-zh/content/args_kwargs/Using_args_and_kwargs_to_call_function.html
一 简介 Python 内置了很多非常有用的函数 比如map() ,reduce(),filter(),还有lambda。熟练应用这些函数可以在写python程序的时候构建精简的代码。本文先来了解map函数。二 使用 用法 map(func, seq1[, seq2,…]) map接收两个参数,第一个参数是函数名,第二个是一个或多个可迭代的序列,返回的是一个集合。运行时,map()将func作用于序列中的每一个元素,并将结果作为一个list返回。如果func为None,作用同zip()。2.1 当seq 只有一个时,map函数返回将func函数作用于 seq每个元素并返回一个新的list集合, 比如需要将seq的元素乘以2 ,使用map可以写成 In [4]: l=[1, 2, 3, 4, 5, 6, 7, 8, 9] In [5]: map(lambda x:x*2 ,l) Out[5]: [2, 4, 6, 8, 10, 12, 14, 16, 18] 如果使用函数来做: rest=[] for i in l: rest.append(i*2) print rest map作为高阶函数,能够把运算规则抽象化,显然map 更精简。2.2 当有多个seq时,map可以并行的取每个seq对应的第M个元素 seqN[M],进行计算。 例子: In [9]: map(lambda x , y : x * y, [2,4,6],[3,2,1]) Out[9]: [6, 8, 6] 记得多个seq的元素个数必须一致 不然会报错 In [1]: print map(lambda x , y : x ** y, [2,4,6],[3,2,1]) [8, 16, 6] seq1=[2,4,6] ,seq2=[3,2] 元素个数不一致则报错。 In [3]: print map(lambda x , y : x ** y, [2,4,6],[3,2]) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-3-d075c46afd0b> in <module>() ----> 1 print map(lambda x , y : x ** y, [2,4,6],[3,2]) <ipython-input-3-d075c46afd0b> in <lambda>(x, y) ----> 1 print map(lambda x , y : x ** y, [2,4,6],[3,2]) TypeError: unsupported operand type(s) for ** or pow(): 'int' and 'NoneType' In [4]: 2.3 map在多进程中的应用。 #!/usr/bin/env python # encoding: utf-8 """ author: yangyi@youzan.com time: 2017/7/16 上午10:42 func: 参考网络上的例子 """ import urllib2 from multiprocessing.dummy import Pool as ThreadPool urls = [ 'http://www.python.org', 'http://www.python.org/about/', 'http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html', 'http://www.python.org/doc/', 'http://www.python.org/download/', 'http://www.python.org/getit/', 'http://www.python.org/community/', 'https://wiki.python.org/moin/', 'http://planet.python.org/', 'https://wiki.python.org/moin/LocalUserGroups', 'http://www.python.org/psf/', 'http://docs.python.org/devguide/', 'http://www.python.org/community/awards/' ] # Make the Pool of workers pool = ThreadPool(4) # Open the urls in their own threads # and return the results results = pool.map(urllib2.urlopen, urls) #close the pool and wait for the work to finish pool.close() pool.join() 该例子使用了multiprocessing.dummy的线程池Pool的方式,利用用map的特性来处理url集合。 参考文档 [1] [Python] Python中的一些特殊函数
写底层代码的时候,经常使用sort命令对文件或者文本进行排序,本文对sort做简单记录作为学习笔记。sort 的功能是将文件/文本的每一行作为一个单位,相互比较,比较原则是从首字符向后,依次按ASCII码值进行比较,最后将他们按升序输出。 常用的参数 -t, --field-separator=SEP 分隔符:指定排序时所用到的栏位分隔符,通常和 k 一起使用 -k, --key=POS1[,POS2] 指定需要排序的列数,比如-k 1,3 按照第一列和第三列排序。 -b, --ignore-leading-blanks 忽略每行前面开始处的空格字符 -d, --dictionary-order 排序时,处理英文字母、数字和空格字符,以字典顺序排序。忽略其他所有字符 -f, --ignore-case 将小写字母视为大写字母进行排序。 -i, --ignore-nonprinting 排序时,处理 040~176 之间的 ASCII 字符,忽略其他所有字符。 -r, --reverse 以相反的顺序进行排序,倒序。 -u --unique 去掉重复的记录 其他排序参数: -g, --general-numeric-sort 按照数值大小进行排序。 和n在一般情况的都一致,对于科学计数的排序可以使用-g -n, --numeric-sort 按照字符的数值大小进行排序。 -M, --month-sort 将前面3个字母按月份的缩写进行排序。 -c, --check 检查文件是否已经按照顺序排序,排序过为真。 -m, --merge 将几个排序好的文件进行合并。 -o, --output=FILE 将排序后的结果存入FILE,不在控制台输出。 例子 ? ~ cat 1.txt AA:BB:CC aa:30:6 cc:50:3 dd:20:7 bb:10:2 ee:40:4 ee:60:1 以:分隔,按照第三列排序 ? ~ sort -t: -k3 1.txt ee:60:1 bb:10:2 cc:50:3 ee:40:4 aa:30:6 dd:20:7 AA:BB:CC
一前言 使用python进行并发处理多台机器/多个实例的时候,我们可以使用threading ,但是由于著名的GIL存在,实际上threading 并未提供真正有效的并发处理,要充分利用到多核CPU,我们需要使用多进程。Python提供了非常好用的多进程包--multiprocessing。multiprocessing 可以利用multiprocessing.Process对象来创建一个进程,该Process对象与Threading对象的用法基本相同,具有相同的方法(官方原话:"The multiprocessing package mostly replicates the API of the threading module.") 比如:start(),run(),join()的方法。multiprocessing包中也有Lock/Event/Semaphore/Condition/Pipe/Queue类用于进程之间的通信。话不多说 show me the code!二使用2.1 初识异同 下面的程序显示threading和multiprocessing的在使用方面的异同,相近的函数join(),start(),append() 等,并做同一件事情打印自己的进程pid #!/usr/bin/env python # encoding: utf-8 import os import threading import multiprocessing def printer(msg): print(msg, os.getpid()) print('Main begin:', os.getpid()) # threading record = [] for i in range(5): thread = threading.Thread(target=printer, args=('threading',)) thread.start() record.append(thread) for thread in record: thread.join() # multi-process record = [] for i in range(5): process = multiprocessing.Process(target=printer, args=('multiprocessing',)) process.start() record.append(process) for process in record: process.join() print('Main end:', os.getpid()) 输出结果 点击(此处)折叠或打开 Main begin: 9524 threading 9524 threading 9524 threading 9524 threading 9524 threading 9524 multiprocessing 9539 multiprocessing 9540 multiprocessing 9541 multiprocessing 9542 multiprocessing 9543 Main end: 9524 从例子的结果可以看出多线程threading的进程id和主进程(父进程)pid一样 ,同为9524; 多进程打印的pid每个都不一样,for循环中每创建一个process对象都年开一个进程。其他相关的方法基本类似。2.2 用法创建进程的类: Process([group [, target [, name [, args [, kwargs]]]]]), target表示调用对象, args表示调用对象的位置参数元组。 kwargs表示调用对象的字典。 name为进程的别名。 group实质上不使用,为None。方法:is_alive()、join([timeout])、run()、start()、terminate()。其中,Process以start()启动某个进程,并自动调用run方法.属性:authkey、daemon(要通过start()设置,必须设置在方法start之前)、exitcode(进程在运行时为None、如果为–N,表示被信号N结束)、name、pid。其中daemon是父进程终止后自动终止,且自己不能产生新进程,必须在start()之前设置。2.3 创建单进程 单线程比较简单,创建一个 Process的实例对象就好,传入参数 target 为已经定义好的方法worker以及worker需要的参数 #!/usr/bin/env python # encoding: utf-8 """ author: yangyi@youzan.com time: 2017/7/2 下午6:45 func: """ import multiprocessing import datetime, time def worker(interval): print("process start: {0}".format(datetime.datetime.today())); time.sleep(interval) print("process end: {0}".format(datetime.datetime.today())); if __name__ == "__main__": p = multiprocessing.Process(target=worker, args=(5,)) p.start() p.join() print "end!" 2.4 创建多进程 #!/usr/bin/env python # encoding: utf-8 """ author: yangyi@youzan.com time: 2017/7/2 下午7:50 func: """ import multiprocessing def worker(num): print "worker %d" %num if __name__ == "__main__": print("The number of CPU is:" + str(multiprocessing.cpu_count())) proc = [] for i in xrange(5): p = multiprocessing.Process(target=worker, args=(i,)) proc.append(p) for p in proc: p.start() for p in proc: p.join() print "end ..." 输出 点击(此处)折叠或打开 The number of CPU is:4 worker 0 worker 1 worker 2 worker 3 worker 4 main process end ... 2.5 线程池 multiprocessing提供进程池的类--Pool,它可以指定程序最大可以调用的进程数量,当有新的请求提交到pool中时,如果进程池还没有满,那么就会创建一个新的进程用来执行该请求;但如果进程池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来它。构造方法: Pool([processes[, initializer[, initargs[, maxtasksperchild[, context]]]]]) processes : 使用的工作进程的数量,如果processes是None,默认使用os.cpu_count()返回的数量。 initializer: 如果initializer是None,那么每一个工作进程在开始的时候会调用initializer(*initargs)。 maxtasksperchild:工作进程退出之前可以完成的任务数,完成后用一个新的工作进程来替代原进程,来让闲置的资源被释放。maxtasksperchild默认是None,意味着只要Pool存在工作进程就会一直存活。 context: 用在制定工作进程启动时的上下文,一般使用multiprocessing.Pool()或者一个context对象的Pool()方法来创建一个池,两种方法都适当的设置了context。实例方法: apply(func[, args[, kwds]]):同步进程池 apply_async(func[, args[, kwds[, callback[, error_callback]]]]) :异步进程池 close() : 关闭进程池,阻止更多的任务提交到pool,待任务完成后,工作进程会退出。 terminate() : 结束工作进程,不在处理未完成的任务. join() : 等待工作线程的退出,在调用join()前必须调用close()或者 terminate(),因为被终止的进程需要被父进程调用wait(join等价与wait),否则进程会成为僵尸进程。 #!/usr/bin/env python # encoding: utf-8 """ author: yangyi@youzan.com time: 2017/7/2 下午7:50 func: """ from multiprocessing import Pool import time def worker(num): print "worker %d" %num time.sleep(2) print "end worker %d" %num if __name__ == "__main__": proc_pool = Pool(2) for i in xrange(4): proc_pool.apply_async(worker, (i,)) #使用了异步调用,从输出结果可以看出来 proc_pool.close() proc_pool.join() print "main process end ..." 输出结果 点击(此处)折叠或打开 worker 0 worker 1 end worker 0 end worker 1 worker 2 worker 3 end worker 2 end worker 3 main process end .. 解释:创建一个进程池pool 对象proc_pool,并设定进程的数量为2,xrange(4)会相继产生四个对象[0, 1, 2, 4],四个对象被提交到pool中,因pool指定进程数为2,所以0、1会直接送到进程中执行,当其中的2个任务执行完之后才空出2进程处理对象2和3,所以会出现输出 worker 2 worker 3 出现在end worker 0 end worker 1之后。思考一下如果调用 proc_pool.apply(worker, (i,)) 的输出结果会是什么样的?2.6 使用queue multiprocessing提供队列类,可以通过调用multiprocessing.Queue(maxsize) 初始化队列对象,maxsize表示队列里面最多的元素个数。 例子 创建了两个函数入队,出队,出队处理时使用了lock特性,串行化取数据。 #!/usr/bin/env python # encoding: utf-8 """ author: yangyi@youzan.com time: 2017/7/2 下午9:03 func: """ import time from multiprocessing import Process, current_process,Lock,Queue import datetime def inputQ(queue): time.sleep(1) info = "proc_name: " + current_process().name + ' was putted in queue at: ' + str(datetime.datetime.today()) queue.put(info) def outputQ(queue,lock): info = queue.get() lock.acquire() print ("proc_name: " + current_process().name + ' gets info :' + info) lock.release() if __name__ == '__main__': record1 = [] # store input processes record2 = [] # store output processes lock = Lock() # To prevent messy print queue = Queue(3) for i in range(10): process = Process(target=inputQ, args=(queue,)) process.start() record1.append(process) for i in range(10): process = Process(target=outputQ, args=(queue,lock)) process.start() record2.append(process) for p in record1: p.join() queue.close() # No more object will come, close the queue for p in record2: p.join() 2.7 使用pipe Pipe可以是单向(half-duplex),也可以是双向(duplex)。我们通过mutiprocessing.Pipe(duplex=False)创建单向管道 (默认为双向)。一个进程从PIPE一端输入对象,然后被PIPE另一端的进程接收,单向管道只允许管道一端的进程输入,而双向管道则允许从两端输入。 用法 multiprocessing.Pipe([duplex]) 该类返回一组对象实例(conn1, conn2),分别代表发送和接受消息的两端。 #!/usr/bin/env python # encoding: utf-8 """ author: yangyi@youzan.com time: 2017/7/2 下午8:01 func: """ from multiprocessing import Process, Pipe def p1(conn, name): conn.send('hello ,{name}'.format(name=name)) print "p1 receive :", conn.recv() conn.close() def p2(conn, name): conn.send('hello ,{name}'.format(name=name)) print "p2 receive :", conn.recv() conn.close() if __name__ == '__main__': parent_conn, child_conn = Pipe() proc1 = Process(target=p1, args=(child_conn, "parent_conn")) proc2 = Process(target=p2, args=(parent_conn, "child_conn")) proc1.start() proc2.start() proc1.join() proc2.join() 输出: 点击(此处)折叠或打开 p1 receive : hello ,child_conn p2 receive : hello ,parent_conn 该例子中 p1 p2 通过pipe 给彼此相互发送信息,p1 发送"parent_conn" 给 p2 ,p2 发送"child_conn" 给p1.2.8 daemon程序对比结果 import multiprocessing import datetime, time def worker(interval): print("process start: {0}".format(datetime.datetime.today())); time.sleep(interval) print("process end: {0}".format(datetime.datetime.today())); if __name__ == "__main__": p = multiprocessing.Process(target=worker, args=(5,)) p.start() print "end!" 输出: 点击(此处)折叠或打开 end! process start: 2017-07-02 18:47:30.656244 process end: 2017-07-02 18:47:35.657464 设置 daemon = True,程序随着主程序结束而不等待子进程。 import multiprocessing import datetime, time def worker(interval): print("process start: {0}".format(datetime.datetime.today())); time.sleep(interval) print("process end: {0}".format(datetime.datetime.today())); if __name__ == "__main__": p = multiprocessing.Process(target=worker, args=(5,)) p.daemon = True p.start() print "end!" 输出: end! 因为子进程设置了daemon属性,主进程结束,multiprocessing创建的进程对象就随着结束了。 import multiprocessing import datetime, time def worker(interval): print("process start: {0}".format(datetime.datetime.today())); time.sleep(interval) print("process end: {0}".format(datetime.datetime.today())); if __name__ == "__main__": p = multiprocessing.Process(target=worker, args=(5,)) p.daemon = True # p.start() p.join() #进程执行完毕后再关闭 print "end!" 输出: 点击(此处)折叠或打开 process start: 2017-07-02 18:48:20.953754 process end: 2017-07-02 18:48:25.954736 2.9 Lock() 当多个进程需要访问共享资源的时候,Lock可以用来避免访问的冲突。 实例方法: acquire([timeout]): 使线程进入同步阻塞状态,尝试获得锁定。 release(): 释放锁。使用前线程必须已获得锁定,否则将抛出异常。 例子: 多个进程使用同一个std_out ,使用lock机制确保同一个时刻有一个一个进程获取输出。 #!/usr/bin/env python # encoding: utf-8 """ author: yangyi@youzan.com time: 2017/7/2 下午9:28 func: """ from multiprocessing import Process, Lock def func_with_lock(l, i): l.acquire() print 'hello world', i l.release() def func_without_lock(i): print 'hello world', i if __name__ == '__main__': lock = Lock() print "func_with_lock :" for num in range(10): Process(target=func_with_lock, args=(lock, num)).start() 输出: 点击(此处)折叠或打开 func_with_lock : hello world 0 hello world 1 hello world 2 hello world 3 hello world 4 hello world 5 hello world 6 hello world 7 hello world 8 hello world 9 三 小结 本文参考官方资料以及其他资源,对multiprocesssing 的使用方式做了总结,还有很多知识需要详细阅读官方文档。纸上来得终觉浅,绝知此事要躬行。参考资料 [1]官方文档 [2]Python标准库10 多进程初步 (multiprocessing包)
一 前言 和团队内部的同事一起沟通,讨论了MySQL 数据库系统数据安全性问题,主要针对MySQL丢数据库的场景 ,主从不一致的场景 ,还有业务层面使用不得当导致主备库数据结构不一样的情况,本文是基于以上的讨论和总结做的思维导图。二 思维导图 参考 资料 [1] MYSQL数据丢失讨论 [2] 细看InnoDB数据落盘 [3] MySQL5.7 深度解析: Loss-Less半同步复制技术 [4] MySQL 5.7 Replication 相关新功能说明
一 简介 Python和MySQL交互的模块有 MySQLdb 和 PyMySQL(pymysql),MySQLdb是基于C 语言编写的,而且Python3 不在支持MySQLdb 。PyMySQL是一个纯Python写的MySQL客户端,它的目标是替代MySQLdb,可以在CPython、PyPy、IronPython和Jython环境下运行,PyMySQL在MIT许可下发布。 在开发基于Python语言的项目中,为了以后系统能兼容Python3,我们使用了PyMySQL替换了MySQLdb。下面我们来熟悉一下pymysql的使用。 二 安装方式 pymsql的源码 https://github.com/PyMySQL/PyMySQL ,目前还在持续更新。 安装要求: Python -- one of the following: CPython >= 2.6 or >= 3.3 PyPy >= 4.0 IronPython 2.7 MySQL Server -- one of the following: MySQL >= 4.1 (tested with only 5.5~) MariaDB >= 5.1 安装 pip install PyMySQL 相关文档 , 强烈建议大家仔细阅读一遍。其用法和MySQLdb相差无几,核心用法一致。这样使用pymysql替换mysqldb的成本极小。三 基于pymysql的数据库交互 #!/usr/bin/env python # encoding: utf-8 """ author: yangyi@youzan time: 2015/6/8 上午11:34 func: 基于pymysql的数据库交互类,支持事务提交和回滚,返回结果记录行数,和insert的最新id """ import pymysql from warnings import filterwarnings filterwarnings('ignore', category=pymysql.Warning) CONNECT_TIMEOUT = 100 IP = 'localhost' PORT = 3306 USER = 'root' PASSSWORD = '' class QueryException(Exception): """ """ class ConnectionException(Exception): """ """ class MySQL_Utils(): def __init__( self, ip=IP, port=PORT, user=USER, password=PASSSWORD, connect_timeout=CONNECT_TIMEOUT, remote=False, socket='', dbname='test'): self.__conn = None self.__cursor = None self.lastrowid = None self.connect_timeout = connect_timeout self.ip = ip self.port = port self.user = user self.password = password self.mysocket = socket self.remote = remote self.db = dbname self.rows_affected = 0 def __init_conn(self): try: conn = pymysql.connect( host=self.ip, port=int(self.port), user=self.user, db=self.db, connect_timeout=self.connect_timeout, charset='utf8', unix_socket=self.mysocket) except pymysql.Error as e: raise ConnectionException(e) self.__conn = conn def __init_cursor(self): if self.__conn: self.__cursor = self.__conn.cursor(pymysql.cursors.DictCursor) def close(self): if self.__conn: self.__conn.close() self.__conn = None #专门处理select 语句 def exec_sql(self, sql, args=None): try: if self.__conn is None: self.__init_conn() self.__init_cursor() self.__conn.autocommit = True self.__cursor.execute(sql, args) self.rows_affected = self.__cursor.rowcount results = self.__cursor.fetchall() return results except pymysql.Error as e: raise pymysql.Error(e) finally: if self.__conn: self.close() # 专门处理dml语句 delete,updete,insert def exec_txsql(self, sql, args=None): try: if self.__conn is None: self.__init_conn() self.__init_cursor() if self.__cursor is None: self.__init_cursor() self.rows_affected=self.__cursor.execute(sql, args) self.lastrowid = self.__cursor.lastrowid return self.rows_affected except pymysql.Error as e: raise pymysql.Error(e) finally: if self.__cursor: self.__cursor.close() self.__cursor = None # 提交 def commit(self): try: if self.__conn: self.__conn.commit() except pymysql.Error as e: raise pymysql.Error(e) finally: if self.__conn: self.close() #回滚操作 def rollback(self): try: if self.__conn: self.__conn.rollback() except pymysql.Error as e: raise pymysql.Error(e) finally: if self.__conn: self.close() # 适用于需要获取插入记录的主键自增id def get_lastrowid(self): return self.lastrowid #获取dml操作影响的行数 def get_affectrows(self): return self.rows_affected #MySQL_Utils初始化的实例销毁之后,自动提交 def __del__(self): self.commit() 四 小结 前几天刚刚将我们的系统中的MySQLdb 替换为PyMySQL, 还未遇到问题。欢迎大家使用测试上述脚本,有问题欢迎和我讨论。如果本文对您有帮助 ,可以赞助 一瓶饮料。
一 前言 相信大部分能看到这篇blog的人都听说过知乎吧?如果你没有听说过,那么链接在这里 知乎 作为一个知乎er,为了更加深入的理解“xxx 是一种什么体验”(的图片),为了践行 “技术改变生活”(实则有些wuliao) ,使用requsets 爬取知乎中最优价值的内容,本文本着探索的精神,写一段获取内容的python程序。二 践行 #!/usr/bin/env python #-*- coding:utf-8 -*- import re import requests import os from urlparse import urlsplit from os.path import basename def getHtml(url): session = requests.Session() # 模拟浏览器访问 header = { 'User-Agent': "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36", 'Accept-Encoding': 'gzip, deflate'} res = session.get(url, headers=header) if res.status_code == 200: content = res.content else: content = '' return content def mkdir(path): if not os.path.exists(path): print '新建文件夹:', path os.makedirs(path) return True else: print u"图片存放于:", os.getcwd() + os.sep + path return False def download_pic(img_lists, dir_name): print "一共有 {num} 张照片".format(num=len(img_lists)) for image_url in img_lists: response = requests.get(image_url, stream=True) if response.status_code == 200: image = response.content else: continue file_name = dir_name + os.sep + basename(urlsplit(image_url)[2]) try: with open(file_name, "wb") as picture: picture.write(image) except IOError: print("IO Error\n") return finally: picture.close print "下载 {pic_name} 完成!".format(pic_name=file_name) def getAllImg(html): # 利用正则表达式把源代码中的图片地址过滤出来 #reg = r'data-actualsrc="(.*?)">' reg = r'https://pic\d.zhimg.com/[a-fA-F0-9]{5,32}_\w+.jpg' imgre = re.compile(reg, re.S) tmp_list = imgre.findall(html) # 表示在整个网页中过滤出所有图片的地址,放在imglist中 # 清理掉头像和去重 获取data-original的内容 tmp_list = list(set(tmp_list)) # 去重 imglist = [] for item in tmp_list: if item.endswith('r.jpg'): img_list.append(item) print 'num : %d' % (len(imglist)) return imglist if __name__ == '__main__': question_id = 35990613 zhihu_url = "https://www.zhihu.com/question/{qid}".format(qid=question_id) html_content = getHtml(zhihu_url) path = 'zhihu_pic' mkdir(path) # 创建本地文件夹 img_list = getAllImg(html_content) # 获取图片的地址列表 download_pic(img_list, path) # 保存图片 本代码还存在一些不足的地方,无法完全获取全部的图片,需要在兼容 自动点击 ”更多“ 加载更多答案。 代码第二版解决了第一版代码中不能自动加载的问题。 #!/usr/bin/env python #-*- coding:utf-8 -*- import re import requests import os from urlparse import urlsplit from os.path import basename headers = { 'User-Agent': "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36", 'Accept-Encoding': 'gzip, deflate'} def mkdir(path): if not os.path.exists(path): print '新建文件夹:', path os.makedirs(path) return True else: print u"图片存放于:", os.getcwd() + os.sep + path return False def download_pic(img_lists, dir_name): print "一共有 {num} 张照片".format(num=len(img_lists)) for image_url in img_lists: response = requests.get(image_url, stream=True) if response.status_code == 200: image = response.content else: continue file_name = dir_name + os.sep + basename(urlsplit(image_url)[2]) try: with open(file_name, "wb") as picture: picture.write(image) except IOError: print("IO Error\n") continue finally: picture.close print "下载 {pic_name} 完成!".format(pic_name=file_name) def get_image_url(qid, headers): # 利用正则表达式把源代码中的图片地址过滤出来 #reg = r'data-actualsrc="(.*?)">' tmp_url = "https://www.zhihu.com/node/QuestionAnswerListV2" size = 10 image_urls = [] session = requests.Session() # 利用循环自动完成需要点击 “更多” 获取所有答案,每个分页作为一个answer集合。 while True: postdata = {'method': 'next', 'params': '{"url_token":' + str(qid) + ',"pagesize": "10",' + '"offset":' + str(size) + "}"} page = session.post(tmp_url, headers=headers, data=postdata) ret = eval(page.text) answers = ret['msg'] size += 10 if not answers: print "图片URL获取完毕, 页数: ", (size - 10) / 10 return image_urls #reg = r'https://pic\d.zhimg.com/[a-fA-F0-9]{5,32}_\w+.jpg' imgreg = re.compile('data-original="(.*?)"', re.S) for answer in answers: tmp_list = [] url_items = re.findall(imgreg, answer) for item in url_items: # 这里去掉得到的图片URL中的转义字符'\\' image_url = item.replace("\\", "") tmp_list.append(image_url) # 清理掉头像和去重 获取data-original的内容 tmp_list = list(set(tmp_list)) # 去重 for item in tmp_list: if item.endswith('r.jpg'): print item image_urls.append(item) print 'size: %d, num : %d' % (size, len(image_urls)) if __name__ == '__main__': question_id = 26037846 zhihu_url = "https://www.zhihu.com/question/{qid}".format(qid=question_id) path = 'zhihu_pic' mkdir(path) # 创建本地文件夹 img_list = get_image_url(question_id, headers) # 获取图片的地址列表 download_pic(img_list, path) # 保存图片
一 介绍 在构建数据库自动化运维系统的时候,数据库服务器上必须要有一个agent来执行web服务器端发起的命令,我们研究了好几种技术Celery,Redis Queue 或者基于socket实现,当然还有自己写,因为之前有同事已经完成了一个agent---servant,在和同事沟通之后,我们决定复用servant,不用重复造轮子。servant是一款基于go语言编写的,通过http协议调用,提供权限认证和远程调用,支持异步执行命令的agent ,满足我们目前数据库备份任务,定时收集数据库元数据信息,定时校验备份的有效性的任务需求。本文是一篇how to 文档,相对比较详细的介绍如何安装和使用servant,希望对读者朋友有所帮助。二安装2.1 软件准备 因为该agent是基于go语言编写的,所以要安装 go语言包 yum install -y go cd /opt/ git clone https://github.com/xiezhenye/servant.git cd /opt/servant 方式一 make rpm 方式二 make 2.2 目录结构 编译之后查看主要的目录结构 bin # 编译的二进制文件 conf # 配置文件目录 example # README.md scripts #servantctl 执行文件 用于启停 查看状态等 src #源代码文件 维护servant的操作命令 /opt/servant/scripts/servantctl (start|stop|restart|status|help) 启动的时候遇到报错请到/data/logs/servant/servant.log 查看log的信息哪里有错2.3 配置文件详解 默认在/opt/servant/conf里面有配置文件 servant.xml <?xml version="1.0" encoding="UTF-8"?> <config> <server> <listen>:2465</listen> #监听的端口 <auth enabled="true"> #调用的时候是否启用 权限 验证,生产环境建议开启 <maxTimeDelta>30</maxTimeDelta> # 启动权限验证的时候 超时时间,超过30s 则认为该调用无效 </auth> <log>/data/logs/servant/servant.log</log> # 日志目录log ,这是有赞标准的日志目录,其他朋友在自己环境需要适当调整 </server> <!-- ...... --> </config example 的配置文件,使用的时候需要根据实际情况进行调整 <?xml version="1.0" encoding="utf-8" ?> <config> <server> ##server和/opt/servant/conf/servant.xml 配置是一样的。 <listen>:2465</listen> <auth enabled="0"> <maxTimeDelta>300</maxTimeDelta> </auth> <log>servant.log</log> </server> #commands 定义了一个可执行的命令组,其中包含了多个command,其中 lang 可以是exec 或者bash id 是每一组command的标示,runas标示以什么样的用户执行。 background="true" 标示以后台方式执行,并且servant 立即返回 <commands id="db1"> <command id="foo" runas="mysql" lang="bash"> <code>echo "hello world $(whoami)"</code> </command> <command id="grep" lang="exec"> <code>grep hello</code> </command> <command id="sleep" timeout="5" lang="exec"> <code> sleep ${t}</code> </command> </commands> # daemon <daemon id="daemon1" retries="10" lang="bash"> <code>sleep 10000</code> </daemon> # 定时器 ,定期执行某一个命令 tick 执行命令的间隔 deadline 命令执行的最长时间,如果为5s 则命令最长执行5s ,超过5s会被kill掉? <timer id="xx" tick="5" deadline="5" lang="bash"> <code> <![CDATA[ date >>/tmp/timer.log ]]> </code> </timer> #文件操作类,和commands类似,可以配置多个操作文件的命令,主要包含 获取文件内容,创建文件,删除文件,读取指定字节范围 root 表示有权限访问指定的目录,例子中是访问 /tmp/ 目录下的文件。 <files id="db1"> <dir id="binlog1"> <root>/tmp/</root> <allow>get</allow> <allow>head</allow> <allow>post</allow> <allow>delete</allow> <allow>put</allow> <pattern>log-bin\.\d+</pattern> #正则表达式 </dir> </files> #这个比较少用 访问数据库 <database id="mysql" driver="mysql" dsn="root:@tcp(127.0.0.1:3306)/test"> <query id="select_1">select 1;</query> </database> # <vars id="vars"> <var id="foo"> <value>bar</value> </var> <var id="hello" expand="true"> <value>${world}</value> </var> </vars> # 配合auth=true的时候一起使用,访问的时候 必须使用和配置文件中指定的user ,否则不能调用servant <user id="user1"> <key>someKey</key> <host>192.168.1.0/24</host> #指定允许访问servant 的ip源地址。通常建议使用本地调用,更加安全。 <files id="db1" /> <commands id="db1" /> </user> </config> 以上针对常用的配置做了解释,更加详细的解释可以参考 servant的readme.md2.4 具体的测试用例 为了测试方便,先去掉权限认证。 comand 支持get 和post 两种方式调用 [root@rac4 22:38:05 /opt/servant/conf/extra] # curl http://127.0.0.1:2465/commands/db1/foo hello world mysql [root@rac4 22:40:07 /opt/servant/conf/extra] # echo "hello world" | curl -XPOST http://127.0.0.1:2465/commands/db1/grep -d @- hello world [root@rac4 22:40:08 /opt/servant/conf/extra] # echo "hxxello world" | curl -XPOST http://127.0.0.1:2465/commands/db1/grep -d @- 文件类型操作获取文件内容 [root@rac4 22:38:00 /opt/servant/conf/extra] # curl http://127.0.0.1:2465/files/db1/test/yz.log youzan ,nihao ,yangyi dba 创建文件 [root@rac4 22:41:56 /opt/servant/conf/extra] # curl -XPOST http://127.0.0.1:2465/files/db1/test/54.txt -d "hello world " 验证上面的写入情况 [root@rac4 22:42:03 /opt/servant/conf/extra] # curl http://127.0.0.1:2465/files/db1/test/54.txt hello world 更新文件内容 [root@rac4 22:45:13 /opt/servant/conf/extra] # curl -XPUT http://127.0.0.1:2465/files/db1/test/54.txt -d "yangyi dba" [root@rac4 22:45:26 /opt/servant/conf/extra] # curl http://127.0.0.1:2465/files/db1/test/54.txt yangyi dba 开启权限验证,生产环境下从安全的角度考虑建议开启权限验证 修改配置文件 启用auth 为true 和设置user 配置 [root@rac4 22:16:50 /opt/servant/conf] # uri='/commands/db1/foo' # ts=$(date +%s) # key=someKey # curl -H "Authorization: ${user} ${ts} $(echo -n "${user}${key}${ts}GET${uri}"|sha1sum|cut -f1 -d' ')" "http://127.0.0.1:2465${uri}" [root@rac4 22:30:30 /opt/servant/conf] log报错 执行失败,因为ts 的实际时间是22:16:50,执行的实际时间是22:30:30 超时时间是30s,故调用失败 2017/05/05 22:30:29 INFO (6) [commands] + 127.0.0.1:42798 GET /commands/db1/foo 2017/05/05 22:30:30 WARN (6) [commands] - auth failed: timestamp delta too large 重新设置时间 ts 再次执行 成功。 [root@rac4 22:30:58 /opt/servant/conf] # ts=$(date +%s) [root@rac4 22:31:02 /opt/servant/conf] # curl -H "Authorization: ${user} ${ts} $(echo -n "${user}${key}${ts}GET${uri}"|sha1sum|cut -f1 -d' ')" "http://127.0.0.1:2465${uri}" hello world mysql 日志输出 2017/05/05 22:31:05 INFO (7) [commands] + 127.0.0.1:42808 GET /commands/db1/foo 2017/05/05 22:31:05 INFO (7) [commands] command: [bash -c echo "hello world $(whoami)"] 2017/05/05 22:31:05 INFO (7) [commands] process started. pid: 27706 2017/05/05 22:31:05 INFO (7) [commands] - execution done 2.5 安装过程中遇到的问题 1 安装的时候 需要创建 mkdir -p /opt/servant/conf/extra 2 认证权限问题 因为默认的/opt/servant/conf/servant.xml 的auth =true ,需要改为false。 不然使用curl 执行命令 curl http://127.0.0.1:2465/commands/db1/foo 日志里面报错 2017/05/05 21:52:30 INFO (3) [commands] + 127.0.0.1:41988 GET /commands/db1/foo 2017/05/05 21:52:31 WARN (3) [commands] - auth failed: bad auth header三 总结 总体上而言 ,servant能够满足大部分的agent需求,欢迎大家使用,如果有任何问题 可以联系我反馈或者联系 作者 如果您觉得对你的devops有帮助 ,可以赞助 一瓶饮料。
一 装饰器是什么 装饰器是一个用于封装函数或者类的代码工具,显式地将封装器作用于函数或者类上,达到程序运行时动态增加功能的目的。对于函数运行前处理常见前置条件(常见的web登陆授权验证),或者在函数执行之后做善后工作(比如异常处理,记录log 等等)。二 如何使用装饰器 装饰器本质上就是一个可用接受调用也可以返回调用的高阶函数。该函数以被装饰的函数为参数(还可以加上其他值作为参数)。在装饰器内进行装饰器的逻辑处理,执行被装饰函数,并返回一个装饰过的函数,听起来是不是有点绕,Talk is cheap,show me the code . 本文使用函数now 和函数add作为例子, import datetime def now(): print 'now is ', datetime.datetime.today() def add(x, y): ret = x + y print '{x} + {y} = {retval}'.format(x=x,y=y,retval=ret) 2.1 装饰器语法 有两种方式显示调用装饰器的方法。 方法一:func = deco(func) 方法二:Python 2.5之后 为装饰器引入了特殊的语法 @ --语法糖,在装饰器名称前使用@ 符号,添加在被装饰的函数定义之前。 @deco def now(): print 'now is ', datetime.datetime.today() # 调用now now() 本文将从 参数这个角度来由浅入深介绍装饰器,函数有不带参数和带参数的两种情况,装饰器也有带参数和不带参数的两种情况,装饰器对处理带参数和不带参数的情况也会有锁不同。2.2 不带参数的情况 我们需要在调用函数 now 之前和之后加上调用记录。 def deco(func): print 'begin call %s():' % (func.__name__) func() print 'end call %s():' % (func.__name__) return func #装饰器的参数是被装饰的函数对象,返回原函数的对象。 yangyiDBA:test yangyi$ python 1.py begin call now(): now is 2017-05-01 14:40:57.309836 end call now(): now is 2017-05-01 14:40:57.309868 但是从上面的例子看 结果输出了两次now 时间,明显不符合我们的要求,因为装饰器必须返回被调用函数,return func的时候发生了第二次。后面我们会解决这个问题。2.3 带参数的情况,因为函数的参数个数是不确定的 ,我们需要借助(*args, **kwargs),自动适应变参和命名参数。 #!/usr/bin/env python # coding:utf-8 import datetime import functools def deco(func): @functools.wraps(func) # def wrapper(*args, **kw): print 'begin call %s():' % (func.__name__) result=func(*args, **kw) # 如果函数无返回值 ,可以直接使用func(*args, **kw) print 'end call %s():' % (func.__name__) return result #这里 result 是为了func 有返回值, return wrapper @deco def add(x, y): ret = x + y print '{x} + {y} = {retval}'.format(x=x,y=y,retval=ret) @deco def now(): print 'now is ', datetime.datetime.today() add(2,5) now() 上面的装饰器做了如下事情 1 函数func作为参数传给 deco()。 2 functool.wraps 将func 的属性复制给 warper。 3 执行函数func前后执行某些动作。 4 返回结果。 5 返回wrapper 函数对象。 这里特别说明functool.wraps的作用,由于装饰器导致解释器认为函数本身发生了改变,在某些情况下可能会导致一些问题。Python通过functool.wraps解决了这个问题: 在编写装饰器时,在实现前加入 @functools.wraps(func) 可以保证装饰器不会对被装饰函数造成影响。 特别说明其他要使用装饰器的时候会有其他的写法 比如直接返回被装饰的函数。 def deco(func): @functools.wraps(func) # def wrapper(*args, **kw): print 'begin call %s():' % (func.__name__) return func(*args, **kw) return wrapper 输出 yangyiDBA:test yangyi$ python 1.py begin call add(): 2 + 5 = 7 end call add(): begin call now(): now is 2017-05-01 15:20:51.597859 end call now(): 2.4 带参数的装饰器 如果装饰器本身传入参数,就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本: #!/usr/bin/env python # coding:utf-8 import datetime import functools def deco(text): def _deco(func): def wrapper(*args, **kw): print '%s, begin call %s():' % (text,func.__name__) result=func(*args, **kw) # 如果函数无返回值 ,可以直接使用func(*args, **kw) print '%s, end call %s():' % (text,func.__name__) return result #这里 result 是为了func 有返回值, return wrapper return _deco @deco("yangyi") def add(x, y): ret = x + y print '{x} + {y} = {retval}'.format(x=x,y=y,retval=ret) @deco("youzan") def now(): print 'now is ', datetime.datetime.today() add(2,5) now() 测试结果: yangyiDBA:test yangyi$ python 2.py yangyi, begin call add(): 2 + 5 = 7 yangyi, end call add(): youzan, begin call now(): now is 2017-05-01 18:47:54.728296 youzan, end call now(): 2.5 Python内置装饰器 在Python中有三个内置的装饰器,都是跟class相关的:staticmethod、classmethod 和property。 staticmethod 是类静态方法,其跟成员方法的区别是没有 self 参数,并且可以在类不进行实例化的情况下调用 classmethod 与成员方法的区别在于所接收的第一个参数不是 self (类实例的指针),而是cls(当前类的具体类型) property 是属性的意思,表示可以通过通过类实例直接访问的信息2.6 跨文件调用,因为装饰器本质是一个函数。在工程实现里我们可以通过创建一个公用的decorator,作为基础装饰器供其他函数调用。 三 小结 Python一切皆对象,函数也是,也可以赋值给其他变量,理解这点再去理解装饰器就容易多了。刚刚研究装饰器,总结的可能比较浅显,想要深入学习装饰器的可以看看下面的 参考文章。四 参考文章 1 《Python高级编程》 第一章装饰器 2 《装饰器》 3 Python装饰器学习(九步入门) 4 《详解Python的装饰器》
一 简介 最近在和 同事 一起开发一套数据库管理平台 ,该平台使用Django 作为web 框架。和大多数数据库管理平台一样 ,该平台提供 备份,恢复,申请实例,实例上下线 以及数据质量对比 等功能。本文主要是记录 开发一套系统使用哪些功能组件。二 基础组件 2.1 web框架 Django 一款通用的web 框架,缺点不支持异步调用模式,推荐大家尝试使用Flask 。 2.2 前端 Bootstrap 典型的UI方案 JavaScript 实现按钮功能,异步刷新 2.3 定时功能 Django-crontab 执行定时任务 Celery 分布式任务调度 2.4 agent ,后台功能 os ,subprocess 模块 用于执行系统命令,文件操作。 requests 用于调用管理平台的api,解决数据库服务器使用密码和元数据进行交户的问题。 servant 基于go语言实现的agent ,开源工具。 2.5 日志记录 logging 记录系统异常 和自定义日志输出。 2.6 数据库交互 自己定义了一个MySQL orm ,增强对表设计结构的控制。 2.7 登陆认证 Django-auth 结合本公司自己的账号系统,有效控制系统权限。 2.8 三 开发工具 IDE PyCharm CE 版本 代码管理 git SourceTree git 客户端管理工具 (提高效率,但是用多了会导致对git命令的生疏)四 总结 暂时就写这么多吧,自己还在逐步深入了解其他的Python 的各种模板,以后会随着系统平台功能的完善,陆续更新本博客。也期望能够给以后入手写运维平台的朋友一点借鉴。
一 前言 最近一直在做开发相关的工作--基于Django的web 平台,其中需要从model层传输数据到view 层做数据展示或者做业务逻辑处理。我们采用通用的Json格式--Json(JavaScript Object Notation) 是一种轻量级的数据交换格式,易于阅读和程序解析。二 认识Json 2.1 Json 结构 常见的Json格式为 “名称/值”对的集合,其中 值可以是对象,列表,字典,字符串等等。比如 backup_data = {"back_to_host": "dbbk0", "ip_address": "10.10.20.3", "host_name": "rac4", "port": 3306} 2.2 使用Json Python的Json模块序列化与反序列化的过程分别是 编码和解码。这两个过程涉及到两组不同的函数 编码 把一个Python对象编码转换成Json字符串,json.dumps(data)/json.dump(data,file_handler) 解码 把Json格式字符串解码转换成Python对象,json.loads(data)/json.load(file_handler) 在python中要使用Json模块做相关操作,必须先导入: import Json 2.3 主要函数 编码函数主要有 json.dumps(data)/json.dump(data,file_handler) json.dumps()的参数是将python对象转换为字符串,如使用json.dumps序列化的对象json_dumps=json.dumps({'a':1, 'b':2}) ,json_dumps='{"b": 2, "a": 1}' json.dump 是将内置类型序列化为json对象后写入文件。 解码函数主要由json.loads(data)/json.load(file_handler) json.loads的参数是内存对象,把Json格式字符串解码转换成Python对象,json_loads=json.loads(d_json) #{ b": 2, "a": 1},使用load重新反序列化为dict json.load()的参数针对文件句柄,比如本地有一个文件/tmp/test.json json_load=json.load(open('/tmp/test.json')) 具体案例参考如下: In [3]: data={"back_to_host": "rac1", ...: "ip_address": "10.215.20.3", ...: "host_name": "rac3", ...: "port": 3306} In [7]: json_str=json.dumps(data) In [8]: print json_str {"ip_address": "10.215.20.3", "back_to_host": "rac1", "host_name": "rac3", "port": 3306} In [9]: json_loads=json.load(json_str) --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-9-180506f16431> in <module>() ----> 1 json_loads=json.load(json_str) /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.pyc in load(fp, encoding, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw) 284 285 "" 注意 从上面的报错信息来看 json.loads 传参是字符串类型,并不是文件句柄,没有 read()属性。 In [10]: json_loads=json.loads(json_str) In [11]: print json_loads {u'back_to_host': u'rac1', u'ip_address': u'10.215.20.3', u'host_name': u'rac3', u'port': 3306} In [12]: type(json_loads) Out[12]: dict In [13]: type(json_str) Out[13]: str 利用dump 将数据写入 dump.json In [17]: with open('/tmp/dump.json','w') as f: ...: json.dump(json_str,f) ...: yangyiDBA:~ yangyi$ cat /tmp/dump.json "{\"ip_address\": \"10.10.20.3\", \"back_to_host\": \"rac1\", \"host_name\": \"rac3\", \"port\": 3306}" yangyiDBA:~ yangyi$ 利用json.load 将dump.sjon的数据读出来并赋值给 data In [18]: with open('/tmp/dump.json','r') as f: ...: data=json.load(f) ...: In [19]: print data {"ip_address": "10.10.20.3", "back_to_host": "rac1", "host_name": "rac3", "port": 3306} 三 小结 本文算是一篇学习笔记,主要对比了json.loads/json.load , json.dumps/ json.dump 的使用差异 ,方便以后更好的使用json 。 推荐文章Json概述以及python对json的相关操作
一 简介 最近在压测新的存储,正好把工作过程中积累的对高性能MySQL相关的知识体系构建起来,做成思维导图的方式。总结乃一家之言,有不妥之处,望给位读者朋友指正。二 思维导图 构建高性能MySQL系统涵盖从单机 硬件,os ,文件系统,内存,到MySQL 本身的配置,以及schema 设计,索引设计 ,再到数据库架构上的水平和垂直拓展。 说明 1 IO相关的优化可能还不完整,以后会逐步完善。 2 关于数据库系统水平和垂直拆分是一个比较大的命题,这里略过,每个公司的业务规模不一样,选取的拆分策略也有所不同。
一 前言 在讨论数据表字段设计的时候,有同学提出使用vabinary 代替 varchar ,部分开发不明所以,其实我也是。两者之间具体有什么区别?使用vabinary 代替 varchar 对业务有何优势?本文尝试从性能,数据大小,查询,创建索引等对比功能等方面进行研究,有不妥或者不到位之处还请各位读者朋友提示。二 对比测试2.1 测试环境 数据库版本 Percona Server 5.6.24-72.2-log create table vbinary ( id int primary key auto_increment , val varbinary(776) not null default '' ) engine=innodb default charset=utf8mb4; create table vachar ( id int primary key auto_increment , val varchar(12) not null default '' ) engine=innodb default charset=utf8mb4; insert into vbinary(val) values('abaa'),('aabb'),('bcdd'),('ccdd'); insert into vachar(val) values('abaa'),('aabb'),('bcdd'),('ccdd') 2.2 定义 varchar(N) 字符串类型,用于存储变长字符串,使用表默认或者指定的校验集合,其中N代表存储字符的个数,详细信息请移步《浅谈varchar(N)》. varbinary(N)二进制字符串类型,以二进制字节串存储字符,无字符集校验区别,均以二进制实际数值作比较。2.3 长度定义 varchar存储的是字符个数,varbinary存储的是字节个数。 test [RW] 10:57:50 >insert into vbinary (val,name) value('2msdmlsdyo2enwlenw','disodmalsdsi'); Query OK, 1 row affected, 1 warning (0.00 sec) test [RW] 10:57:55 >show warnings; +---------+------+------------------------------------------+ | Level | Code | Message | +---------+------+------------------------------------------+ | Warning | 1265 | Data truncated for column 'val' at row 1 | +---------+------+------------------------------------------+ 1 row in set (0.00 sec) test [RW] 10:58:11 >insert into vbinary (val,name) value('有赞是一家移动零售服务提供商','disodmalsdsi'); Query OK, 1 row affected, 1 warning (0.01 sec) test [RW] 10:59:00 >show warnings; +---------+------+------------------------------------------+ | Level | Code | Message | +---------+------+------------------------------------------+ | Warning | 1265 | Data truncated for column 'val' at row 1 | +---------+------+------------------------------------------+ 1 row in set (0.00 sec) test [RW] 10:59:08 >select * from vbinary; +----+--------------+--------------+ | id | val | name | +----+--------------+--------------+ | 6 | 2msdmlsdyo2e | disodmalsdsi | | 7 | 有赞是一 | disodmalsdsi | # +----+--------------+--------------+ 7 rows in set (0.00 sec) test [RW] 10:59:12 >insert into vachar(val,name) value('有赞是一家移动零售服务提供商','disodmalsdsi'); Query OK, 1 row affected, 1 warning (0.00 sec) test [RW] 11:00:02 >show warnings; +---------+------+------------------------------------------+ | Level | Code | Message | +---------+------+------------------------------------------+ | Warning | 1265 | Data truncated for column 'val' at row 1 | +---------+------+------------------------------------------+ 1 row in set (0.00 sec) test [RW] 11:00:06 >select * from vachar; +----+--------------------------------------+--------------+ | id | val | name | +----+--------------------------------------+--------------+ | 4 | ccdd | yz | | 5 | 有赞是一家移动零售服务提 | disodmalsdsi | +----+--------------------------------------+--------------+ 5 rows in set (0.00 sec) 分析: varbinary(N)中长度N指的是字节串的长度,一个数字/英文字母占用1个字节,一个汉字占用3个字节(默认utf8、utf8mb4字符集),指定N 则可以存储 N 个数字或者字母,N/3个汉字。 varchar(N)中长度N指的是字符串的长度,一个数字/英文字母/汉字占用一个字符,指定N 可以存储N个字符。 注意两种存储类型支持的字段长度计算方式的不同,会给开发带来一定的困扰,使用varbinary的开发需要深入了解该字段的存储单位,设计字段的时候还要根据业务逻辑计算好N的值是多少。否则可能会被截断 ,在sql_mode为严格模式时则会报错。2.4 索引功能 分别对name字段创建索引 test [RW] 10:47:01 >alter table vbinary add name varbinary(255) not null default 'yz'; Query OK, 0 rows affected (0.02 sec) Records: 0 Duplicates: 0 Warnings: 0 test [RW] 10:47:24 >alter table vbinary add key idx_name(name); Query OK, 0 rows affected (0.01 sec) Records: 0 Duplicates: 0 Warnings: 0 test [RW] 10:48:25 >rename table vchar to vachar; Query OK, 0 rows affected (0.01 sec) test [RW] 10:49:00 >alter table vachar add name varchar(255) not null default 'yz'; Query OK, 0 rows affected (0.02 sec) Records: 0 Duplicates: 0 Warnings: 0 test [RW] 10:49:31 >alter table vachar add key idx_name(name); Query OK, 0 rows affected, 1 warning (0.02 sec) Records: 0 Duplicates: 0 Warnings: 1 test [RW] 10:49:53 >show Warnings; +---------+------+---------------------------------------------------------+ | Level | Code | Message | +---------+------+---------------------------------------------------------+ | Warning | 1071 | Specified key was too long; max key length is 767 bytes | +---------+------+---------------------------------------------------------+ 1 row in set (0.00 sec) test [RW] 10:50:06 >show create table vachar \G *************************** 1. row *************************** Table: vachar Create Table: CREATE TABLE `vachar` ( `id` int(11) NOT NULL AUTO_INCREMENT, `val` varchar(12) NOT NULL DEFAULT '', `name` varchar(255) NOT NULL DEFAULT 'yz', PRIMARY KEY (`id`), KEY `idx_name` (`name`(191)) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 1 row in set (0.00 sec) test [RW] 10:50:19 >show create table vbinary \G *************************** 1. row *************************** Table: vbinary Create Table: CREATE TABLE `vbinary` ( `id` int(11) NOT NULL AUTO_INCREMENT, `val` varbinary(12) NOT NULL DEFAULT '', `name` varbinary(255) NOT NULL DEFAULT 'yz', PRIMARY KEY (`id`), KEY `idx_name` (`name`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 1 row in set (0.00 sec) test [RW] 11:53:08 >create table vbinary1 -> ( -> id int primary key auto_increment , -> val varbinary(776) not null default '' -> ) engine=innodb default charset=utf8mb4; Query OK, 0 rows affected (0.01 sec) test [RW] 11:53:09 >alter table vbinary1 add key idx_val(val); Query OK, 0 rows affected, 1 warning (0.02 sec) Records: 0 Duplicates: 0 Warnings: 1 test [RW] 11:53:37 >show Warnings; +---------+------+---------------------------------------------------------+ | Level | Code | Message | +---------+------+---------------------------------------------------------+ | Warning | 1071 | Specified key was too long; max key length is 767 bytes | +---------+------+---------------------------------------------------------+ 1 row in set (0.00 sec) test [RW] 11:53:44 >show create table vbinary1 \G *************************** 1. row *************************** Table: vbinary1 Create Table: CREATE TABLE `vbinary1` ( `id` int(11) NOT NULL AUTO_INCREMENT, `val` varbinary(776) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `idx_val` (`val`(767)) ## 被修改为 767 ,索引支持的最大字节数。 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 1 row in set (0.00 sec) 分析: 基于 varbinary和 varchar 存储字符的长度定义不同,varchar 可以存储字符串前191个字符的索引,varbinary 字段的索引则最多可以存储767字节。如果是英文字母则可以存储更长的字符串。2.5 校验方面 test [RW] 12:15:06 >select * from vachar where val='ABAA'; +----+------+------+ | id | val | name | +----+------+------+ | 1 | abaa | yz | +----+------+------+ 1 row in set (0.00 sec) test [RW] 12:14:31 >select * from vbinary where val='ABAA'; Empty set (0.00 sec) test [RW] 12:15:11 >select * from vbinary where val='abaa'; +----+------+------+ | id | val | name | +----+------+------+ | 1 | abaa | yz | +----+------+------+ 1 row in set (0.00 sec) 分析:varbinary存储的是二进制字节串而不是字符串,这意味着它没有字符集校验的概念,排序和比较都是基于字节中的实际数值大小进行的。varchar类型存储的列在比较的时候是通过字符集的方式进行的,varchar 中'ABAA'和'abaa'是一致的.2.6 性能测试 使用mysqlslap 进行10个并发100w次查询做对比 [root@rac4 00:31:35 ~] # time mysqlslap --no-defaults -uroot --create-schema=test -S /srv/my3306/run/mysql.sock --number-of-queries=1000000 --concurrency=10 --query="select * from vbinary where val='abaa';" Benchmark Average number of seconds to run all queries: 30.569 seconds Minimum number of seconds to run all queries: 30.569 seconds Maximum number of seconds to run all queries: 30.569 seconds Number of clients running queries: 10 Average number of queries per client: 100000 real 0m30.574s user 0m8.124s sys 0m6.286s [root@rac4 00:32:18 ~] # time mysqlslap --no-defaults -uroot --create-schema=test -S /srv/my3306/run/mysql.sock --number-of-queries=1000000 --concurrency=10 --query="select * from vachar where val='abaa';" Benchmark Average number of seconds to run all queries: 31.986 seconds Minimum number of seconds to run all queries: 31.986 seconds Maximum number of seconds to run all queries: 31.986 seconds Number of clients running queries: 10 Average number of queries per client: 100000 real 0m31.991s user 0m8.351s sys 0m6.407s 分析 简单的select查询对比来看 varbinary 30.569s varchar 31.986svarbinary 相对性能有 1.4s 约4%的性能提升,在压测环境下每秒几乎3wqps,如果是普通的业务场景1000-2000左右的qps,varbinary带来的性能可以忽略不计.三 总结 本文从存储长度单位,索引,查询条件校验,性能方面做了测试,其优点是 无需考虑字符集,比较的时候安装字节比较理论上比字符要快(测试结果的确会快4% 左右,但不明显),考虑实际应用的时候 varbinary 存储单位的改变给开发带来更多的迷惑性,尤其是使用 varbinary 存储汉字时,开发需要更多的考虑具体设计多长才能满足业务需求,存在被截断的风险。从结果上来看并没有特别好的理由让我们选择varbinary。 推荐文章《官方文档》
一 sandbox是什么?MySQL Sandbox是一个非常简单快捷的安装搭建MySQL实例的工具,它可以非常快速地满足我们对MySQL环境各种需求:单机实例,主从,一主多从等等架构(区别于自己安装MySQL 软件)。比如 新的数据库版本发行之后,想要尽快尝鲜 ,又不想花太多资源去安装,就可以使用sandbox帮助我们完成创建单个或者主从结构的实例。对于那些不懂MySQL安装的开发,测试同学而言,可以使用sandbox的快速搭建一个符合要求的数据库。MySQL Sandbox 快速,是以秒来衡量的,谁用谁知道。二 如何安装和使用2.1 安装sandbox 本文的案例是基于Centos虚拟机测试。 yum install cpan -y yum install perl-Test-Simple -y cpan MySQL::Sandbox echo 'export SANDBOX_AS_ROOT=1' >> /root/.bash_profile && source /root/.bash_profile 获取Percona server 5.7.17 版本 wget "https://www.percona.com/downloads/Percona-Server-5.7/Percona-Server-5.7.17-11/binary/tarball/Percona-Server-5.7.17-11-Linux.x86_64.ssl101.tar.gz" 2.2 常用命令 安装完成之后默认会在 /usr/local/bin/ 目录下产生make_开头的文件。 make_sandbox 基于二进制压缩包创建MySQL实例 make_sandbox_from_source 基于源码创建MySQL实例,参数是而执行 ./configure && make 成功的源码存放目录 make_sandbox_from_installed 基于已经安装好的mysql可执行文件目录安装MySQL实例 make_sandbox_from_url 从网上下载docker 镜像进行安装,具体参考 --help 命令 make_multiple_sandbox 创建多个相同版本的MySQL实例 make_multiple_custom_sandbox 创建不同版本的MySQL实例 make_replication_sandbox 搭建主从复制结构,可以是一主一从,也可以是一主多从。 sbtool : sandbox管理工具 要深入了解各个命令的具体用法,请参考源码目录下的README文档,然后再自己动手实践,能理解更深刻,毕竟纸上来得终觉浅,绝知此事要躬行。下面主要通过 make_sandbox 和 make_replication_sandbox 来介绍如何使用 。2.3 使用sandbox安装单个实例 root@rac4:/data/mysql# >make_sandbox /data/mysql/Percona-Server-5.7.17-11-Linux.x86_64.ssl101.tar.gz unpacking /data/mysql/Percona-Server-5.7.17-11-Linux.x86_64.ssl101.tar.gz Executing low_level_make_sandbox --basedir=/data/mysql/5.7.17 \ --sandbox_directory=msb_5_7_17 \ --install_version=5.7 \ --sandbox_port=5717 \ --no_ver_after_name \ --my_clause=log-error=msandbox.err The MySQL Sandbox, version 3.2.05 (C) 2006-2016 Giuseppe Maxia Installing with the following parameters: upper_directory = /root/sandboxes sandbox_directory = msb_5_7_17 sandbox_port = 5717 check_port = no_check_port = datadir_from = script install_version = 5.7 basedir = /data/mysql/5.7.17 tmpdir = my_file = operating_system_user = root db_user = msandbox remote_access = 127.% bind_address = 127.0.0.1 ro_user = msandbox_ro rw_user = msandbox_rw repl_user = rsandbox db_password = msandbox repl_password = rsandbox my_clause = log-error=msandbox.err ...... 省略部分内容 prompt_prefix = mysql prompt_body = [\h] {\u} (\d) > force = no_ver_after_name = 1 verbose = load_grants = 1 no_load_grants = no_run = no_show = keep_uuid = history_dir = do you agree? ([Y],n) Y 输入Y 然后sandbox就会启动一个实例,需要等待20s 左右。 # Starting server . sandbox server started # Loading grants Your sandbox server was installed in $HOME/sandboxes/msb_5_7_17 因为本案例采用root用户安装测试,新生成的数据库目录在 /root/sandboxes/msb_5_7_17,其中的文件如下 大家可以研究各个可执行文件的具体内容。常用的有use,stop,start,restart 等等,例如 root@rac4:~/sandboxes/msb_5_7_17# >./use --登陆数据库 Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 9 Server version: 5.7.17-11 Percona Server (GPL), Release 11, Revision f60191c Copyright (c) 2009-2016 Percona LLC and/or its affiliates mysql [localhost] {msandbox} ((none)) > show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | | test | +--------------------+ 5 rows in set (0.00 sec) 搭建主从,本例中启用gtid 并且设置创建1个slave. 因为上例已经创建了一个5.7.17 源程序目录,我们可以基于该目录创建主从,当然也可以基于源码的压缩包。 root@rac4:/data/mysql# >make_replication_sandbox --gtid --how_many_slaves=1 5.7.17 installing and starting master installing slave 1 starting slave 1 .. sandbox server started initializing slave 1 replication directory installed in $HOME/sandboxes/rsandbox_5_7_17 根据结果提示sandbox创建的主从在目录$HOME/sandboxes/rsandbox_5_7_17,进入该目录查看有如下文件 其中master 和node1 分别是主库和备库的数据库目录, m和n1 都是登陆主库的命令,s1 和n2 都是登陆slave 的命令,其他的可以从文件名知道具体用途。这里介绍两个命令test_replication和check_slaves 两个命令功能类似,都是检查slave 的状态信息。check_slaves会把主库相关信息输出。 root@rac4:~/sandboxes/rsandbox_5_7_17# >sh test_replication 检查主备关系 # Master log: mysql-bin.000001 - Position: 10732 - Rows: 20 # Testing slave #1 ok - Slave #1 acknowledged reception of transactions from master ok - Slave #1 IO thread is running ok - Slave #1 SQL thread is running ok - Table t1 found on slave #1 ok - Table t1 has 20 rows on #1 # TESTS : 5 # FAILED: 0 ( 0.0%) # PASSED: 5 (100.0%) # exit code: 0 root@rac4:~/sandboxes/rsandbox_5_7_17# >./check_slaves # master port: 20192 File: mysql-bin.000001 Position: 10732 Executed_Gtid_Set: 00020192-1111-1111-1111-111111111111:1-40 slave # 1 port: 20193 Master_Log_File: mysql-bin.000001 Read_Master_Log_Pos: 10732 Slave_IO_Running: Yes Slave_SQL_Running: Yes Exec_Master_Log_Pos: 10732 Retrieved_Gtid_Set: 00020192-1111-1111-1111-111111111111:1-40 Executed_Gtid_Set: 00020192-1111-1111-1111-111111111111:1-40 三 小结 按照之前要部署虚拟机安装MySQL的时间和精力来看,使用sandbox的感觉就是一个字-爽,只需简单的命令即可完成而且对使用者几乎是透明的当你需要快速搭建最小化测试环境时,完全可以使用sandbox助你一臂之力。当然本文仅仅只是本人在比较短时间内测试的总结,需要更加深入了解sandbox使用的,可以多看看源码和各个命令。推荐 官方文档
一 前言 大家对于MySQL的逻辑备份工具mysqldump应该都比较了解,相对于mysqldump,本文介绍一款由MySQL ,Facebook 等公司的开发维护另外一套逻辑备份恢复工具---mydumper/myloader目前已经开发到0.9.1 版本。 mydumper 具有如下特性 1 支持多线程导出数据,速度比mysqldump快。 2 支持一致性备份,使用FTWRL(FLUSH TABLES WITH READ LOCK)会阻塞DML语句,保证备份数据的一致性。 3 支持将导出文件压缩,节约空间。 4 支持多线程恢复。 5 支持以守护进程模式工作,定时快照和连续二进制日志 6 支持按照指定大小将备份文件切割。 7 数据与建表语句分离。二 原理参考一张图 介绍mydumper的工作原理 mydumper的主要工作步骤 1 主线程 FLUSH TABLES WITH READ LOCK, 施加全局只读锁,以阻止DML语句写入,保证数据的一致性 2 读取当前时间点的二进制日志文件名和日志写入的位置并记录在metadata文件中,以供即使点恢复使用 3 START TRANSACTION WITH CONSISTENT SNAPSHOT; 开启读一致事务 4 启用N个(线程数可以指定,默认是4)dump线程导出表和表结构 5 备份非事务类型的表 6 主线程 UNLOCK TABLES,备份完成非事务类型的表之后,释放全局只读锁 7 dump InnoDB tables, 基于事物导出InnoDB表 8 事物结束三 安装使用3.1 安装 mydumper 基于c语言编写,需要编译安装,因此需要安装编译工具。 yum install glib2-devel mysql-devel zlib-devel pcre-devel zlib gcc-c++ gcc cmake -y wget https://launchpadlibrarian.net/225370879/mydumper-0.9.1.tar.gz tar xf mydumper-0.9.1.tar.gz cd mydumper-0.9.1/ cmake . make && make install 点击(此处)折叠或打开 # cmake . -- Using mysql-config: /opt/mysql/bin/mysql_config -- Found MySQL: /opt/mysql/include, /usr/lib64/libperconaserverclient.so;/usr/lib64/libpthread.so;/usr/lib64/libm.so;/usr/lib64/librt.so;/usr/lib64/libdl.so -- Found ZLIB: /usr/lib64/libz.so (found version "1.2.3") -- Found PkgConfig: /usr/bin/pkg-config (found version "0.23") -- checking for one of the modules 'glib-2.0' -- checking for one of the modules 'gthread-2.0' -- checking for module 'libpcre' -- found libpcre, version 7.8 -- Found PCRE: /usr/include CMake Warning at docs/CMakeLists.txt:9 (message): Unable to find Sphinx documentation generator -- ------------------------------------------------ -- MYSQL_CONFIG = /opt/mysql/bin/mysql_config -- CMAKE_INSTALL_PREFIX = /usr/local -- BUILD_DOCS = ON -- WITH_BINLOG = OFF -- RUN_CPPCHECK = OFF -- Change a values with: cmake -D<Variable>=<Value> -- ------------------------------------------------ -- -- Configuring done -- Generating done -- Build files have been written to: /root/mydumper-0.9.1 # make Scanning dependencies of target mydumper [ 25%] Building C object CMakeFiles/mydumper.dir/mydumper.c.o [ 50%] Building C object CMakeFiles/mydumper.dir/server_detect.c.o [ 75%] Building C object CMakeFiles/mydumper.dir/g_unix_signal.c.o Linking C executable mydumper [ 75%] Built target mydumper Scanning dependencies of target myloader [100%] Building C object CMakeFiles/myloader.dir/myloader.c.o Linking C executable myloader [100%] Built target myloader # make install [ 75%] Built target mydumper [100%] Built target myloader Linking C executable CMakeFiles/CMakeRelink.dir/mydumper Linking C executable CMakeFiles/CMakeRelink.dir/myloader Install the project... -- Install configuration: "" -- Installing: /usr/local/bin/mydumper -- Installing: /usr/local/bin/myloader 安装好之后 ,会生成两个文件: /usr/local/bin/mydumper /usr/local/bin/myloader注意: 一般会遇到找不到 mysql-libraries 的问题,可以参考 stackoverflow 的回答,如果再解决不了,则可能是自己制定的MySQL安装目录的问题,比如我自己的安装目录是/opt/mysql/ 则需要做一个软连接 ln -s /opt/mysql/lib/libperconaserverclient.so /usr/lib64/libperconaserverclient.so 3.2 参数说明mydumper 的常用参数 -B, --database 要导出的dbname -T, --tables-list 需要导出的表名,导出多个表需要逗号分隔,t1[,t2,t3 ....] -o, --outputdir 导出数据文件存放的目录,mydumper会自动创建 -s, --statement-size 生成插入语句的字节数, 默认1000000字节 -r, --rows Try to split tables into chunks of this many rows. This option turns off --chunk-filesize -F, --chunk-filesize 切割表文件的大小,默认单位是 MB ,如果表大于 -c, --compress 压缩导出的文件 -e, --build-empty-files 即使是空表也为表创建文件 -x, --regex 使用正则表达式匹配 db.table -i, --ignore-engines 忽略的存储引擎,多个值使用逗号分隔 -m, --no-schemas 只导出数据,不导出建库建表语句 -d, --no-data 仅仅导出建表结构,创建db的语句 -G, --triggers 导出触发器 -E, --events 导出events -R, --routines 导出存储过程和函数 -k, --no-locks 不执行临时的只读锁,会导致备份不一致 。WARNING: This will cause inconsistent backups --less-locking 最小化在innodb表上的锁表时间 --butai -l, --long-query-guard 设置长时间执行的sql 的时间标准 -K, --kill-long-queries 将长时间执行的sql kill -D, --daemon 以守护进程的方式执行 -I, --snapshot-interval 创建导出快照的时间间隔,默认是 60s ,该参数只有在守护进程执行的时候有用。 -L, --logfile 指定mydumper输出的日志文件,默认使用控制台输出。 --tz-utc SET TIME_ZONE='+00:00' at top of dump to allow dumping of TIMESTAMP data when a server has data in different time zones or data is being moved between servers with different time zones, defaults to on use --skip-tz-utc to disable. --skip-tz-utc --use-savepoints 使用savepoints 减少MDL 锁事件 需要 SUPER 权限 --success-on-1146 Not increment error count and Warning instead of Critical in case of table doesn myloader使用参数 -d, --directory 备份文件的文件夹 -q, --queries-per-transaction 每次事物执行的查询数量,默认是1000 -o, --overwrite-tables 如果要恢复的表存在,则先drop掉该表,使用该参数,需要备份时候要备份表结构 -B, --database 需要还原的数据库 -e, --enable-binlog 启用还原数据的二进制日志 -h, --host The host to connect to -u, --user Username with privileges to run the dump -p, --password User password -P, --port TCP/IP port to connect to -S, --socket UNIX domain socket file to use for connection -t, --threads 还原所使用的线程数,默认是4 -C, --compress-protocol 压缩协议 -V, --version 显示版本 -v, --verbose 输出模式, 0 = silent, 1 = errors, 2 = warnings, 3 = info, 默认为2 四 使用方法 #导出整个库 mydumper -u root -S /srv/my3308/run/mysql.sock -B trade_platform -o /data/trade_platform #仅仅导出platform的ddl语句不包含数据到指定的目录 /data/platform mydumper -u root -S /srv/my3308/run/mysql.sock -B platform -m -o /data/platform #以压缩的方式导出的文件 mydumper -u root -S /srv/my3308/run/mysql.sock -B trade_platform -c -o /data/trade_platform 备份文件以.gz 的格式压缩 #ls metadata trade_platform.config.sql.gz trade_platform.trade_order-schema.sql.gz trade_platform.config-schema.sql.gz trade_platform-schema-create.sql.gz trade_platform.trade_order.sql.gz #使用正则表达式 mydumper -u root -S /srv/my3308/run/mysql.sock --regex='^(?!(mysql|test))' -o /data/bk20170120 其中正则表达式可以是 --regex=order.* 导出所有order 开头的表 mydumper 导出的文件 [root@rac4 17:27:02 /data/platform] # ls metadata platform.config.sql platform.order.sql mydumper 导出的文件 分为 metadata :包含导出时刻的binlog 位点信息 ,如果启用gtid ,则记录gtid信息。 Started dump at: 2017-01-20 17:26:53 SHOW MASTER STATUS: Log: mysql-bin.000025 Pos: 505819083 GTID: Finished dump at: 2017-01-20 17:27:02 db.table.sql :数据文件,insert语句 db.table-schema.sql :包含建表语句 db-schema.sql :包含建库语句 注意 0.9.1 版本去掉了 --binlogs 参数,故会少了 启用binlogs参数相关的文件。 有兴趣的朋友可以继续阅读 这里,有专门针对mydumper与5.7 新出的mysqlpump 工具的讨论。五 小结 从目前的测试来看,mydumper对备份的速度有一定提升,但是没有网络上说的10倍那么高。我已经在我们的开发测试环境部署了该工具的备份脚本,用来解决开发同学的偶尔冒失行为。相比mysqldump,mydumper的导出的文件形式是每个表一个文件,对于开发/测试环境的误操作恢复十分有效。 参考文章 [1] mydumper备份原理和使用方法
一 前言 DBA 运维就是填坑的过程,其他人挖坑,自己填;自己挖坑,自己填,说多了都是泪。好吧言归正传,今天凌晨忙碌了一个通宵做IDC 交互机维护改造以及升级数据库服务器的事情,需要重启服务器。重启完成OS和重新部署数据库周边配套设施之后,系统没有问题,早上8点多开始数据库的error log 一直出现,别问为啥现在才处理(我补觉到11点多 ,13点才到公司。) 2017-01-10 20:47:45 21194 [Warning] Too many connections 二 排查 上面的报警信息虽然不严重,但是error log一直有warnning信息写入,总是要解决的。遇到“Too many connections”报错通常的情况是当前的数据库连接数超过了系统 max_connections,max_user_connections 设置的大小。从这方面入手,具体的排查思路。通常会检查 1 数据库能否登陆,登陆是否会报错,但是登陆db 无异常。 2 检查 max_connections,max_user_connections的大小配置 3 检查数据库系统的连接分布,如下图 显然都正常的 ,系统配置 4000个连接,单个用户300多个连接 ,远远小于系统设置的值。 SELECT substring_index(host, ':',1) AS host_name,state,count(*) FROM information_schema.processlist GROUP BY state,host_name; 分析到这里似乎陷入了僵局。在当前连接数 < max_connections 且当前连接数< max_user_connections 的时候,竟然出现了 "[Warning] Too many connections ",于是乎问了其他DBA同行,拓展一下思路,他们也表示差异,也无其他思路。 和凌晨一起做变更的同事反馈目前遇到的问题,他的提示一语惊醒梦中人---我们启用sql-killer(类似pt-killer实时监测系统,有执行时间超过1.02s左右的sql 就会kill掉)是通过管理端口连接数据库。 什么是管理端口---在MySQL启动时使用该参数extra_port指定一个端口号(不要和正常的数据库服务端口冲突),Percona Server会监听来自该端口的请求。启用该参数可以解决使用thread_pool特性时,由于所有的连接池worker忙于处理慢querey或者被锁定导致DBA无法通过正常的端口连接DB, 以便DBA可以正常维护数据库。显然使用管理端口的初衷是好的 ,也是避免慢查询堵住数据库,sql-killer可以从管理端口连接到db,然后kill 产生慢查询的会话。 于是我们把sql-killer 停止,果然 error信息也随之停止。从管理端口方面检查发现 extra_max_connections 重启之后 变为1 ,导致sql-killer请求无法连接,报"Too many connections " 。知道原因之后 就是动态修改实例中的参数,修改配置文件中的参数,然后重启sql-killer ,至此问题解决。三 小结 解决这个异常大概花费了大约1.5h,究其原因还是对自己的系统掌握不够,数据库配置文件方面没有进行标准化,导致一系列的后续问题,假如没有同事提醒还有工具通过管理端口进行数据库连接,估计我需要花费更久的时间来排查问题。以后自己要梳理数据库标准化的配置,统一到所有数据库。 常规的思维解决常规的问题。
一 前言 因为在准备做压力测试方面的工作,看到sysbench 目前最新的版本是0.5 ,相比之前的0.4的版本,最大的变化是 test 参数的改变,在压测MySQL时,新版本中test将取值为 lua脚本,该种方式给压测工作带来很大的灵活性。二 安装 因为之前的博文中讲述过如何安装sysbench 故本文不做过多讲解,不过需要提示请确保当前系统中是否安装m4 autoconf automake libtool 这几个包, 如果没有则通过 yum install -y m4 autoconf automake libtool安装 获取sysbench然后安装 ./autogen.sh ./configure --prefix=/opt/sysbench --with-mysql --with-mysql-includes=/opt/mysql/include --with-mysql-libs=/usr/lib64/mysql make and make install安装完成之后 执行sysbench --help 查看完整的帮助。 安装完成之后 执行sysbench --help 查看完整的帮助。三使用 之前0.5 版本test值是lua脚本,检查一下db压测可以使用那些脚本 [root@rac4 11:32:45 ~/sysbench/sysbench/tests/db] # ls *.lua common.lua insert.lua oltp_simple.lua select.lua select_random_ranges.lua update_non_index.lua delete.lua oltp.lua parallel_prepare.lua select_random_points.lua update_index.lua 看看大家压测的时候用到的最典型的脚本 [root@rac4 11:36:32 ~/sysbench/sysbench/tests/db] # more update_non_index.lua pathtest = string.match(test, "(.*/)") or "" dofile(pathtest .. "common.lua") function thread_init(thread_id) set_vars() end function event(thread_id) local table_name local c_val local query table_name = "sbtest".. sb_rand_uniform(1, oltp_tables_count) c_val = sb_rand_str("###########-###########-###########-###########-###########-###########-###########-###########-###########-###########") query = "UPDATE " .. table_name .. " SET c='" .. c_val .. "' WHERE id=" .. sb_rand(1, oltp_table_size) rs = db_query(query) end 如果大家会lua脚本,则可以根据自己的业务场景,简单的定制化压测脚本。 cd /root/sysbench/ /opt/sysbecn/bin/sysbench --test=./sysbench/tests/db/update_non_index.lua --mysql-table-engine=innodb --mysql-db=test --oltp-table-size=5000000 --mysql-user=root --mysql-socket=/srv/my3308/run/mysql.sock --oltp-tables-count=16 prepare /opt/sysbecn/bin/sysbench --test=./sysbench/tests/db/update_non_index.lua --mysql-table-engine=innodb --mysql-db=test --oltp-table-size=5000000 --mysql-user=root --mysql-socket=/srv/my3308/run/mysql.sock --oltp-tables-count=16 run /opt/sysbecn/bin/sysbench --test=./sysbench/tests/db/update_non_index.lua --mysql-table-engine=innodb --mysql-db=test --oltp-table-size=5000000 --mysql-user=root --mysql-socket=/srv/my3308/run/mysql.sock --oltp-tables-count=16 cleanup 四 遇到的问题 # ./autogen.sh libtoolize 1.4+ wasn't found, exiting # ./autogen.sh automake 1.10.x (aclocal) wasn't found, exiting 说明需要安装 libtool ,automake 的安装包 安装完上述几个包之后,我还遇到了如下错误: libtool: link: gcc -W -Wall -Wextra -Wpointer-arith -Wbad-function-cast -Wstrict-prototypes -Wnested-externs -Winline -funroll-loops -Wundef -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls -Wcast-align -pthread -O2 -ggdb3 -o sysbench sysbench.o sb_timer.o sb_options.o sb_logger.o db_driver.o tests/fileio/libsbfileio.a tests/threads/libsbthreads.a tests/memory/libsbmemory.a tests/cpu/libsbcpu.a tests/mutex/libsbmutex.a scripting/libsbscript.a drivers/mysql/libsbmysql.a -L/opt/mysql/lib -lmysqlclient_r scripting/lua/src/liblua.a -ldl -lrt -lm -pthread /usr/bin/ld: cannot find -lmysqlclient_r collect2: ld returned 1 exit status 原因是因为自己当前环境中没有安装 mysql-devel 导致没有 libmysqlclient_r.so.16 。解决方法 yum install -y mysql-devel
一 前言 本周接二连三的出现开发人员在测试环境和生产误操作导致数据库误删除/更新,对DBA而言,回滚数据着实是一件头疼的事情,凡涉及到恢复线上数据必然对应用带来一定的影响。大多数情况是开发误操作delete数据,update多数行,根据之前的操作经验,本文介绍常用的恢复方法。 写本文的时候 Monogdb 也被曝出有被利用安全漏洞,数据被删除了,希望各位DBA/安全相关人员及时查看自己负责的业务数据库安全相关问题,保护好自己的数据。二常用的恢复方式 2.1 利用备份恢复 使用这种方式的前提必须有最近的备份集或者知道出现误操作起始的binlog 位点或者GTID,利用备份集恢复到中间的机器上,然后利用MySQL的slave 特性 START SLAVE [SQL_THREAD] UNTIL MASTER_LOG_FILE = 'log_name', MASTER_LOG_POS = log_pos; until_option: UNTIL { {SQL_BEFORE_GTIDS | SQL_AFTER_GTIDS} = gtid_set | MASTER_LOG_FILE = 'log_name', MASTER_LOG_POS = log_pos | RELAY_LOG_FILE = 'log_name', RELAY_LOG_POS = log_pos | SQL_AFTER_MTS_GAPS } 恢复出到一个临时的实例,将误删除,更新的数据 dump 出来并恢复到老的实例里面。恢复数据期间的受影响的表最好不可写,否则将难以达到最想要的结果。例如 a=2 ,被误更新为 a=4,恢复的期间有被更新为a=7 ,结果恢复后又恢复为a=2 。 此种恢复方式 不适合恢复大量数据库,且需要临时实例。 2.2 利用开源工具binlog2sql 恢复。 binlog2sql 是大众点评公司的DBA 开发的一款基于通过解析binlog将delete 恢复为insert,update 的值 set 字段和where条件做对调的原理来恢复数据的。 使用限制 MySQL的binlog format 必须是row 安装 git clone https://github.com/danfengcao/binlog2sql.git && cd binlog2sql pip install -r requirments.txt 用法 usage: binlog2sql.py [-h HOST] [-u USER] [-p PASSWORD] [-P PORT] [--start-file STARTFILE] [--start-position STARTPOS] [--stop-file ENDFILE] [--stop-position ENDPOS] [--start-datetime STARTTIME] [--stop-datetime STOPTIME] [--stop-never] [--help] [-d [DATABASES [DATABASES ...]]] [-t [TABLES [TABLES ...]]] [-K] [-B] 例子 create table flashback( id int(11) not null auto_increment primary key , stat int(11) not null default 1 ) engine=innodb default charset=utf8; insert into flashback(stat) values (2),(3),(4),(7),(9),(22),(42),(33),(66),(88) 误操作 update flashback set stat=15 恢复数据的步骤 1 获取误操作的dml所在的binlog,不过一般开发可不知道具体binlog,他们只知道什么时间误操作了,binlog2sql支持按照时间范围恢复。 mysql> show master logs; +------------------+-----------+ | Log_name | File_size | +------------------+-----------+ | mysql-bin.000009 | 177 | | mysql-bin.000010 | 464 | | mysql-bin.000011 | 8209 | +------------------+-----------+ 3 rows in set (0.00 sec) 本例子中binlog为mysql-bin.000011 2 利用binlog2sql 恢复数据,先解析binlog获取 update 语句的起始位点,本例中 start 5087 end 5428 python binlog2sql.py -h127.0.0.1 -P3307 -udba -p'dbadmin' -dyang -tflashback --start-file='mysql-bin.000011' 使用binlog2sql -B 参数得到恢复的sql 将获取到的sql 执行到数据库,假如生产环境中真的发生了问题,一定要和开发沟通并且确认需要恢复的确切记录。 mysql> select * from flashback; +----+------+ | id | stat | +----+------+ | 1 | 2 | | 2 | 3 | | 3 | 4 | | 4 | 7 | | 5 | 9 | | 6 | 22 | | 7 | 42 | | 8 | 33 | | 9 | 66 | | 10 | 88 | +----+------+ 10 rows in set (0.00 sec) binlog2sql的限制 mysql server必须开启,离线模式下不能解析 优点(对比mysqlbinlog) 纯Python开发,安装与使用都很简单 自带flashback、no-primary-key解析模式,无需再装补丁 flashback模式下,更适合闪回实战 解析为标准SQL,方便理解、调试 代码容易改造,可以支持更多个性化解析 其实MySQL 还提供了一个参数 sql_safe_updates,该参数将禁止 不带where 条件的delete和update语句。具体用法和介绍还请参考MySQL官方介绍 三 总结 本文简单介绍了两种恢复误操作数据的方法,其实还有其他的方式 比如 使用 mysqlbinlog 编写脚本来恢复数据 ,利用闪回的patch 或者去哪儿的inception 等等 ,大家可以继续去研究。保护数据安全乃DBA的基本职责,每年都有各种 因为数据被误删除导致的惨案。希望每个DBA 都能守护好自己的生命线。推荐几篇关于数据恢复相关的文章 [1] mysqlbinlog flashback 5.6完全使用手册与原理 [2] 拿走不谢,Flashback for MySQL 5.7 [3] MySQL闪回方案讨论及实现 [4] danfengcao, binlog2sql: Parse MySQL binlog to SQL you want ---本文介绍的工具 [5] MySQL下实现闪回的设计思路 (MySQL Flashback Feature)
一 前言 之前的文章《InnoDB锁机制之一》介绍了InnoDB锁中的三种锁:record lock, gap lock,next-key lock ,本文继续介绍另外两种锁 Insert Intention Locks和AUTO-INC Locks二 常见的锁类型2.1 根据锁持有的时间粒度,分为 1. 内存级别:类似mutex,很快释放 2. 语句级别:statement结束,释放 3. 事务级别:transaction提交或者回滚才释放 4. 会话级别:session级别,连接断开才释放2.2 AUTO-INC lock AUTO-INC lock是一个特殊的表级锁,当一个事务向含有自增字段的表插入数据时 ,该事务会获取一个AUTO-INC lock,其他事务必须等待直到已经获取锁的insert 语句结束。因此,多个并发事务不能同时获取同一个表上面的AUTO-INC lock,如果持有AUTO-INC锁太长时间可能会影响到数据库性能(比如INSERT INTO t1... SELECT ... FROM t2这类语句)或者死锁. 鉴于AUTO-INC 锁的特性,MySQL 5.1.22 通过新增参数 innodb_autoinc_lock_mode 来控制自增序列的算法。该参数可以设置为0,1,2. 在学习innodb_autoinc_lock_mode之前,我们先了解insert语句的类型 1 Simple inserts 能够事先确定具体行数的insert语句,比如 insert into tab values()...(); replace 等等。 INSERT ... ON DUPLICATE KEY UPDATE和还有子查询的insert 语句除外。 2 Bulk inserts 和Simple inserts对立,事先不能确定插入行数的 insert/replace语句 ,insert ... select ;replace ...select; load data into table .. 这种情况下Innodb在执行具体的行的时候 会为每一行单独分配一个auto_increment 值。 3 Mixed-mode inserts 该情形是 Simple inserts 模式中,有些insert指定了 自增字段的具体值,有些没有指定。比如: INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d'); INSERT ... ON DUPLICATE KEY UPDATE 接下来我们再看MySQL对auto_increment 的优化模式。innodb_autoinc_lock_mode=0,是传统的方式。InnoDB会在分配前给表加上AUTO_INC锁,并在SQL结束时释放掉。该模式保证了在STATEMENT复制模式下,备库执行类似INSERT … SELECT这样的语句时的一致性,因为这样的语句在执行时无法确定到底有多少条记录,只有在执行过程中不允许别的会话分配自增值,才能确保主备一致。 很显然这种锁模式非常影响并发插入的性能,但却保证了一条SQL内自增值分配的连续性。innodb_autoinc_lock_mode=1 ,这个是InnoDB的默认值。该模式下对于Simple inserts,InnoDB会先加一个 autoinc_mutex锁,然后去判断表上是否有别的线程加了LOCK_AUTO_INC锁,如果有的话,释放autoinc_mutex,并使用传统的加锁模式。否则,在预留本次插入需要的自增值之后,就快速的将autoinc_mutex释放掉。很显然,对于普通的并发INSERT操作,都是无需加LOCK_AUTO_INC锁的。因此该模式提高了系统并发性;innodb_autoinc_lock_mode=2,这种模式下只在分配时加个mutex即可,很快就释放,不会像值为1那样在某些场景下会退化到传统模式。因此设为2不能保证批量插入的复制安全性。2.3 Insert Intention Locks 插入意向锁是gap 锁的一种,只是针对insert。当并发事务多条insert 插入同一个GAP,如果他们不是插入同一行记录,会话之间并不会相互等待。例如索引记录删 有 12 ,17 两个记录,两个会话同时插入记录13,15,他们会分别为(12,17)加上GAP锁,但相互之间并不冲突(因为插入的记录不冲突)。 当向某个数据页中插入一条记录时,总是会调用函数lock_rec_insert_check_and_lock进行锁检查(构建索引时的数据插入除外),会去检查当前插入位置的下一条记录上是否存在锁对象,这里的下一条记录不是指的物理连续,而是按照逻辑顺序的下一条记录。 如果下一条记录上不存在锁对象:若记录是二级索引上的,先更新二级索引页上的最大事务ID为当前事务的ID;直接返回成功。 如果下一条记录上存在锁对象,就需要判断该锁对象是否锁住了GAP。如果GAP被锁住了,并判定和插入意向GAP锁冲突,当前操作就需要等待,加的锁类型为LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION,并进入等待状态。但是插入意向锁之间并不互斥。这意味着在同一个GAP里可能有多个申请插入意向锁的会话。三 参考文章 [1] 官方文档 innodb-auto-increment-handling [2] MySQL · 引擎特性 · InnoDB 事务锁系统简介 --强烈推荐[3] MySQL auto_increment实现
一 主要职责 1.负责数据库高可用体系建设,追求100%的服务持续可用、秒级故障恢复能力。 2.负责数据库成本优化,通过新技术、新产品、新方案全方位地优化系统性能。 3.负责数据库相关流程平台和工具产品的建设,持续改进业务研发和系统维护效率。 4.负责数据库架构设计,基于高可用、高性能、防资损等视角,与研发团队一起进行数据架构设计。 5.负责公司重大业务活动(如双11/双12)数据库保障,致力于让用户感觉丝般顺滑。 6.负责数据库新技术的探索及落地,如存储计算分离、数据库容器化等。二、招聘层级:P6/P7/P8/P9三 岗位要求: 1.熟练掌握ACID、范式设计、索引设计等关系数据库基础理论知识,熟悉2pc、paxos、base、cap等分布式数据库基础理论知识。 2.熟练掌握MySQL、Oracle等一种主流数据库体系架构及运行机制,了解主流的分布式数据库(Spanner/Cassandra等),对各数据库优劣和适用场景有深入的理解,能根据具体场景进行数据库选型和模型设计。 3.熟悉Linux/Unix操作系统,熟练掌握Java/Python/Perl/Golang等语言中的一种,可以通过编写程序解决工作中遇到的问题,具备良好的编程风格。 4.熟练掌握数据库性能优化技巧,能够定位全链路上的性能瓶颈(网络、CPU、IO、操作系统等),并解决问题。 5.具有3年以上项目需求分析、方案架构设计工作经验,具有大型行业应用架构经历以及较强的客户需求调研和需求分析能力者优先。 5.对技术特别是存储技术方面有激情有追求,愿意在数据库领域持续投入青春和精力,渴望突破、用于挑战。 6.良好的团队协作能力,有大局观,有较强的抗压能力和积极的心态。 7.良好的沟通表达能力,具备优秀的文档能力,使用文字、图示清楚地表达架构意图,能够熟练编写各类技术文档。四 联系人 微博 数据块 邮箱:xiaojun.lxj@alibaba-inc.com
一 前言 本来今天打算继续研究InnoDB 锁机制并完成第二篇文章,查找资料的时候忽悠想起来自己遗漏了之前的CSDN的公开课《深入理解MySQL中的undo,redo,mvcc》 。于是乎走入另外一条路了,把MySQL IO 层面的知识复习一遍,加深了对MySQL 数据一致性,主从一致性的理解。本文是结合视频和相关资料整理了一张思维导图,总结的时候,部分 OS/MySQL crash 的例子没有完全给出,以后也需要在完善一下,想深入了解这方面知识的同学,可以自己针对各种写入失败的场景,和主从不一致的场景做分析,我相信有经历这样比较全面的分析之后,对大家解决生产环境数据一致性问题很有帮助。二 思维导图(以后会陆续更新) mvcc 是另外一个大坑,待填。三 参考文章 [1] MySQL数据库InnoDB存储引擎Log漫游(1) [2] MySQL数据库InnoDB存储引擎Log漫游(2) [3] MySQL数据库InnoDB存储引擎Log漫游(3) [4] 浅谈mysql的两阶段提交协议 [5] MYSQL-GroupCommit [6] MySQL数据丢失情况分析 参考文档中前面三篇是一位开发而非DBA 写的,李运华(博客) ,在阿里的时候和他聊过,对他的技术和跨界钻研能力表示钦佩。另外看了许多资料的感触就是:源码面前无秘密,共勉。
一 前言 最近一两年,数据库技术尤其是MySQL方面的发展可谓百花齐放,TokuDB,MyRocks ,MySQL 5.7 GA,MySQL 8.0 doc release 其软件也在开发当中,ALiSQL 开源。其中有功能上的改进的,也有针对Innodb 本身缺陷(主要是存储空间方面的)做优化的,作为数据库技术方面的从业者多少有些应接不暇。结合今年ACMUG 技术大会上的技术分享,Percona官方对MyRocks的表态,阿里在技术上的研究,落地来看,可以明显感觉到Myrocks是一种发展趋势。本文将对MyRocks做一个简单的了解。二 MyRocks 是什么 MyRocks是FB基于levelDB(使用LSM 组织数据结构)开发并且开源出来的数据库存储引擎,支持通用的MySQL 读写,锁机制,MVCC,事务(目前仅支持RR,RC),主从复制。目前已经在FB的用户中心使用。三 MyRocks 解决什么问题 这需要先从FB的业务模式来说,fb 作为全球最大的sns网站,其数据量巨大,需要存储数据的服务器想必也是数十万或者百万级别的,这些都是money。(同样也适用于阿里,腾讯这样体量的公司)为了应对未来存储空间的增长和节约服务器,fb开发了此款存储引擎。为啥Innodb 不满足需求呢? 熟悉Innodb存储引擎的朋友知道Innodb是基于B+Tree 实现的数据存储,其默认的数据块大小是16k,目前支持可调节的block_size. 但是依然存在如下问题: 1 写入放大. B*tree要修改数据时,就需要将新入数据下面的数据重新排位,特别是当写入的数据排在较高的位置时,需要大量的移位操作才能完成写入。而且InnoDB 读写访问数据的最小逻辑单位是数据块,随机写的情况下,修改N行,可能要修改N个数据块。 MyRocks的写操作以append only的方式。根据LSM Tree的算法,其将随机写转化为顺序写,写操作只需更新内存,内存中的数据以块数据形式刷到磁盘,是顺序的IO操作,另外磁盘文件定期的合并操作,也将带来磁盘IO操作。具体实现方式如下: a 当有写操作(或update操作)时,写入位于内存的buffer,内存中通过某种数据结构(如skiplist)保持key有序。 b 一般的实现也会将数据追加写到磁盘Log文件,以备必要时恢复。 c 内存中的数据定时或按固定大小地刷到磁盘,更新操作只不断地写到内存,并不更新磁盘上已有文件。 d 随着越来越多写操作,磁盘上积累的文件也越来越多,这些文件不可写且有序。 e 定时对文件进行合并操作(compaction),消除冗余数据,减少文件数量。 2 磁盘空间碎片 B*tree分裂导致page内有较多空闲空间,总体空间利用率不高。尽管Innodb提供压缩的方式,但是压缩以block为单位,也会造成浪费。比如总共16k的数据,压缩到5k 但是存储磁盘依然需要8k 的空间。 3 RocksDB对齐开销小:SST file (默认2MB)需要对齐,但远大于4k, RocksDB_block_size(默认4k) 不需要对齐,因此对齐浪费空间较少。 四 有哪些限制 1 MyRocks目前只支持两种隔离级别,RC和RR。 2 锁机制不健全 不支持gap lock 3 目前阶段Innodb和Myrocks 混用不稳定,Percona 在做合并的工作,ACMUG大会上的Percona工程师表示已经测试了80%的场景。 4 binlog与RocksDB之间没有xa,异常crash可能丢数据。所以,MyRocks一般开启semi-sync. 5 读性能上相对Inndb 比较弱,不支持MRR ,范围查询比较慢。五 总结 目前而言公开在生产环境使用MyRocks存储引擎的只有FB和阿里和阿里云RDS在技术调研,相信其他同行也在做技术调研,期待未来有更多的生产实践。本文仅仅算是对Myrocks一个粗浅的认知,要掌握MyRocks,知其然,知其所以然。推荐大家去看看LSM Tree的算法Log Structured Merge Trees(LSM) 原理 【推荐 其中的文档】LSM Tree存储组织结构介绍 淘宝MySQL 月报 MyRocks简介 ACMUG大会MyRocks 介绍的PPT 【推荐】
一 背景 MySQL锁机制是一个极其复杂的实现,为数据库并发访问和数据一致提供保障。这里仅仅针对MySQL访问数据的三种锁做介绍,加深自己对锁方面的掌握。二 常见的锁机制 我们知道对于InnoDB存储引擎而言,MySQL 的行锁机制是通过在索引上加锁来锁定要目标数据行的。常见的有如下三种锁类型,本文未声明情况下都是在RR 事务隔离级别下的描述。2.1 Record Locks 记录锁实际上是索引上的锁,锁定具体的一行或者多行记录。当表上没有创建索引时,InnoDB会创建一个隐含的聚族索引,并且使用该索引锁定数据。通常我们可以使用 show innodb status 看到行锁相关的信息。2.2 Gap Locks 间隙锁是锁定具体的范围,但是不包含行锁本身。比如 select * from tab where id>10 and id<20; RR事务隔离级别下会锁定10-20之间的记录,不允许类似15这样的值插入到表里,以便消除“幻读”带来的影响。间隙锁的跨度可以是1条记录(Record low就可以认为是一个特殊的间隙锁 ,多行,或者为空。当访问的字段是唯一键/主键时,间隙锁会降级为Record lock。RR事务隔离级别下访问一个空行 ,也会有间隙锁,后续会举例子说明。 我们可以通过将事务隔离级别调整为RC 模式或者设置innodb_locks_unsafe_for_binlog=1 (该参数已经废弃)来禁用Gap锁。2.3 Next-Key Locks 是Record Lock+Gap Locks,锁定一个范围并且包含索引本身。例如索引值包含 2,4,9,14 四个值,其gap锁的区间如下: (-∞,2],(2,4],(4,9],(9,14],(14,+∞) 本文着重从主键,唯一键、非唯一索引,不存在值访问四个方面来阐述RR模式下锁的表现。三 测试案例3.1 主键/唯一键 CREATE TABLE `lck_primarkey` ( `id` int(11) NOT NULL, val int(11) not null default 0, primary key (`id`), key idx_val(val) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into lck_primarkey values(2,3),(4,5),(9,8),(14,13) 会话1 [session1] >select * from lck_primarkey; +----+-----+ | id | val | +----+-----+ | 2 | 3 | | 4 | 5 | | 9 | 8 | | 14 | 13 | +----+-----+ 4 rows in set (0.00 sec) [session1] >begin; Query OK, 0 rows affected (0.00 sec) [session1] >select * from lck_primarkey where id=9 for update; +----+-----+ | id | val | +----+-----+ | 9 | 8 | +----+-----+ 1 row in set (0.00 sec) 会话2 [session2] >begin; Query OK, 0 rows affected (0.00 sec) [session2] >insert into lck_primarkey values(7,6); Query OK, 1 row affected (0.00 sec) [session2] >insert into lck_primarkey values(5,5); Query OK, 1 row affected (0.00 sec) [session2] >insert into lck_primarkey values(13,13); Query OK, 1 row affected (0.00 sec) [session2] >insert into lck_primarkey values(10,9); Query OK, 1 row affected (0.00 sec) 分析 从例子看,当访问表的where字段是主键或者唯一键的时候,session2中的插入操作并未被 session1 中的id=8 影响。官方表述 “Gap locking is not needed for statements that lock rows using a unique index to search for a unique row. (This does not include the case that the search condition includes only some columns of a multiple-column unique index; in that case, gap locking does occur.) For example, if the id column has a unique index, the following statement uses only an index-record lock for the row having id value 100 and it does not matter whether other sessions insert rows in the preceding gap: select * from tab where id=100 for update” 就是说当语句通过主键或者唯一键访问数据的时候,Innodb会使用Record lock锁住记录本身,而不是使用间隙锁锁定范围。 需要注意以下两种情况: 1 通过主键或则唯一索引访问不存在的值,也会产生GAP锁。 [session1] >begin; Query OK, 0 rows affected (0.00 sec) [session1] >select * from lck_primarkey where id=7 for update; Empty set (0.00 sec) [session2] >insert into lck_primarkey values(8,13); ^CCtrl-C -- sending "KILL QUERY 303042481" to server ... Ctrl-C -- query aborted. ERROR 1317 (70100): Query execution was interrupted [session2] >insert into lck_primarkey values(5,13); ^CCtrl-C -- sending "KILL QUERY 303042481" to server ... Ctrl-C -- query aborted. ERROR 1317 (70100): Query execution was interrupted [session2] >insert into lck_primarkey values(3,13); Query OK, 1 row affected (0.00 sec) [session2] >insert into lck_primarkey values(10,13); Query OK, 1 row affected (0.00 sec) 2 通过唯一索引中的一部分字段来访问数据,比如unique key(a,b,c) ,select * from tab where a=x and b=y; 读者朋友可以自己做这个例子。3.2 非唯一键 CREATE TABLE `lck_secondkey` ( `id` int(11) NOT NULL, KEY `idx_id` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into lck_secondkey values(2),(4),(9),(14) 会话1 [session1] >begin ; Query OK, 0 rows affected (0.00 sec) [session1] >select * from lck_secondkey; +----+ | id | +----+ | 2 | | 3 | | 4 | | 9 | | 14 | +----+ 5 rows in set (0.00 sec) [session1] >select * from lck_secondkey where id=9 for update; +----+ | id | +----+ | 9 | +----+ 1 row in set (0.00 sec) 会话2 [session2] >begin; Query OK, 0 rows affected (0.00 sec) [session2] >insert into lck_secondkey values(3); Query OK, 1 row affected (0.00 sec) [session2] >insert into lck_secondkey values(4); ^CCtrl-C -- sending "KILL QUERY 303040567" to server ... Ctrl-C -- query aborted. ERROR 1317 (70100): Query execution was interrupted [session2] >insert into lck_secondkey values(5); ^CCtrl-C -- sending "KILL QUERY 303040567" to server ... Ctrl-C -- query aborted. ERROR 1317 (70100): Query execution was interrupted [session2] >insert into lck_secondkey values(6); ^CCtrl-C -- sending "KILL QUERY 303040567" to server ... Ctrl-C -- query aborted. ERROR 1317 (70100): Query execution was interrupted [session2] >insert into lck_secondkey values(7); ^CCtrl-C -- sending "KILL QUERY 303040567" to server ... Ctrl-C -- query aborted. ERROR 1317 (70100): Query execution was interrupted [session2] >insert into lck_secondkey values(8); ^CCtrl-C -- sending "KILL QUERY 303040567" to server ... Ctrl-C -- query aborted. ERROR 1317 (70100): Query execution was interrupted [session2] >insert into lck_secondkey values(9); ^CCtrl-C -- sending "KILL QUERY 303040567" to server ... Ctrl-C -- query aborted. ERROR 1317 (70100): Query execution was interrupted [session2] >insert into lck_secondkey values(10); ^CCtrl-C -- sending "KILL QUERY 303040567" to server ... Ctrl-C -- query aborted. ERROR 1317 (70100): Query execution was interrupted [session2] >insert into lck_secondkey values(11); ^CCtrl-C -- sending "KILL QUERY 303040567" to server ... Ctrl-C -- query aborted. ERROR 1317 (70100): Query execution was interrupted [session2] >insert into lck_secondkey values(12); ^CCtrl-C -- sending "KILL QUERY 303040567" to server ... Ctrl-C -- query aborted. ERROR 1317 (70100): Query execution was interrupted [session2] >insert into lck_secondkey values(13); ^CCtrl-C -- sending "KILL QUERY 303040567" to server ... Ctrl-C -- query aborted. ERROR 1317 (70100): Query execution was interrupted [session2] >insert into lck_secondkey values(14); Query OK, 1 row affected (0.00 sec) 分析 事务1 对id=9进行for update 访问,session2 插入[4,13]的值都是失败的。根据MySQL的锁原理,Innodb 范围索引或者表是通过Next-key locks 算法,RR事务隔离级别下,通过非唯一索引访问数据行并不是锁定唯一的行,而是一个范围。从例子上可以看出来MySQL对 [4,9] 和(9,14]之间的记录加上了锁,防止其他事务对4-14范围中的值进行修改。可能有读者对其中 id=4 不能修改,但是id=14的值去可以插入有疑问?可以看接下来的例子 [session1] >select * from lck_primarkey; +----+-----+ | id | val | +----+-----+ | 2 | 3 | | 4 | 5 | | 9 | 8 | | 14 | 13 | +----+-----+ 4 rows in set (0.00 sec) [session1] >begin; Query OK, 0 rows affected (0.00 sec) [session1] >select * from lck_primarkey where val=8 for update; +----+-----+ | id | val | +----+-----+ | 9 | 8 | +----+-----+ 1 row in set (0.00 sec) 会话2 [session2] >begin; Query OK, 0 rows affected (0.00 sec) [session2] >insert into lck_primarkey values(3,5); Query OK, 1 row affected (0.00 sec) [session2] >insert into lck_primarkey values(15,13); Query OK, 1 row affected (0.00 sec) [session2] >select * from lck_primarkey; +----+-----+ | id | val | +----+-----+ | 2 | 3 | | 3 | 5 | | 4 | 5 | | 9 | 8 | | 14 | 13 | | 15 | 13 | +----+-----+ 6 rows in set (0.00 sec) [session2] >insert into lck_primarkey values(16,12); ^CCtrl-C -- sending "KILL QUERY 303040567" to server ... Ctrl-C -- query aborted. ERROR 1317 (70100): Query execution was interrupted [session2] >insert into lck_primarkey values(16,6); ^CCtrl-C -- sending "KILL QUERY 303040567" to server ... Ctrl-C -- query aborted. ERROR 1317 (70100): Query execution was interrupted [session2] >insert into lck_primarkey values(16,5); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction [session2] > [session2] >insert into lck_primarkey values(1,5); Query OK, 1 row affected (0.00 sec) 分析 因为session1 对非唯一键val=8 加上了gap锁 [4,5] -[14,13],非此区间的记录都可以插入表中。记录(1,5),(15,13)不在此gap锁区间,记录(16,12),(16,6),(16,5)中的val值在被锁的范围内,故不能插入。四 总结 写本文的目的主要是在于温故而知新,侧重于温故。本文着重介绍了三种锁,其实还有两种锁Insert Intention Locks和AUTO-INC Locks 留作后面继续分析。五 推荐资料 1 官方资料 2 Innodb锁机制:Next-Key Lock 浅谈 3 MySQL 加锁处理分析
一 前言 最近研究备份恢复MySQL数据库实例,老的数据配置和新的实例的my.cnf 配置不统一,依赖backup-my.cnf 来判断innodb_data_file_path 参数是否修改修改。如何解析 my.cnf 呢?于是研究了Python提供ConfigParser模块。该模块可以完成针对常见的配置文件的读取和修改操作,基本满足需求。二 如何使用 2.1 配置文件的格式 配置文件主要由 section区域 构成,section中可以使用option=value或option:value,来配置参数。 [section1 名称] option1=值1 .... optionN=值N [section2 名称] option1=值1 .... optionN=值N 常见的 my.cnf 格式 如下 [mysqld] innodb_log_files_in_group = 2 innodb_page_size = 16384 innodb_log_block_size = 512 innodb_data_file_path = ibdata1:2G:autoextend innodb_log_file_size = 536870912 2.2 ConfigParser 模块 Python的ConfigParser Module定义了3个类:RawCnfigParser,ConfigParser,SafeConfigParser. 其中RawCnfigParser 是最基础的配置文件读取类,ConfigParser、SafeConfigParser基于 RawCnfigParser做了各自的拓展 本文主要以ConfigParser类为例做介绍。ConfigParser模块的操作主要包括: a 初始化一个 ConfigParser实例 b 读取配置 c 修改配置 读取配置文件常用的方法 cf.read(filename) 读取配置文件内容 cf.sections() 获取所有的section,并以列表的形式返回 cf.options(section) 获取指定section下所有option cf.items(section) 获取指定section下所有键值对,以元组的形式返回 cf.get(section,option) 获取指定section中option的值,返回为string类型 cf.getint(section,option) 获取指定section中option的值,返回为int类型 cf.has_option(section,option) 检查section下是否有指定的option,有返回True,无返回 False cf.has_section(section) 检查是否有section,有返回True,无返回 False 修改配置文件常用的方法 cf.add_section(section) 向配置文件中添加一个新的section cf.set(section,option,value) 对section中的option进行设置 cf.remove_section(section) 删除指定的section cf.remove_option(section,option) 删除指定section中的option 注意对于修改配置文件的操作需要调用write将内容写入配置文件。 2.3 例子 点击(此处)折叠或打开 #!/usr/bin/python2.6 #coding:utf8 import ConfigParser old_mycnf_file='backup-my.cnf' new_mycnf_file='my.cnf' cf =ConfigParser.ConfigParser() cf.read(new_mycnf_file) sec=cf.sections() print 'sections:' ,sec opts = cf.options("mysqld") print 'options:', opts kvs = cf.items("mysqld") for kv in kvs: print kv innodb_data_file_path=cf.get('mysqld','innodb_data_file_path') innodb_log_file_size=cf.get('mysqld','innodb_log_file_size') print 'innodb_data_file_path :',innodb_data_file_path print 'innodb_log_file_size :',innodb_log_file_size print "修改之后" cf.set('mysqld','innodb_data_file_path','ibdata1:1G:autoextend') cf.write(open(new_mycnf_file, "w")) cf.read(new_mycnf_file) innodb_data_file_path=cf.get('mysqld','innodb_data_file_path') print 'innodb_data_file_path :',innodb_data_file_path yangyiDBA:test yangyi$ python writecnf.py sections: ['mysqld'] options: ['innodb_log_files_in_group', 'innodb_page_size', 'innodb_log_block_size', 'innodb_data_file_path', 'innodb_log_file_size', 'ibdata1'] ('innodb_log_files_in_group', '2') ('innodb_page_size', '16384') ('innodb_log_block_size', '512') ('innodb_data_file_path', 'ibdata1:2G:autoextend') ('innodb_log_file_size', '536870912') ('ibdata1', '2g:autoextend = ibdata1:2G:autoextend') innodb_data_file_path : ibdata1:1G:autoextend innodb_log_file_size : 536870912 修改之后 innodb_data_file_path : ibdata1:1G:autoextend 三 小结 根据ConfigParser 模块提供的函数,基本可以满足日常工作中对配置文件的修改操作。其他更详细的资料请参考官方文档。
一 简介 Supervisor 是一款基于Python的进程管理工具,可以很方便的管理服务器上部署的应用程序。supervisor是C/S模型的程序,其server端是supervisord 服务,client 端是supervisorctl 命令 。 Supervisor的功能如下: 1 启动、重启、关闭包括但不限于python进程。 2 查看进程的运行状态。 3 批量维护多个进程。 思考一下当应用服务器要部署多个服务程序,机器关闭,重启,如何批量维护?此时supervisor是一个不错的选择。可以用 supervisor 同时启动所有应用程序而不用逐个启动。二 如何安装配置 2.1 安装步骤请移步 官方文档 本文主要介绍如何配置和常用的命令 2.2 supervisor的配置 supervisor启动的时候如果没有加上-c参数,则会使用默认的配置文件启动,supervisor会按照如下顺序去寻找默认配置文件: $CWD/supervisord.conf $CWD/etc/supervisord.conf /etc/supervisord.conf $CWD表示当前的工作目录,上面三个路径从上到下优先级递减,也就是说supervosir会优先去检查$CWD/supervisord.conf文件是否存在,存在就使用该文件启动supervisor,否则向下继续检查。 当然我们也可以使用如下命令生成配置文件: echo_supervisord_conf > /etc/supervisord.conf 配置文件内如参考如下 [rpcinterface:supervisor] supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface [unix_http_server] file=/tmp/supervisor.sock ; supervisord 服务进程的sock文件 [supervisord] logfile=/data/logs/supervisord/supervisord.log ; 日志文件,默认是 $CWD/supervisord.log logfile_maxbytes=50MB ; 日志文件大小,超出50MB会做轮转,默认为50MB logfile_backups=10 ; 日志文件保留备份数量 loglevel=info ; 日志级别,默认 info,其它: debug,warn,trace pidfile=/var/run/supervisord.pid ; pid 文件 nodaemon=false ; 是否在前台启动,默认是 false,即以 daemon 的方式启动 minfds=10240 ; 可以打开的文件描述符的最小值,默认 1024 minprocs=200 ; 可以打开的进程数的最小值,默认 200 #### ### [supervisorctl] serverurl = unix:///tmp/supervisor.sock [include] files = /etc/supervisord.d/*.conf ;包含需要管理的应用程序的配置文件 我们把文件内容分成两块1 supervisord自身的配置项内容2 需要管理的应用程程序的配置,在[include]里面 2.3 应用程序的cnf文件配置信息 应用程序的配置文件格式 应用程序的配置文件格式需要[program:PROGRAM_NAME] 部分的配置,PROGRAM_NAME表示 supervisord 要管理那个进程描述,会在客户端supervisorctl 或 web 界面显示,可以通过 supervisorctl start|restart|stop PROGRAM_NAME 来操作维护该进程。 [program:PROGRAM_NAME] 属性1=参数1 .... 属性N=参数N 举个例子: 通过supervisor 管理haunt程序 [program:haunt] directory = /opt/haunt_agent ; 程序的启动目录 command= /opt/haunt_agent/bin/haunt_agent -c /opt/haunt_agent/conf/haunt_agent.ini; 启动haunt程序的命令,与手动启动的命令一致 autostart = true ;在 supervisord 启动的时候也自动启动 startsecs = 5 ;启动 5 秒后没有异常退出,就当作已经正常启动了 autorestart = true ;程序异常退出后自动重启 startretries = 3 ; 启动失败自动重试次数,默认是 3 user = app ; 用哪个用户启动 redirect_stderr = true ; 把 stderr 重定向到 stdout,默认 false stdout_logfile_maxbytes = 10MB ; stdout 日志文件大小,默认 20MB stdout_logfile_backups = 10 ; stdout 日志文件备份数 stdout_logfile = /data/logs/supervisor/haunt_stdout.log; stdout日志文件,注意当指定目录不存在时无法正常启动,所以需要手动创建目录(supervisord 会自动创建日志文件) ; 可以通过 environment 来添加需要的环境变量,一种常见的用法是修改 PYTHONPATH ; environment=PYTHONPATH=$PYTHONPATH:/path/to/somewhere 三 常用的命令 3.1 启动supervisor #明确指定配置文件 supervisord -c /etc/supervisord.conf 如果以不指定配置文件启动,则会找默认文件 supervisord 3.2 supervisorctl 命令介绍 supervisorctl 是supervisord的命令行客户端工具,启动时需要指定与supervisord使用同一份配置文件,否则与supervisord一样按照顺序查找配置文件。 supervisorctl -c /etc/supervisord.conf 进入命令行模式 # supervisorctl SayHello EXITED Nov 02 11:27 PM sample RUNNING pid 10082, uptime 2:56:32 hawk_agent:hawk_agent-1 RUNNING pid 10084, uptime 2:56:32 supervisor> status SayHello EXITED Nov 02 11:27 PM sample RUNNING pid 10082, uptime 2:56:35 hawk_agent:hawk_agent-1 RUNNING pid 10084, uptime 2:56:35 supervisor> reload Really restart the remote supervisord process y/N? y Restarted supervisord supervisor> supervisor> status SayHello RUNNING pid 4359, uptime 0:00:02 sample RUNNING pid 4358, uptime 0:00:02 yz-hawk_agent:yz-hawk_agent-1 RUNNING pid 4360, uptime 0:00:02 常用的命令介绍: # 停止某一个进程,program_name 为 配置文件中[program:x] 里的 x supervisorctl stop program_name # 启动某个进程 supervisorctl start program_name # 重启某个进程 supervisorctl restart program_name # 结束所有属于名为 groupworker 这个分组的进程 (start,restart 同理) supervisorctl stop groupworker: # 结束 groupworker:name1 这个进程 (start,restart 同理) supervisorctl stop groupworker:name1 # 停止全部进程,注:start、restart、stop 都不会载入最新的配置文件 supervisorctl stop all # 载入最新的配置文件,停止原有进程并按新的配置启动、管理所有进程,相当于重启所有的服务,该命令慎用 supervisorctl reload # 根据最新的配置文件,启动新配置或有改动的进程,配置没有改动的进程不会受影响而重启 supervisorctl update 3.3 支持以 group 的方式来管理多个进程 supervisor 可以将多个应用程序以group的方式管理。 [group:GROUP_NAME] programs=prog_name1,prog_name2 ; each refers to 'x' in [program:x] definitions priority=999 ; the relative start priority (default 999) 使用group配置之后,使用supervisorctl 管理进程的时候,变为管理group组内所有的程序 supervisorctl [start|restart|stop|reload] GROUP_NAME 管理单个应用程序 supervisorctl [start|restart|stop|reload] GROUP_NAME:prog_name1 supervisorctl [start|restart|stop|reload] GROUP_NAME:prog_name2 如果你的应用程序比较多而且部分应用程序有关联性,可以使用group的方式,但是如果每个应用程序相互独立且不耦合,推荐使用 “分而治之”的思路,每个应用程序单独一个。这样运维应用程序的时候 更方便简单。四 参考文章 [1] Python 进程管理工具 Supervisor 使用教程 [2] 官方文档
一 前言 Redis Queue 一款轻量级的P分布式异步任务队列,基于Redis作为broker,将任务存到redis里面,然后在后台执行指定的Job。就目前而言有三套成熟的工具celery,huey ,rq 。按照功能和使用复杂度来排序的话也是 celery>huey>rq. 因为rq 简单,容易上手,所以自己做的系统也会使用RQ作为分布式任务调度系统。二 安装 因为RQ 依赖于Redis 故需要安装版本>= 2.6.0.具体安装方法请参考《Redis初探》。*nix 系统环境下安装RQ: pip install rq 无需其他配置即可以使用RQ。三 原理 RQ 主要由三部分构成 Job ,Queues,Worker 构成。job也就是开发定义的函数用来实现具体的功能。调用RQ 把job 放入队列Queues,Worker 负责从redis里面获取任务并执行,根据具体情况返回函数的结果。3.1 关于job 一个任务(job)就是一个Python对象,具体表现为在一个工作(后台)进程中异步调用一个函数。任何Python函数都可以异步调用,简单的将函数与参数追加到队列中,这叫做入队(enqueueing)。3.2 关于Queue 将任务加入到队列之前需要初始化一个连接到指定Redis的Queue q=Queue(connection=redis_conn) from rq_test import hello result = q.enqueue(hello,'yangyi') queue有如下属性: timeout :指定任务最长执行时间,超过该值则被认为job丢失,对于备份任务 需要设置一个比较长的时间 比如24h。 result_ttl :存储任务返回值的有效时间,超过该值则失效。 ttl :specifies the maximum queued time of the job before it'll be cancelled depends_on :specifies another job (or job id) that must complete before this job will be queued job_id : allows you to manually specify this job's job_id at_front :will place the job at the front of the queue, instead of the back kwargs and args : lets you bypass the auto-pop of these arguments, ie: specify a timeout argument for the underlying job function. 需要关注的是 depends_on ,通过该属性可以做级联任务A-->B ,只有当A 执行成功之后才能执行B . 通过指定队列的名字,我们可以把任务加到一个指定的队列中: q = Queue("low", connection = redis_conn) q.enqueue(hello, "杨一") 对于例子中的Queue("low"),具体使用的时候可以替换"low"为任意的复合业务逻辑名字,这样就可以根据业务的需要灵活地归类的任务了。一般会根据优先级给队列命名(如:high, medium, low). 如果想要给enqueue传递参数的情况,可以使用enqueue_call方法。在要传递超时参数的情况下: q = Queue("low", connection = redis_conn) q.enqueue_call(func=hello, args= ("杨一",),timeout = 30) 3.3 关于worker Workers将会从给定的队列中不停的循环读取任务,当所有任务都处理完毕就等待新的work到来。每一个worker在同一时间只处理一个任务。在worker中,是没有并发的。如果你需要并发处理任务,那就需要启动多个worker。 目前的worker实际上是fork一个子进程来执行具体的任务,也就是说rq不适合windows系统。而且RQ的work是单进程的,如果想要并发执行队列中的任务提高执行效率需要使用threading针对每个任务进行fork线程。 worker的生命周期有以下几个阶段组成: 1 启动,载入Python环境 2 注册,worker注册到系统上,让系统知晓它的存在。 3 开始监听。从给定的redis队列中取出一个任务。如果所有的队列都是空的且是以突发模式运行的,立即退出。否则,等待新的任务入队。 4 分配一个子进程。分配的这个子进程在故障安全的上下文中运行实际的任务(调用队列中的任务函数) 5 处理任务。处理实际的任务。 6 循环。重复执行步骤3。四 如何使用 简单的开发一个deamon 函数,用于后端异步调用,注意任意函数都可以加入队列,必须能够在入队的时候 被程序访问到。 #!/usr/bin/env python #-*- coding:utf-8 -*- def hello(name): print "hello ,%s"%name ip='192.168.0.1' num=1024 return name,ip,num def workat(name): print "hello %s ,you r workat youzan.com "%(name) 4.1 构建队列,将任务对象添加到队列里面 >>> from redis import Redis,ConnectionPool >>> from rq import Queue >>> pool = ConnectionPool(db=0, host='127.0.0.1', port=6379, ... password='yangyi') >>> redis_conn = Redis(connection_pool=pool) >>> q=Queue(connection=redis_conn) >>> from rq_test import hello >>> >>> result = q.enqueue(hello,'yangyi') >>> result = q.enqueue(hello,'youzan.com') 先实例化一个Queue类q,然后通过enqueue方法发布任务。第一个参数是执行的函数名,后面是函数执行所需的参数,可以是args也可以是kwargs,案例中是一个字符串。 然后会返回一个Job类的实例,后面会具体介绍Job类的实例具体的api。4.2启动worker ,从日志上可以看到执行了utils.hello('yangyi') utils.hello('youzan.com') 。当然这个只是简单的调用介绍,生产环境还要写的更加健壮,针对函数执行的结果进行相应的业务逻辑处理。 root@rac2:~# >python woker.py 23:44:48 RQ worker u'rq:worker:rac2.3354' started, version 0.6.0 23:44:48 Cleaning registries for queue: default 23:44:48 23:44:48 *** Listening on default... 23:44:48 default: utils.hello('yangyi') (63879f7c-b453-4405-a262-b9a6b6568b68) hello ,yangyi 23:44:48 default: Job OK (63879f7c-b453-4405-a262-b9a6b6568b68) 23:44:48 Result is kept for 500 seconds 23:44:48 23:44:48 *** Listening on default... 23:45:12 default: utils.hello('youzan.com') (e4e9ed62-c476-45f2-b66a-4b641979e731) hello ,youzan.com 23:45:12 default: Job OK (e4e9ed62-c476-45f2-b66a-4b641979e731) 23:45:12 Result is kept for 500 seconds 需要说明的是其实 worker的启动顺序应该在job放入队列之前,一直监听rq里面是否有具体的任务,当然如果worker晚于job 加入队列启动,job的状态会显示为 queued 状态。4.3 查看作业执行的情况 当任务加入队列,queue.enqueue()方法返回一个job实例。其定义位于rq.job文件中,可以去查看一下它的API,主要用到的API有: >>> from rq import job >>> job = q.enqueue(hello,'youzan.com') >>> job.get_id() ##获取任务的id ,如果没有指定 ,系统会自动分配一个随机的字符串。 u'17ad0b3a-195e-49d5-8d31-02837ccf5fa6' >>> job = q.enqueue(hello,'youzan.com') >>> print job.get_status() ##获取任务的处理状态 finished >>> step1=q.enqueue(workat,) ##故意不传递参数,让函数执行失败,则获取的状态值是 failed >>> print step1.get_status() failed >>> print job.result # 当任务没有执行的时候返回None,否则返回非空值,如果 函数 hello() 有return 的值,会赋值给result None 当我们把worker 监听进程停止,然后重新发布任务,查看此时任务的在队列的状态,会显示为 queued >>> job = q.enqueue(hello,'youzan') >>> print job.get_status() queued >>> print job.to_dict() #把job实例转化成一个字典,我们主要关注状态。 {u'origin': u'default', u'status': u'queued', u'description': u"rq_test.hello('youzan')", u'created_at': '2016-09-06T08:00:40Z', u'enqueued_at': '2016-09-06T08:00:40Z', u'timeout': 180, u'data': '\x80\x02(X\r\x00\x00\x00rq_test.helloq\x01NU\x06youzanq\x02\x85q\x03}q\x04tq\x05.'} >>> job.cancel() # 取消作业,尽管作业已经被执行,也可以取消 >>> print job.to_dict() {u'origin': u'default', u'status': u'queued', u'description': u"rq_test.hello('youzan')", u'created_at': '2016-09-06T08:00:40Z', u'enqueued_at': '2016-09-06T08:00:40Z', u'timeout': 180, u'data': '\x80\x02(X\r\x00\x00\x00rq_test.helloq\x01NU\x06youzanq\x02\x85q\x03}q\x04tq\x05.'} >>> print job.get_status() queued >>> >>> job.delete() # 从redis队列中删除该作业 >>> print job.get_status() None >>> print job.to_dict() {u'origin': u'default', u'description': u"rq_test.hello('youzan')", u'created_at': '2016-09-06T08:00:40Z', u'enqueued_at': '2016-09-06T08:00:40Z', u'timeout': 180, u'data': '\x80\x02(X\r\x00\x00\x00rq_test.helloq\x01NU\x06youzanq\x02\x85q\x03}q\x04tq\x05.'} 五 参考文章[1] 官方文档 [2] 翻译 - Python RQ Job [3] 翻译 - Python RQ Workers [4] 云峰就她了 这位博主写了很多rq相关的实践经验,值得参考。
写在前面 本系列文章基于 5.7.12 版本讲述MySQL的新特性。从安装,文件结构,SQL ,优化 ,运维层面 复制,GITD等几个方面展开介绍 5.7 的新特性和功能。同时也建议大家跟踪官方blog和官方文档,以尽快知悉其新的变化。6.1 优化(工具方面)增强 5.7 版本中如果一个会话正在执行sql,且该sql 是支持explain的,那么我们可以通过指定会话id,查看该sql的执行计划。 EXPLAIN [options] FOR CONNECTION connection_id 该功能可以在一个会话里面查看另外一个会话中正在执行的长查询。 mysql> show processlist; +----+-------------+-----------------+------+---------+------+--------------------------------------------------------+------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+-------------+-----------------+------+---------+------+--------------------------------------------------------+------------------+ | 1 | system user | | NULL | Connect | 78 | Connecting to master | NULL | | 2 | system user | | NULL | Connect | 78 | Slave has read all relay log; waiting for more updates | NULL | | 3 | system user | | NULL | Connect | 78 | Waiting for an event from Coordinator | NULL | | 4 | system user | | NULL | Connect | 78 | Waiting for an event from Coordinator | NULL | | 5 | system user | | NULL | Connect | 78 | Waiting for an event from Coordinator | NULL | | 6 | system user | | NULL | Connect | 78 | Waiting for an event from Coordinator | NULL | | 8 | root | localhost:47896 | NULL | Query | 0 | starting | show processlist | | 9 | root | localhost:47897 | NULL | Query | 3 | User sleep | select sleep(10) | +----+-------------+-----------------+------+---------+------+--------------------------------------------------------+------------------+ 8 rows in set (0.00 sec) mysql> explain FOR CONNECTION 9; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+ | 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | No tables used | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+ 1 row in set (0.00 sec) 可以参考官方文档 和 work log 6.2 hint 功能增强 相比于MySQL5.6版本的hint 主要是index 级别的hint和控制表join 顺序的hint,5.7.7之后,MySQL增加了优化器hint,来控制sql执行的方式,因为目前MySQL支持nest loop join,故暂时无hint来修改sql 的join方式。熟悉Oracle 的朋友是否会发现MySQL 和Oracle 在功能上越来越近了。话说回来5.7的hint (先别和 index hint 比较)的用法 ,和oracle 的类似: SELECT /*+ NO_RANGE_OPTIMIZATION(t3 PRIMARY, f2_idx) */ f1 FROM t3 WHERE f1 > 30 AND f1 < 33; SELECT /*+ BKA(t1) NO_BKA(t2) */ * FROM t1 INNER JOIN t2 WHERE ...; SELECT /*+ NO_ICP(t1, t2) */ * FROM t1 INNER JOIN t2 WHERE ...; SELECT /*+ SEMIJOIN(FIRSTMATCH, LOOSESCAN) */ * FROM t1 ...; EXPLAIN SELECT /*+ NO_ICP(t1) */ * FROM t1 WHERE ... 优化器级别的hint分四种类型 Global: The hint affects the entire statement Query block: The hint affects a particular query block within a statement ,什么是query block (SELECT ... ) UNION (SELECT /*+ ... */ ... ) --后面的括号里面的称为 query block 。 Table-level: The hint affects a particular table within a query block Index-level: The hint affects a particular index within a table 其他更加详细的信息请参考 官方文档 6.3 触发器功能增强 5.7版本之前一个表 对于每种action(INSERT,UPDATE, DELETE)和时机(BEFORE or AFTER) 只能支持一种类型的触发器。新版本可以针对同一个action支持多个触发器。6.4 syslog 功能 之前的版本,*nix系统上的MySQL支持将错误日志发送到syslog是通过mysqld_safe捕获错误输出然后传递到syslog来实现的。新的版本原生支持将错误日志输出到syslog,且适用于windows系统,只需要通过简单的参数(log_syslog等)配置即可。参考 官方文档 MySQL支持–syslog选项,可将在交互式模式下执行过的命令输出到syslog中(*nix系统下一般是.mysql_history)。对于匹配“ignore”过滤规则(可通过 –histignore选项或者 MYSQL_HISTIGNORE环境变量进行设置)的语句不会被记入。关于mysql客户端的日志使用参见:官方文档6.5 虚拟列 在MySQL 5.7中,支持两种Generated Column, 1 Virtual Generated Column :只将Generated Column保存在数据字典中表的元数据,每次读取该列时进行计算,并不会将这一列数据持久化到磁盘上; 注意:MySQL 5.7.8 以前 虚拟列字段不支持创建索引。5.7.8之后Innodb支持在虚拟列创建辅助索引。 2 Stored Generated Column : 将Column持久化到存储,会占用一定的存储空间。与Virtual Column相比并没有明显的优势,因此,MySQL 5.7中,不指定Generated Column的类型,默认是Virtual Column。 创建虚拟列语法: col_name data_type [GENERATED ALWAYS] AS (expression) [VIRTUAL | STORED] [UNIQUE [KEY]] [COMMENT comment] [[NOT] NULL] [[PRIMARY] KEY] 具体的例子 CREATE TABLE triangle ( id int(10) not null primary key auto_increment, sidea DOUBLE, sideb DOUBLE, sidec DOUBLE AS (SQRT(sidea * sidea + sideb * sideb)) ); INSERT INTO triangle (sidea, sideb) VALUES(1,1),(3,4),(6,8),(12,16); mysql> select * from triangle; +----+-------+-------+--------------------+ | id | sidea | sideb | sidec | +----+-------+-------+--------------------+ | 1 | 1 | 1 | 1.4142135623730951 | | 2 | 3 | 4 | 5 | | 3 | 6 | 8 | 10 | | 4 | 12 | 16 | 20 | +----+-------+-------+--------------------+ 4 rows in set (0.00 sec) mysql> explain select * from triangle where sidec > 10; +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | triangle | NULL | ALL | NULL | NULL | NULL | NULL | 4 | 33.33 | Using where | +----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec) mysql> alter table triangle add key idx_sidec(sidec); Query OK, 0 rows affected (0.03 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> explain select * from triangle where sidec > 10; +----+-------------+----------+------------+-------+---------------+-----------+---------+------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+----------+------------+-------+---------------+-----------+---------+------+------+----------+-------------+ | 1 | SIMPLE | triangle | NULL | range | idx_sidec | idx_sidec | 9 | NULL | 1 | 100.00 | Using where | +----+-------------+----------+------------+-------+---------------+-----------+---------+------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec) 看到这个例子,熟悉oracle的朋友可能会和函数索引作比较,两者比较类似.使用虚拟列达到函数索引或者解决业务上的设计缺陷,但是个人不建议使用类似的功能,因为虚拟列在一定程度上也会给后期运维带来潜在的风险和复杂度。网络上的例子基本都是使用虚拟列解决业务逻辑上的问题,违背了数据库只存储数据的初衷,思考一下MVC 框架的基本逻辑,业务逻辑要放到C 层或者V层,M层只存放数据即可。 各位看官自己要了解更详细的虚拟列资料请移步官方文档 小结 本文算是对5.7版本功能类新特性做收尾,后面会介绍主从复制,GTID,InnoDB 性能优化方面以及数据库升级相关的知识,这些特性更具有操作性,相对前面几篇文章会更难写。另外学习5.7的过程中,我查看了很多MySQL的worklog,发现这里简直是一个MySQL资料宝库,很多功能的需求和原理讨论都可以在这里找到。想深入学习MySQL的朋友可以多钻研 http://dev.mysql.com/worklog/ 相关文章[1] 《MySQL 5.7 官方文档》[2] 《MySQL 5.7 初探》[3] 《MySQL 5.7新特性之一》[4] 《MySQL 5.7新特性之二》[5] 《MySQL 5.7新特性之三》[6] 《MySQL 5.7新特性之四》[7] 《MySQL 5.7新特性之五》
一 前言 纳西姆.尼古拉斯.塔勒布的经典著作《黑天鹅》中对“黑天鹅现象”的定义是 - 不可预测,人们事前往往低估其发生的可能性 - 造成极大影响 - 事后回头再看,又觉得此事发生的有理 二 分析 稳定性是一项衡量基础系统是否永续服务的绝对指标,作为资深DBA从业人员,相信大多数公司运维团队都会制定稳定性的SLA指标达到N个9,为用户提供Full-Time 服务。然而前一段时间各种"黑天鹅”式的因素导致一系列的系统故障,严重影响了C端B端的用户的使用体验。故障是数据库系统或者说业务系统的“脆弱性”表现。什么是导致业务故障的“黑天鹅”呢?例举最近遇到的和数据库相关的场景: a 程序异常,比如异常传参导致本应该获取1行数据的结果去调用14w行,高压力下慢查询将数据库会话占满,引发”雪崩效应“。 b 正常分页调用,但是遇到大分页查询高频访问db,同样会导致慢查询引发“雪崩效应”。 c 第三方业务开发不了解api的使用方法 ,选择全量拉取而非增量拉取业务数据,导致大量慢查询。 上述三个例子的共性基本都含有慢查询,高频访问。找到导致问题发生的数据库层面的原因,剩下的就是发挥产品/开发DBA的特长了,获取到慢查询,然后各个击破之。本文举例几个具有代表性的sql案例一 大分页查询优化 商家会使用第三方软件拉取订单数据进行对账,使用limit N,M 分页查询每次拉取50 或者100页,小批量数据时比如N小于 10000时性能表现正常,但是遇到大的商家比如罗辑思维 ,糕妈优选等大商家,拉取数据的时间会随着N 的增加而增大。 select * from so where 1 and `bb` = 'xxxxx' and `cc` in ('5') and `dd` in ('0','1','2','3') order by id desc limit 70000,100; 优化方法 1 利用索引的有序性,更确切的是利用 where条件的索引有序性,尽可能使用到组合索引的created_time有序性代替使用order by id查询,MySQL在使用索引的时候 只能利用一个有效索引,order by id 可能会导致优化器选择主键而非 cc,dd,created_time这样的组合索引。 2 通常我们推荐使用 延迟关联 的方法来优化大分页查询---利用覆盖索引获取复合条件的记录的主键id,然后驱动表根据主键来访问想要的数据,这样的访问速度要比limit 顺序扫描全索引然后回表的速度要快很多。 select a.* from so a,(select id from so where 1 and `bb` = 'xxxxx' and `cc` in ('5') and `dd` in ('0','1','2','3') order by created_time desc limit 70000,101 ) b where a.id=b.id; 3 应用层优化商家本质上是想要获取全量数据,之前的方式是每天或者每周固定时间点定期获取某个时间段内的全量数据,换个思路我们的业务提供push推送任务,专门主动推送商家的增量数据,这样可以避免大批量的拉取全量数据,减少db的不稳定性也同时节约公司的带宽成本。案例二 join 查询优化 大致的业务逻辑根据商品交易信息获取商家售卖销量,相关sql 以及表结构信息 select count(o.ono) as num from so o, oi i where o.ono = i.ono and `o`.`kid` = 'xxxx' and `i`.`gid` = 'yyyy'; oi 表的索引 KEY `idx_sid` (`idx_sid`) USING BTREE, KEY `idx_ono` (`idx_ono`) USING BTREE, KEY `idx_created` (`created`) so 表的索引 key idx_kid(kid,cc,created_time) 在MySQL中,目前而言只有一种join算法 也即是nested loop join:是通过驱动表(from后的第一个表)的结果集作为循环的基础数据,然后将结果集中的数据作为过滤条件一条条地到下一个表中查询数据,最后合并结果。本案例中可以理解为 以so kid=16553711 的结果数据 去匹配 oi 表中gid=yyyy 符合记录的数据,然后做count操作。通常我们对于join查询的优化原则 1 减少nested loop的循环次数,使用小结果集驱动大结果集。 2 优先优化Nested Loop的内层循环,内循环中的where条件一定要使用最优的索引。 3 保证join语句中被驱动表的join条件字段已经被索引; 4 如果无法保证被 驱动表的Join条件字段被索引且内存充足的情况下,可以通过调join_buffer_size来设置join buffer的大小 。 优化方法 1 根据优化原则我们将 oi表的idx_ono 索引调整为 idx_gid_ono(gid,ono),使用覆盖索引解决内循环回表的IO消耗。可能会有人会咨询为什么不调整表的顺序,其实第一个想到优化的就是调整顺序,但是在现有索引条件下调整驱动表的顺序并没有提高查询效率。 2 其实作为一个服务电商业务线的老司机,我认为涉及C端应用调用应该避免或者说禁止使用join查询,业务增长带来访问量透传给DB的压力,很可能将上面的优化结果轻松覆盖。最优化的方式尽可能的使用kv查询,单表查询。好在我们公司给力的开发同学王野已经将该优化业务迁移到es中,直接通过es获取结果。案例三 并发count(*) 优化 因为业务逻辑处理不力,导致数据库并发count 进程数飙高到200左右,严重影响到其他业务的正常请求。其实对于count操作的优化相对比较有限 1 确保where条件一定利用到最优索引。 2 业务层面避免并发count操作,可以使用缓存来规避直接访问db。 关于count的优化文章可以参考 拙作 《性能优化之 count(*) VS count(col) 》 三 小结 最近一个多月一直紧跟公司的慢查询这块做集中优化,到目前为止效果相当不错,基本将慢查询减少了90%左右。从slow log文件大小来看,此次优化将文件大小从1M 减少到4k 左右,解决了绝大多数的潜在的系统风险。 诚然通过优化慢查询,使用缓存 ,并无法绝对避免“黑天鹅”式故障发生,系统的稳定性是应用层的健壮性,底层基础服务 网络,机器硬件,数据库层面等各个环节息息相关的,我们要做的就是通过提高数据库系统和业务系统的 “反脆弱性”,提高抗击打能力,为用户提供可持续的稳定的服务。四 推荐文章 [1] 《黑天鹅:如何应对不可知的未来》 [2] 《反脆弱:从不确定性中获益》 [3] 《关于高可用的系统》
写在前面 曾经给其他朋友 公司写了很多招聘广告,现在终于要为自己写一篇来招聘DBA了。本人花名 杨一 ,真名就是博客上面的名字啦 ,从毕业一直做DBA,在阿里工作四年半,经历阿里云,RDS DBA,外部去IOE项目组,阿里集团电商业务DBA,现在就职于杭州有赞科技有限公司. 有赞 ,是一家零售服务技术提供商,为商家提供全套移动电商产品服务方案。最近两年公司发展迅猛,希望找一个可以背靠背工作的DBA伙伴.岗位要求 1 具有扎实的数据库基础知识,自己懂,也能教,因为要服务于开发同学,帮他们解决各种疑难杂症,定期为开发提供MySQL/Redis 数据库方面的培训。 2 熟悉MySQL,NoSQL数据库方面的运维技能:整体数据库规划,主从复制,备份,故障诊断,系统调优,监控报警。 3 最好具有一定的开发能力,可以是python方面的,目前我们已经在做自动化运维系统。4 有 要性 ,渴望突破,自我驱动力强,喜欢有挑战的工作。5 具有良好的文档编写能力,清晰的表达能力,能使用专业,准确的语言描述技术原理,架构。 6 具有电商方面的数据库运维经验者优先。 岗位职责 1 承担各个业务/项目的数据库服务职责,为开发提供技术方案,SQL 优化,日常数据库相关的技术咨询。 2 完善当前的数据库运维体系,为公司的数据库服务稳定性负责,为业务提供高可用,高性能的数据库系统服务。 联系方式微博 杨奇龙 邮箱 qilong.yangql@gmail.com
一 前言 TokuDB 是一个高性能、支持MVCC的MySQL 和 MariaDB 的存储引擎。TokuDB 的主要特点是数据压缩功能出色,对高写压力的支持,由美国TokuTek公司(http://www.tokutek.com/) 研发,该公司于2015年4月份被Percona收购,理所当然地提供了TokuDB版本的Percona Server。本文使用Peronca server 5.6.30 版本进行测试安装。二 安装前的准备 请参考官方文档 Percona Server YUM源 ,Percona Server tokudb安装文档三 安装步骤3.1 关闭系统的大页 echo never > /sys/kernel/mm/transparent_hugepage/enabled echo never > /sys/kernel/mm/transparent_hugepage/defrag echo never > /sys/kernel/mm/redhat_transparent_hugepage/enabled echo never > /sys/kernel/mm/redhat_transparent_hugepage/defrag 3.2 禁用SELINUX root@rac2:~# >vim /etc/selinux/config 设置SELINUX=disabled root@rac2:~# >setenforce 0 root@rac2:~# >getenforce 3.3 利用percona 的yum 源进行安装 yum install http://www.percona.com/downloads/percona-release/redhat/0.1-3/percona-release-0.1-3.noarch.rpm yum list | grep percona #检查yum源里面是否有 tokudb相关的rpm包 yum install Percona-Server-tokudb-56.x86_64 -y 3.4 初始化数据库实例并启动数据库 /usr/bin/mysql_install_db --user=mysql --datadir=/srv/my3306/data --basedir=/usr/ --defaults-file=/srv/my3306/my.cnf & /usr/bin/mysqld_safe --defaults-file=/srv/my3306/my.cnf --user=mysql & 3.5 使用ps_tokudb_admin安装tokudb 存储引擎 ,记得实例必须是启动状态的。 root@rac2:/srv/my3306/data# >ps_tokudb_admin --enable -uroot -h127.0.0.1 -P 3306 Checking SELinux status... INFO: SELinux is in permissive mode. Checking if Percona Server is running with jemalloc enabled... INFO: Percona Server is running with jemalloc enabled. Checking transparent huge pages status on the system... INFO: Transparent huge pages are currently disabled on the system. Checking if thp-setting=never option is already set in config file... INFO: Option thp-setting=never is not set in the config file. (needed only if THP is not disabled permanently on the system) Checking TokuDB engine plugin status... INFO: TokuDB engine plugin is not installed. Adding thp-setting=never option into /etc/my.cnf INFO: Successfully added thp-setting=never option into /etc/my.cnf Installing TokuDB engine... INFO: Successfully installed TokuDB engine plugin. --> 说明Tokudb 存储引擎插件安装成功 3.6 登陆实例进行检查 root@rac2:/srv/my3306/data# >my 3306 Server version: 5.6.31-77.0-log Percona Server (GPL), Release 77.0, Revision 5c1061c mysql> show engines; +---------+---------+--------------------------------------------------------------+--------------+------+------------+ | Engine | Support | Comment | Transactions | XA | Savepoints +---------+---------+--------------------------------------------------------------+--------------+------+------------+ | TokuDB | YES | Percona TokuDB Storage Engine with Fractal Tree(tm) Technology| YES | YES | YES | +---------+---------+---------------------------------------------------------------+-------------+------+------------+ 10 rows in set (0.02 sec) mysql> SELECT @@tokudb_version; +------------------+ | @@tokudb_version | +------------------+ | 5.6.31-77.0 | +------------------+ 1 row in set (0.00 sec) 四 测试4.1 压缩比例 创建innodb 和tokudb存储引擎的表,测试插入 和文件大小。 mysql> create table t_tokudb (id int,val varchar(256)) engine=tokudb default charset utf8; Query OK, 0 rows affected (0.05 sec) mysql> insert into t_tokudb(val) select val from t_tokudb; Query OK, 262144 rows affected (2.32 sec) Records: 262144 Duplicates: 0 Warnings: 0 mysql> CREATE TABLE `t_innodb` ( -> `id` int(11) DEFAULT NULL, -> `val` varchar(256) DEFAULT NULL -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8; Query OK, 0 rows affected (0.13 sec) mysql> insert into t_innodb select * from t_tokudb; Query OK, 1048576 rows affected (10.40 sec) Records: 1048576 Duplicates: 0 Warnings: 0 查看innodb 表和tokudb 表的大小 root@rac2:/srv/my3306/data# >du -sm _yang_t_tokudb_main_5_2_1d.tokudb8 _yang_t_tokudb_main_5_2_1d.tokudb root@rac2:/srv/my3306/data/yang# >du -sm t_innodb.ibd149 t_innodb.ibd root@rac2:/srv/my3306/data/yang# > innodb 149M tokudb 8M压缩比达到惊人的 149/8 = 18:1. 因为测试例子中val 的值都是相同的,生产环境中val值不相同的会比较多,压缩比会有所减小。4.2 存储引擎转换 mysql> show create table t1 \G *************************** 1. row *************************** Table: t1 Create Table: CREATE TABLE `t1` ( `id` int(10) NOT NULL AUTO_INCREMENT, `val` varchar(10) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 1 row in set (0.01 sec) mysql> alter table t1 engine=tokudb; Query OK, 4 rows affected (0.25 sec) Records: 4 Duplicates: 0 Warnings: 0 4.3 online ddl 测试 mysql> alter table t_tokudb add name varchar(30) default 'a'; Query OK, 0 rows affected (0.06 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> alter table t_tokudb add key idx_a(name); Query OK, 0 rows affected (4.79 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> alter table t_tokudb drop key idx_a; Query OK, 0 rows affected (0.57 sec) Records: 0 Duplicates: 0 Warnings: 0
一 简介 偏向于业务的(MySQL)DBA或者业务的开发者来说,order by 排序是一个常见的业务功能,将结果根据指定的字段排序,满足前端展示的需求。然而排序操作也是经常出现慢查询排行榜的座上宾。本文将从原理和实际案例优化,order by 使用限制等几个方面来逐步了解order by 排序。二 原理 在了解order by 排序的原理之前,强烈安利两篇关于排序算法的文章 《归并排序的实现》 《经典排序算法》。MySQL 支持两种排序算法,常规排序和优化,并且在MySQL 5.6版本中 针对order by limit M,N 做了特别的优化,这里列为第三种。 2.1 常规排序 a 从表t1中获取满足WHERE条件的记录 b 对于每条记录,将记录的主键+排序键(id,col2)取出放入sort buffer c 如果sort buffer可以存放所有满足条件的(id,col2)对,则进行排序;否则sort buffer满后,进行排序并固化到临时文件中。(排序算法采用的是快速排序算法) d 若排序中产生了临时文件,需要利用归并排序算法,保证临时文件中记录是有序的 e 循环执行上述过程,直到所有满足条件的记录全部参与排序 f 扫描排好序的(id,col2)对,并利用id去捞取SELECT需要返回的列(col1,col2,col3) g 将获取的结果集返回给用户。 从上述流程来看,是否使用文件排序主要看sort buffer是否能容下需要排序的(id,col2)对,这个buffer的大小由sort_buffer_size参数控制。此外一次排序需要两次IO,一次是捞(id,col2),第二次是捞(col1,col2,col3),由于返回的结果集是按col2排序,因此id是乱序的,通过乱序的id去捞(col1,col2,col3)时会产生大量的随机IO。对于第二次MySQL本身一个优化,即在捞之前首先将id排序,并放入缓冲区,这个缓存区大小由参数read_rnd_buffer_size控制,然后有序去捞记录,将随机IO转为顺序IO。 2.2 优化排序 常规排序方式除了排序本身,还需要额外两次IO。优化的排序方式相对于常规排序,减少了第二次IO。主要区别在于,放入sort buffer不是(id,col2),而是(col1,col2,col3)。由于sort buffer中包含了查询需要的所有字段,因此排序完成后可以直接返回,无需二次捞数据。这种方式的代价在于,同样大小的sort buffer,能存放的(col1,col2,col3)数目要小于(id,col2),如果sort buffer不够大,可能导致需要写临时文件,造成额外的IO。当然MySQL提供了参数max_length_for_sort_data,只有当排序元组小于max_length_for_sort_data时,才能利用优化排序方式,否则只能用常规排序方式。 2.3 优先队列排序 为了得到最终的排序结果,无论怎样,我们都需要将所有满足条件的记录进行排序才能返回。那么相对于优化排序方式,是否还有优化空间呢?5.6版本针对Order by limit M,N语句,在空间层面做了优化,加入了一种新的排序方式:优先队列,这种方式采用堆排序实现。堆排序算法特征正好可以解limit M,N 这类排序的问题,虽然仍然需要所有元素参与排序,但是只需要M+N个元组的sort buffer空间即可,对于M,N很小的场景,基本不会因为sort buffer不够而导致需要临时文件进行归并排序的问题。对于升序,采用大顶堆,最终堆中的元素组成了最小的N个元素,对于降序,采用小顶堆,最终堆中的元素组成了最大的N的元素。三 优化 通过上面的原理分析,我们知道排序的本质是通过一定的算法(耗费cpu 运算,内存,临时文件IO)将结果集变成有序的结果集。如何优化呢?答案是分两个方面利用索引的有序性(MySQL的B+ 树索引是默认从小到大递增排序)减少排序,最好的方式是直接不排序。 create table t1( id int not null primary key , key_part1 int(10) not null, key_part2 varchar(10) not null default '', key_part3 key idx_kp1_kp2(key_part1,key_part2,key_part4), key idx_kp3(id) ) engine=innodb default charset=utf8 以下种类的查询是可以利用到索引 idx_kp1_kp2的 SELECT * FROM t1 ORDER BY key_part1,key_part2,... ; SELECT * FROM t1 WHERE key_part1 = constant ORDER BY key_part2; SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 DESC; SELECT * FROM t1 WHERE key_part1 = 1 ORDER BY key_part1 DESC, key_part2 DESC; SELECT * FROM t1 WHERE key_part1 > constant ORDER BY key_part1 ASC; SELECT * FROM t1 WHERE key_part1 < constant ORDER BY key_part1 DESC; SELECT * FROM t1 WHERE key_part1 = constant1 AND key_part2 > constant2 ORDER BY key_part2 温馨提示 ,各位看官要辩证的看待官方给的例子,自己多动手实践。无法利用到索引排序的情况,其实我觉得这是本文的重点,对于广大开发同学而言,记住那种不能利用索引排序会更简单些。 1 最常见的情况 用来查找结果的索引(key2) 和 排序的索引(key1) 不一样,where a=x and b=y order by id; SELECT * FROM t1 WHERE key2=constant ORDER BY key1; 2 排序字段在不同的索引中,无法使用索引排序 SELECT * FROM t1 ORDER BY key1,key2; 3 排序字段顺序与索引中列顺序不一致,无法使用索引排序,比如索引是 key idx_kp1_kp2(key_part1,key_part2) SELECT * FROM t1 ORDER BY key_part2, key_part1; 4 order by中的升降序和索引中的默认升降不一致,无法使用索引排序 SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 ASC; 5 ey_part1是范围查询,key_part2无法使用索引排序 SELECT * FROM t1 WHERE key_part1> constant ORDER BY key_part2; 5 rder by和group by 字段列不一致 SELECT * FROM t1 WHERE key_part1=constant ORDER BY key_part2 group by key_part4; 6 索引本身是无序存储的,比如hash 索引,不能利用索引的有序性。 7 order by字段只被索引了前缀 ,key idx_col(col(10)) select * from t1 order by col ; 8 对于还有join的关联查询,排序字段并非全部来自于第一个表,使用explain 查看执行计划第一个表 type 值不是const 。 当无法避免排序操作时,又该如何来优化呢?很显然,优先选择using index的排序方式,在无法满足利用索引排序的情况下,尽可能让 MySQL 选择使用第二种单路算法来进行排序。这样可以减少大量的随机IO操作,很大幅度地提高排序的效率。 1 加大 max_length_for_sort_data 参数的设置 在 MySQL 中,决定使用老式排序算法还是改进版排序算法是通过参数max_length_for_sort_data来决定的。当所有返回字段的最大长度小于这个参数值时,MySQL 就会选择改进后的排序算法,反之,则选择老式的算法。所以,如果有充足的内存让MySQL 存放须要返回的非排序字段,就可以加大这个参数的值来让 MySQL 选择使用改进版的排序算法。 2 去掉不必要的返回字段 当内存不是很充裕时,不能简单地通过强行加大上面的参数来强迫 MySQL 去使用改进版的排序算法,否则可能会造成 MySQL 不得不将数据分成很多段,然后进行排序,这样可能会得不偿失。此时就须要去掉不必要的返回字段,让返回结果长度适应 max_length_for_sort_data 参数的限制。 同时也要规范MySQL开发规范,尽量避免大字段。当有select 查询列含有大字段blob或者text 的时候,MySQL 会选择常规排序。"
一 写在前面 本系列文章基于 5.7.12 版本讲述MySQL的新特性。从安装,文件结构,SQL ,优化 ,运维层面 复制,GITD等几个方面展开介绍 5.7 的新特性和功能。同时也建议大家跟踪官方blog和官方文档,以尽快知悉其新的变化。本文将重点介绍新版本对JSON格式的支持。5.1 支持JSON 从MySQL 5.7.8 开始,MySQL支持原生的JSON格式,即有独立的json类型,用于存放 json格式的数据。JSON 格式的数据并不是以string格式存储于数据库而是以内部的binary 格式,以便于快速的定位到json 格式中值。 在插入和更新操作时MySQL会对JSON 类型做校验,已检查数据是否符合json格式,如果不符合则报错。同时5.7.8 版本提供了四种JSON相关的函数,从而不用遍历全部数据。 a 创建: JSON_ARRAY(), JSON_MERGE(), JSON_OBJECT() b 修改: JSON_APPEND(), JSON_ARRAY_APPEND(), JSON_ARRAY_INSERT(), JSON_INSERT(), JSON_QUOTE(), JSON_REMOVE(), JSON_REPLACE(), JSON_SET(), and JSON_UNQUOTE() c 查询: JSON_CONTAINS(), JSON_CONTAINS_PATH(), JSON_EXTRACT(), JSON_KEYS(),JSON_SEARCH(). d 属性: JSON_DEPTH(), JSON_LENGTH(), JSON_TYPE() JSON_VALID(). 我们通过简单的例子来对json有一定的认识。 创建 mysql> SELECT JSON_ARRAY('id', 1, 'name', 'dba@youzan'); +-------------------------------------------+ | JSON_ARRAY('id', 1, 'name', 'dba@youzan') | +-------------------------------------------+ | ["id", 1, "name", "dba@youzan"] | +-------------------------------------------+ 1 row in set (0.00 sec) mysql> SELECT JSON_OBJECT('id', 1, 'name', 'dba@youzan'); +--------------------------------------------+ | JSON_OBJECT('id', 1, 'name', 'dba@youzan') | +--------------------------------------------+ | {"id": 1, "name": "dba@youzan"} | +--------------------------------------------+ 1 row in set (0.00 sec) 初始化 create table json_test ( id int(11) PRIMARY KEY NOT NULL auto_increment, data json ) engine=innodb default charset=utf8; insert into json_test values (1,'{ "DBA": [ { "firstName": "yi", "lastName":"yang", "email": "dba@youzan.com" }], "SA": [{ "firstName": "you", "lastName": "zan", "email": "sa@youzan.com" }], "PE": [{ "firstName": "xiao", "lastName": "xiao", "email": "pe@youzan.com" }] }') 修改 mysql> select * from json_test \G *************************** 1. row *************************** id: 1 data: {"PE": [{"email": "pe@youzan.com", "lastName": "xiao", "firstName": "xiao"}], "SA": [{"email": "sa@youzan.com", "lastName": "zan", "firstName": "you"}], "DBA": [{"email": "dba@youzan.com", "lastName": "yang", "firstName": "yi"}]} 1 row in set (0.00 sec) mysql> update json_test set data=json_array_append(data,'$.DBA','{"email": "dba@youzan.com", "lastName": "yang", "firstName": "qilong"}') where id=1; Query OK, 1 row affected (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from json_test \G *************************** 1. row *************************** id: 1 data: {"PE": [{"email": "pe@youzan.com", "lastName": "xiao", "firstName": "xiao"}], "SA": [{"email": "sa@youzan.com", "lastName": "zan", "firstName": "you"}], "DBA": [{"email": "dba@youzan.com", "lastName": "yang", "firstName": "yi"}, "{\"email\": \"dba@youzan.com\", \"lastName\": \"yang\", \"firstName\": \"qilong\"}"]} 1 row in set (0.00 sec) 删除 mysql> update json_test set data=json_remove(data,'$.DBA[1]') where id=1; Query OK, 1 row affected (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from json_test \G *************************** 1. row *************************** id: 1 data: {"PE": [{"email": "pe@youzan.com", "lastName": "xiao", "firstName": "xiao"}], "SA": [{"email": "sa@youzan.com", "lastName": "zan", "firstName": "you"}], "DBA": [{"email": "dba@youzan.com", "lastName": "yang", "firstName": "yi"}]} 1 row in set (0.00 sec) 查看 json的key mysql> SELECT id,json_keys(data) as "keys" FROM json_test; +----+---------------------+ | id | keys | +----+---------------------+ | 1 | ["PE", "SA", "DBA"] | +----+---------------------+ 1 row in set (0.00 sec) 查看DBA对应的值 mysql> SELECT id,json_extract(data,'$.DBA[0]') from json_test; +----+--------------------------------------------------------------------+ | id | json_extract(data,'$.DBA[0]') | +----+--------------------------------------------------------------------+ | 1 | {"email": "dba@youzan.com", "lastName": "yang", "firstName": "yi"} | +----+--------------------------------------------------------------------+ 1 row in set (0.00 sec) 其他函数的用法请感兴趣的读者朋友自行参考《官方文档》 MySQL 5.7 版本提供的json格式以及对应的操作函数极丰富了MySQL的存储格式,可以在一定程度上和Mongodb和pg竞争,对于经常使用MySQL varchar 存储json的业务是一个福音。同时再强调一下对于OLTP业务的表结构设计 尽可能的避免大字段存储。一来是减少不必要的查询带来的IO,带宽,内存方面的影响 二来是 避免因为表大小太大导致的ddl 时间成本增加系统风险。5.2 sys schema MySQL 5.7 版本新增了sys 数据库,该库通过视图的形式把information_schema 和performance_schema结合起来,查询出更加令人容易理解的数据,帮助DBA快速获取数据库系统的各种纬度的元数据信息,帮助DBA和开发快速定位性能瓶颈。详细的信息请参考《官方文档》,这里给两个例子能直观的了解sys 功能的强大。 mysql> select * from sys.schema_table_statistics limit 2\G *************************** 1. row *************************** table_schema: yang table_name: json_test total_latency: 1.81 ms rows_fetched: 21 fetch_latency: 1.45 ms rows_inserted: 2 insert_latency: 192.67 us rows_updated: 2 update_latency: 166.94 us rows_deleted: 0 delete_latency: 0 ps io_read_requests: 54 io_read: 4.21 KiB io_read_latency: 289.37 us io_write_requests: 43 io_write: 388.53 KiB io_write_latency: 703.51 us io_misc_requests: 75 io_misc_latency: 40.02 ms ##直接查看未使用过的索引 ,方便吗? mysql> SELECT * FROM schema_unused_indexes; +---------------+-------------+------------+ | object_schema | object_name | index_name | +---------------+-------------+------------+ | yang | t | idx_a | | yang | yy | idx_nm | +---------------+-------------+------------+ 2 rows in set (0.00 sec) 参考文章 [1] 《MySQL 5.7 官方文档》[2] 《MySQL 5.7 初探》[3] 《MySQL 5.7新特性之一》[4] 《MySQL 5.7新特性之二》[5] 《MySQL 5.7新特性之三》 [6] 《MySQL 5.7新特性之四》
写在前面 本系列文章基于5.7.12 版本讲述MySQL的新特性。从安装,文件结构,SQL ,优化 ,运维层面 复制,GITD等几个方面展开介绍5.7 的新特性和功能。同时也建议大家跟踪官方blog和官方文档,以尽快知悉其新的变化。前面写了一篇文章介绍 innodb 的特性,囿于相关知识点比较多 ,本文继续介绍5.7版本的innodb 新特性。4.1 innodb buffer dump 功能增强 5.7.5 新增加innodb_buffer_pool_dump_pct参数,来控制每个innodb buffer中转储活跃使用的innodb buffer pages的比例。之前的版本默认值是100%,当触发转储的时候 会全量dump innodb buffer pool中的pages。如果启用新的参数比如40 ,每个innodb buffer pool instance中有100个 ,每次转储每个innodb buffer 实例中的40个pages。 注意:当innodb发现innodb 后台io资源紧张时,会主动降低该参数设置的比例。4.2 支持多线程刷脏页 MySQL 5.6.2版本中,MySQL将刷脏页的线程从master线程独立出来,5.7.4版本之后,MySQL系统支持多线程刷脏页,进程的数量由innodb_page_cleaners参数控制,该参数不能动态修改,最小值为1 ,最大值支持64,5.7.7以及之前默认值是1 ,5.7.8版本之后修改默认参数为4。当启用多线程刷脏也,系统将刷新innodb buffer instance脏页分配到各个空闲的刷脏页的线程上,如果设置的innodb_page_cleaners>innodb_buffer_pool_instances,系统会自动重置为innodb_buffer_pool_instances大小。4.3 动态调整 innodb buffer size 从5.7.5版本, MySQL支持在不重启系统的情况下动态调整innodb_buffer_pool_size。resize的过程是以chunk(每个chunk的大小默认为128M)的为单位迁移pages到新的内存空间,迁移进度可以通过Innodb_buffer_pool_resize_status 查看。记住整个resize的大小是以chunk为单位的。innodb_buffer_pool_chunk_size的大小,计算公式是innodb_buffer_pool_size / innodb_buffer_pool_instances,新调整的值必须是 innodb_buffer_pool_chunk_size*innodb_buffer_pool_instances的整数倍。如果不是整数倍,则系统则会调整值为大于两者乘积的最大值。 例子 mysql> SHOW STATUS WHERE Variable_name='InnoDB_buffer_pool_resize_status'; +----------------------------------+----------------------------------------------------------------------+ | Variable_name | Value | +----------------------------------+----------------------------------------------------------------------+ | Innodb_buffer_pool_resize_status | Size did not change (old size = new size = 268435456. Nothing to do. | +----------------------------------+----------------------------------------------------------------------+ 1 row in set (0.00 sec) mysql> set global innodb_buffer_pool_size=128*1024*1024; Query OK, 0 rows affected (0.00 sec) mysql> SHOW STATUS WHERE Variable_name='InnoDB_buffer_pool_resize_status'; +----------------------------------+----------------------------------------------------+ | Variable_name | Value | +----------------------------------+----------------------------------------------------+ | Innodb_buffer_pool_resize_status | Completed resizing buffer pool at 160702 23:53:51. | +----------------------------------+----------------------------------------------------+ 1 row in set (0.00 sec) mysql> set global innodb_buffer_pool_size=256*1024*1024; Query OK, 0 rows affected (0.00 sec) mysql> SHOW STATUS WHERE Variable_name='InnoDB_buffer_pool_resize_status'; +----------------------------------+----------------------------------------------------+ | Variable_name | Value | +----------------------------------+----------------------------------------------------+ | Innodb_buffer_pool_resize_status | Completed resizing buffer pool at 160702 23:54:19. | +----------------------------------+----------------------------------------------------+ 1 row in set (0.00 sec) online调整bp size的log 记录大致过程 a 计算要调整的bpsize b 禁止AHI,清理所有的索引缓存 c Withdrawing block是遍历freelist 确定可以使用的空闲block d 锁住整个buffer pool e 迁移重新分配chunk/删除可以释放的chunk f 设置innodb_buffer_pool_size为新的值 g 重新开启AHI 2016-07-02T15:40:44.724495Z 0 [Note] InnoDB: Resizing buffer pool from 134217728 to 268435456 (unit=134217728). 2016-07-02T15:40:44.724546Z 2 [Note] InnoDB: Resizing buffer pool from 134217 (new size: 268435456 bytes) 2016-07-02T15:40:44.724559Z 0 [Note] InnoDB: Disabling adaptive hash index. 2016-07-02T15:40:44.724979Z 0 [Note] InnoDB: disabled adaptive hash index. 2016-07-02T15:40:44.725029Z 0 [Note] InnoDB: Withdrawing blocks to be shrunken. 2016-07-02T15:40:44.725040Z 0 [Note] InnoDB: Latching whole of buffer pool. 2016-07-02T15:40:44.725210Z 0 [Note] InnoDB: buffer pool 0 : resizing with chunks 1 to 2. 2016-07-02T15:40:44.735439Z 0 [Note] InnoDB: buffer pool 0 : 1 chunks (8192 blocks) were added. 2016-07-02T15:40:44.735511Z 0 [Note] InnoDB: Completed to resize buffer pool from 134217728 to 268435456. 2016-07-02T15:40:44.735561Z 0 [Note] InnoDB: Re-enabled adaptive hash index. 2016-07-02T15:40:44.735586Z 0 [Note] InnoDB: Completed resizing buffer pool at 160702 23:40:44. 这个特性是最令众多MySQL DBA 期待的特性之一。以后线上动态扩容,缩容就无需做数据库切换了,间接增强了系统的稳定性和DBA的生活幸福感。当然本文中介绍的略显粗略。详细内容请参考《官方文档》 4.4 支持全局表空间 全局表空间可以被所有的数据库的表共享,而且相比于 file-per-table tablespaces. 使用共享表空间可以节约元数据方面的内存。(需要更深入的了解共享表空间 主要是大小 收缩问题) mysql> CREATE TABLESPACE `youzan_com` -> ADD DATAFILE 'youzan_com.ibd' FILE_BLOCK_SIZE = 16k; Query OK, 0 rows affected (0.02 sec) mysql> use yang Database changed mysql> create table yztb(id int primary key not null ,val char(10)) engine=innodb default charset=utf8 TABLESPACE youzan_com ; Query OK, 0 rows affected (0.04 sec) mysql> create database youzan default charset utf8; Query OK, 1 row affected (0.02 sec) mysql> use youzan Database changed mysql> mysql> create table yztb(id int primary key not null ,val char(10)) engine=innodb default charset=utf8 TABLESPACE youzan_com ; Query OK, 0 rows affected (0.03 sec) 4.5 行格式默认为DYNAMIC 从MySQL 5.7.9 开始,行格式DYNAMIC 取代COMPACT 成为innodb存储引擎默认的行格式,MySQL提供了新的参数innodb_default_row_format来控制Innodb 行格式,详细的信息请参考《Specifying the Row Format for a Table》4.6 支持原生的分区表 在MySQL 5.7.6之前的版本中,创建分区表时MySQL为每个分区创建一个ha_partition handler,自MySQL 5.7.6之后,MySQL支持原生的分区表并且只会为分区表创建一个partition-aware handler,这样的分区表功能增强节约分区表使用的内存。对于老版本创建的分区表在升级到新的版本之后怎么处理呢?莫慌,5.7.9之后,MySQL提供了如下升级方式解决这个问题: ALTER TABLE ... UPGRADE PARTITIONING. 当然友情提示:从我个人的理解来看,在没有合适的自动化维护分区表系统的基础上,不推荐使用分区表。四年的工作经历已经数次在分区表上掉坑里了。4.7 支持truncate undo logs MySQL 5.7.5版本开始支持truncate undo 表空间中的undo log。启用该特性必须设置innodb_undo_log_truncate=[ON|1]。大致原理是系统必须设置至少两个undo 表空间(初始化的时候设置 innodb_undo_tablespaces=2 ) 用于清理undo logs的切换。该特性的好处是 解决了 ibdata 文件一直增大的问题,减轻系统的空间使用。 详细信息参考《官方文档》小结 到这里 innodb 部分算是基本完成,但是依然有很多其他的特性需要"探索" ,自己在写《MySQL 5.7 新特性》系列文章的时候,或深入或简单阅读官方文档,深刻的感觉到5.7 有很多新的变化,同时也感到自己对于5.6版本的官方文档并未阅读透彻,并没有之前学习Oracle的时候的学习方式---官方文档是最好的教材。在这里仅以过来人 DBA 老司机的角度给MySQL DBA新人的建议 多阅读官方文档,胜过市面上99%的书籍。 后面会继续探索MySQL 5.7 新特性。参考文章 [1] 《MySQL 5.7 官方文档》 [2] 《MySQL 5.7 初探》 [3] 《MySQL 5.7新特性之一》 [4] 《MySQL 5.7新特性之二》[5] 《MySQL 5.7新特性之三》
一 简介 在使用Python 开发MySQL自动化相关的运维工具的时候,遇到一些有意思的问题,本文介绍Python的 subprocess 模块以及如何和MySQL交互具体操作,如启动 ,关闭 ,备份数据库。二 基础知识 Python2.4引入subprocess模块来管理子进程,可以像Linux 系统中执行shell命令那样fork一个子进程执行外部的命令,并且可以连接子进程的output/input/error管道,获取命令执行的输出,错误信息,和执行成功与否的结果。 Subprocess 提供了三个函数以不同的方式创建子进程。他们分别是subprocess.call() 父进程等待子进程完成,并且返回子进程执行的结果 0/1 其实现方式 def call(*popenargs, **kwargs): return Popen(*popenargs, **kwargs).wait() 例子 >>> out=subprocess.call(["ls", "-l"]) total 88 drwxr-xr-x 5 yangyi staff 170 1 25 22:37 HelloWorld drwxr-xr-x 11 yangyi staff 374 12 18 2015 app -rw-r--r-- 1 yangyi staff 3895 4 19 11:29 check_int.py ..... 省略一部分 >>> print out 0 >>> out=subprocess.call(["ls", "-I"]) ls: illegal option -- I usage: ls [-ABCFGHLOPRSTUWabcdefghiklmnopqrstuwx1] [file ...] >>> print out 1 subprocess.check_call() 父进程等待子进程完成,正常情况下返回0,当检查退出信息,如果returncode不为0,则触发异常 subprocess.CalledProcessError,该对象包含有returncode属性,应用程序中可用try...except...来检查命令是否执行成功。 其实现方式 def check_call(*popenargs, **kwargs): retcode = call(*popenargs, **kwargs) if retcode: cmd = kwargs.get("args") raise CalledProcessError(retcode, cmd) return 0 例子 >>> out=subprocess.check_call(["ls"]) HelloWorld check_int.py enumerate.py hello.py >>> print out 0 >>> out=subprocess.check_call(["ls",'-I']) #执行命令失败的时候回抛出CalledProcessError异常,并且返回结果1 ls: illegal option -- I usage: ls [-ABCFGHLOPRSTUWabcdefghiklmnopqrstuwx1] [file ...] Traceback (most recent call last): File "", line 1, in <module> File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 540, in check_call raise CalledProcessError(retcode, cmd) subprocess.CalledProcessError: Command '['ls', '-I']' returned non-zero exit status 1 subprocess.check_output() 和 subprocess.check_call() 类似,但是其返回的结果是执行命令的输出,而非返回0/1 其实现方式 def check_output(*popenargs, **kwargs): process = Popen(*popenargs, stdout=PIPE, **kwargs) output, unused_err = process.communicate() retcode = process.poll() if retcode: cmd = kwargs.get("args") raise CalledProcessError(retcode, cmd, output=output) return output 例子 >>> out=subprocess.check_output(["ls"]) #成功执行命令 >>> print out HelloWorld check_int.py enumerate.py flasky hello.py >>> out=subprocess.check_output(["ls","-I"])#执行命令出现异常直接打印出异常信息。 ls: illegal option -- I usage: ls [-ABCFGHLOPRSTUWabcdefghiklmnopqrstuwx1] [file ...] Traceback (most recent call last): File "", line 1, in <module> File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 573, in check_output raise CalledProcessError(retcode, cmd, output=output) subprocess.CalledProcessError: Command '['ls', '-I']' returned non-zero exit status 1 >>> 通过上面三个例子,我们可以看出前面两个函数不容易控制输出内容,在使用subprocess包中的函数创建子进程执行命令的时候,需要考虑1) 在创建子进程之后,父进程是否暂停,并等待子进程运行。2) 如何处理函数返回的信息(命令执行的结果或者错误信息)3) 当子进程执行的失败也即returncode不为0时,父进程如何处理后续流程?三 subprocess的核心类 Popen() 认真的读者朋友可以看出上面三个函数都是基于Popen实现的,为啥呢?因为 subprocess 仅仅提供了一个类,call(),check_call(),check_outpu()都是基于Popen封装而成。当我们需要更加自主的应用subprocess来实现应用程序的功能时, 我们要自己动手直接使用Popen()生成的对象完成任务。接下来我们研究Popen()的常见用法 ,详细的用法请参考官方文档 Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0) 这里我们只要掌握常用的参数即可 args 字符串或者列表,比如 "ls -a" / ["ls","-a"] stdin/stdout/stderr 为None时表示没有任何重定向,继承父进程,还可以设置为PIPE 创建管道/文件对象/文件描述符(整数)/stderr 还可以设置为 STDOUT 后面会给出常见的用法 shell 是否使用shell来执行程序。当shell=True, 它将args看作是一个字符串,而不是一个序列。在Unix系统,且 shell=True时,shell默认使用 /bin/sh. 如果 args是一个字符串,则它声明了通过shell执行的命令。这意味着,字符串必须要使用正确的格式。 如果 args是一个序列,则第一个元素就是命令字符串,而其它的元素都作为参数使用。可以这样说,Popen等价于: Popen(['/bin/sh', '-c', args[0], args[1], ...]) 与上面第二部分介绍的三个函数不同,subprocess.Popen() fork子进程之后主进程不会等待子进程结束,而是直接执行后续的命令。当我们需要等待子进程结束必须使用wait()或者communicate()函数。举个例子, import subprocess sbp=subprocess.Popen(["ping","-c","5","www.youzan.com"]) print "ping is not done" 从执行结果上看,子进程 ping命令并未执行完毕,subprocess.Popen()后面的命令就开始执行了。Popen常见的函数 Popen.poll() 用于检查子进程是否已经结束,设置并返回returncode属性。 Popen.wait() 等待子进程结束,设置并返回returncode属性。 Popen.communicate(input=None) 与子进程进行交互。向stdin发送数据,或从stdout和stderr中读取数据。可选参数input指定发送到子进程的参数。 Communicate()返回一个元组:(stdoutdata, stderrdata)。注意:如果希望通过进程的stdin向其发送数据,在创建Popen对象的时候,参数stdin必须被设置为PIPE。同样,如果希望从stdout和stderr获取数据,必须将stdout和stderr设置为PIPE。需要注意的是 communicate()是Popen对象的一个方法,该方法会阻塞父进程,直到子进程完成。 Popen.send_signal(signal) 向子进程发送信号。 Popen.terminate() 终止子进程。 Popen.kill() 杀死子进程。 Popen.pid 获取子进程的进程ID。 Popen.returncode 获取进程的返回值,成功时,返回0/失败时,返回 1。如果进程还没有结束,返回None。 这里需要多做说明的是 对于 wait() 官方提示 Warning This will deadlock when using stdout=PIPE and/or stderr=PIPE and the child process generates enough output to a pipe such that it blocks waiting for the OS pipe buffer to accept more data. Use communicate() to avoid that. 即当stdout/stdin设置为PIPE时,使用wait()可能会导致死锁。因而建议使用communicate 而对于communicate,文档又给出: Interact with process: Send data to stdin. Read data from stdout and stderr, until end-of-file is reached. Wait for process to terminate. The optionalinput argument should be a string to be sent to the child process, orNone, if no data should be sent to the child.communicate() returns a tuple (stdoutdata, stderrdata). Note that if you want to send data to the process’s stdin, you need to create the Popen object with stdin=PIPE. Similarly, to get anything other thanNone in the result tuple, you need to give stdout=PIPE and/orstderr=PIPE too. Note The data read is buffered in memory, so do not use this method if the data size is large or unlimited. communicate会把数据读入内存缓存下来,所以当数据很大或者是无限的数据时不要使用。 那么坑爹的问题来了:当你要使用Python的subprocess.Popen实现命令行之间的管道传输,同时数据源又非常大(比如读取上GB的文本或者无尽的网络流)时,官方文档不建议用wait,同时communicate还可能把内存撑爆,我们该怎么操作?四 Subprocess 和MySQL 的交互 纸上来得终觉浅,绝知此事要躬行。自动化运维需求中会有重启/关闭/备份/恢复 MySQL的需求。怎么使用Python的subprocess来解决呢?启动MySQL的命令如下 startMySQL="/usr/bin/mysqld_safe --defaults-file=/srv/my{0}/my.cnf --read_only=1 & ".format(port) 实际上使用child=subprocess.Popen(startMySQL,shell=True,stdout=stdout=subprocess.PIPE),子进程mysql_safe是无任何返回输出的,使用,child.communicate()或者读取stdout 则会持续等待。 需要使用 child.wait()或者child.poll()检查子进程是否执行完成。 import subprocess,time def startMySQL(port): startMySQL="/usr/bin/mysqld_safe --defaults-file=/srv/my{0}/my.cnf --read_only=1 & ".format(port) child=subprocess.Popen(startMySQL, shell=True,stdout=subprocess.PIPE) child.poll() time.sleep(3) #有些MySQL实例启动可能需要一定的时间 if child.returncode: print "instance {0} startup failed ...".format(port) else: print "instance {0} startup successed ...".format(port) return root@rac3:~/python# >python 1.py instance 3308 startup successed ... 五 参考资料 [1] 官方文档 [2] Python中的subprocess与Pipe [3] python类库31[进程subprocess]
前言 Python 的logging 模块定义的函数和类为应用程序和库实现了一个灵活的事件日志系统。该模块提供多种日志级别并且支持多种记录日志的方式比如 终端,文件等等。在编写一个软件系统的时候 ,使用日志系统十分有必要 记录函数的执行过程和异常报错信息。本文算是一个学习笔记,对于跨文件引用的初学者有一定帮助。 一 入门 talk is cheap ,show me the code. 1 例子 logt.py #!/usr/bin/python # -*- coding:utf-8 -*- import logging logging.debug('this is a debug message') logging.info('this is a info message') logging.warn('this is a warn message') logging.error('this is a error message') logging.critical('this is a critical message') 运行该脚本 root@rac4:~# >python logt.py WARNING:root:this is a warn message ERROR:root:this is a error message CRITICAL:root:this is a critical message 看到这个输出 ,有人可能会有疑问 为什么 logging.debug()和logging.info()没有输出内容? 恩,这个是个好问题,莫慌,且看下文分析。 2. logging 的日志级别 logging 提供了完整的日志体系,支持五种日志级别以便记录程序的执行过程。 DEBUG 详细信息,典型地调试问题的时候会使用。 INFO 证明事情按预期工作。 WARNING 表明发生了一些意外,或者不久的将来会发生问题(如‘磁盘满了’)。软件还是在正常工作。 ERROR 由于更严重的问题,软件已不能执行一些功能了。 CRITICAL 严重错误,表明软件已不能继续运行了。 以上五种日志级别从低到高分别是:DEBUG < INFO < WARNING < ERROR < CRITICAL 。默认的是WARNING,只有日志级别高于WARNING的日志信息才会输出,而输出有两种方式 一种输出控制台,也是默认的方式,另一种是记录到文件中,如日志文件。 3 logging的配置 python提供了多种配置方式控制日志的显示格式,内容,目的等。如上述例子中的日志输出“WARNING:root:this is awarn message”。 显式创建记录器Logger、处理器Handler和格式化器Formatter,并进行相关设置; 通过简单方式进行配置,使用basicConfig()函数直接进行配置; 通过配置文件进行配置,使用fileConfig()函数读取配置文件; 通过配置字典进行配置,使用dictConfig()函数读取配置信息; 本文使用basicConfig()方式作为例子。 basicConfig()支持下列关键字参数。 格式 描述 filename 创建一个FileHandler,使用指定的文件名,而不是使用StreamHandler。 filemode 如果指明了文件名,指明打开文件的模式(如果没有指明filemode,默认为'a',即append方式)。 format handler使用指明的格式化字符串。详细的请参考 官方文档 datefmt 使用指明的日期/时间格式 比如 '%Y-%m-%d %H:%M:%S' 2016-06-14 10:10:00。 level 指定logger的日志级别。 stream 使用指明的流来初始化StreamHandler。该参数与'filename'不兼容,如果两个都有,'stream'将被忽略。 #!/usr/bin/python # -*- coding:utf-8 -*- import logging logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', datefmt='%Y%m%d %H:%M:%S', filename='myapp.log', filemode='w') logging.info('info message') logging.warn('warn message') logging.error('error message') logging.critical('critical message') 二 进阶介绍 logging模块提供四个组件logger,handler,filter,formatter logger:记录器,为应用代码提供日志接口。logger最长用的操作有两类:配置和发送日志消息。可以通过logging.getLogger(name 获取logger对象,如果不指定name则返回root对象,如第一个例子。多次使用相同的name调用getLogger方法返回同一个logger对象。 调用方法: logger = logging.getLogger(logger_name) #如果不指定 logger_name ,则默认创建一个root logger,见第一个例子。 logger.setLevel(logging.ERROR) #设置日志级别为ERROR,即只有日志级别大于等于ERROR的日志才会输出 logger.addHandler(handler_name) #为logger实例增加一个处理器 logger.removeHandler(handler_name) # 为logger实例删除一个处理器 handler:将日志内容发送到合适的目的,比如文件,终端等。一个logger对象可以通过addHandler方法添加0到多个handler,每个handler又可以定义不同日志级别,以实现日志分级过滤显示。 详细信息请移步 官方文档 调用方法 StreamHandler ch = logging.StreamHandler(stream=None) FileHandler fh = logging.FileHandler(filename, mode='a', encoding=None, delay=False) 返回FileHandler类的实例。指明的文件会被打开,并用作日志流。如果没有指明mode,使用'a'。如果encoding不为None,会用指定的编码来打开文件。如果delay为真,只到第一次调用emit()的时候才打开文件。默认情况下,文件会一直增长。filter:提供一种优雅的方式决定一个日志记录是否发送到handler。formatter:指定日志记录输出的具体格式。formatter的构造方法需要两个参数:消息的格式字符串和日期字符串,这两个参数都是可选的。现在我们测试另外一个例子 test_log.py ,该脚本定义了一个init_log 函数,通过传入的参数显示的配置logging。函数里面创建两个logging 实例,分别将日志输出到文件和终端。 import logging import logging.handlers LOG_LEVELS = {'DEBUG': logging.DEBUG, 'INFO': logging.INFO, 'ERROR': logging.ERROR, 'CRITICAL': logging.CRITICAL } LOGGING_FORMAT = "%(asctime)s - [%(name)s] - [%(levelname)s] - %(message)s" #日志的输出的格式 STANDARD_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' #时间格式 2016-06-14 10:10:00 DEFAULT_LOG_MAX_SIZE = 50 * 1024 * 1024 #当达到50M就进行切分 def init_log(logger_name, level='DEBUG', logfile='/tmp/logtest.log', formatter=LOGGING_FORMAT, max_size=DEFAULT_LOG_MAX_SIZE): logger = logging.getLogger(logger_name) #初始化一个name为logger_name的logging实例 logger.setLevel(LOG_LEVELS[level]) #设置日志的级别 fh = logging.handlers.RotatingFileHandler(logfile, maxBytes=max_size,backupCount=3) #定义日志输出到文件的handler ,也可以定义 fh=logging.FileHandler(logfile) fh.setLevel(logging.DEBUG) # create console handler with a higher log level ch = logging.StreamHandler() #定义日志输出到终端的handler ch.setLevel(logging.INFO) formatter = logging.Formatter(formatter) fh.setFormatter(formatter) ch.setFormatter(formatter) # add the handlers to the logger logger.addHandler(fh) logger.addHandler(ch) return logger if __name__ == '__main__': LOGGER=init_log('youzan','INFO','0614.log') LOGGER.info('info message') LOGGER.warn('warn message') LOGGER.error('error message') LOGGER.critical('critical message') 执行该脚本 root@rac4:~# >python logconfig.py 2016-06-14 10:38:15,190 - [youzan] - [INFO] - info message 2016-06-14 10:38:15,190 - [youzan] - [WARNING] - warn message 2016-06-14 10:38:15,191 - [youzan] - [ERROR] - error message 2016-06-14 10:38:15,191 - [youzan] - [CRITICAL] - critical message 同时在 0614log 文件里面也会记录对应的log root@rac4:~# >cat 0614.log 2016-06-14 10:38:15,190 - [youzan] - [INFO] - info message 2016-06-14 10:38:15,190 - [youzan] - [WARNING] - warn message 2016-06-14 10:38:15,191 - [youzan] - [ERROR] - error message 2016-06-14 10:38:15,191 - [youzan] - [CRITICAL] - critical message 三 拓展 其实这才是本章的重点,在构建一个整套的程序时,怎么全局配置logging 模块,并在不同的程序中调用呢?例子如下: root@rac4:~/python# >tree . . ├── log2.py — 引用 logconfig中的logging 配置 ├── logconfig.py —配置logging 输出格式 特别注意,如果是跨文件访问定义的logconfig 配置 ,必须在 logconfig.py 所在的目录创建 __init__.py 文件。 log2.py import sys import os sys.path.append(os.path.join(os.path.dirname(__file__), '..')) import logging from logconfig import init_log logger=init_log('logtest','INFO',log='666.log') ##通过传参来定义新的 logging 模块配置。 logger.info('this is a test %s'," yangyi@youzan.com") logger.debug('debug message') logger.warn('warn message') logger.error('error message') logger.critical('critical message') root@rac4:~# >python log2.py 2016-06-14 13:26:50,713 - [logtest] - [INFO] - this is a test yangyi@youzan.com 2016-06-14 13:26:50,713 - [logtest] - [WARNING] - warn message 2016-06-14 13:26:50,714 - [logtest] - [ERROR] - error message 2016-06-14 13:26:50,714 - [logtest] - [CRITICAL] - critical message 四 参考文章 1 http://www.jianshu.com/p/feb86c06c4f4 2 http://python.usyiyi.cn/python_278/library/logging.html#
简介 相信大家在开发脚本或者写程序的时候 ,大多会遇到如何判断已经有程序在运行的情况。比如设计备份binlog ,由于某个实例产生的binlog 数量大于备份的速度,在下一个时间点,会启动一个新的进程对binlog进行备份。那我们要怎么解决呢,本文分别从 shell和python的角度提出我的解决方法,同时也推荐《 Ensure a single instance of an application in Linux》,这里有比较详细的讨论。一 shell 脚本的解决方法 利用mkdir 的特性 创建已经存在的文件目录则会失败。程序第一次运行的时候可以创建一个 /tmp/lock文件夹,标示当前已经运行一个程序,当启动第二个程序时,mkdir /tmp/lock 便会失败。 #!/bin/bash mkdir /tmp/lock if [ $? -ne 0 ];then echo "there is tr script running .. " exit 1 fi trap "rm -fr /tmp/lock " SIGINT SIGTERM sleep 50 if [ -d /tmp/lock ];then rm -fr /tmp/lock echo "rm -fr /tmp/lock" fi 注意 linux中的trap命令是防止脚本异常终止 :被kill (不是kill -9) ,crtl+c 中断 比较详细的资料 《Linux命令之trap - 在脚本中处理信号》http://codingstandards.iteye.com/blog/836588二 python 脚本的解决方法 网上搜索python 锁定文件的时候,都会提示 fcntl 模块。Python的文件锁目前使用的是fcntl这个库,它实际上为 Unix上的ioctl,flock和fcntl 函数提供了一个接口。 fcntl模块的函数flock(file_handle, operation) 其中 file_handle 表示文件描述符,operation 指要进行的锁操作,有如下几种: fcntl.LOCK_UN 解锁:删除floc()函数创建的锁 fcntl.LOCK_EX 排他锁:除加锁进程外其他进程没有对已加锁文件读写访问权限。 fcntl.LOCK_SH 共享锁:所有进程没有写访问权限,即使是加锁进程也没有。所有进程有读访问权限。 fcntl.LOCK_NB 非阻塞锁: 此参数意味着函数不能获得文件锁就立即返回,否则,如果使用LOCK_EX/LOCK_SH请求加锁不成功,则当前进程会等待获得文件锁。使用LOCK_NB可以在获得这个排他锁的情况下不阻塞该进程,LOCK_NB 也可以同LOCK_SH或LOCK_NB进行按位或(|)运算操作,比如fcnt.flock(file_handle,fcntl.LOCK_EX|fcntl.LOCK_NB),此时系统便不会阻塞当前的进程。 注意: 1. 对于文件的f.close() 操作会使文件锁失效; 2. 主进程结束后文件锁失效; 3. flock()的LOCK_EX是"劝告锁",系统内核不会强制检查锁的状态,需要在代码中进行文件操作的地方显式检查才能生效。 测试脚本 脚本中使用is_running 函数对文件加锁,time.sleep(10) 模拟长时间执行的程序,第一次运行lock.py 成功加锁,在程序运行期间 再次运行lock.py ,获取锁时会失败,并且及时退出程序。 #!/usr/bin/python2.6 #coding:utf8 import time import fcntl import sys def is_running(file): lock_file=open(file,"w") try: fcntl.lockf(lock_file,fcntl.LOCK_EX|fcntl.LOCK_NB) print "给文件加锁 ,请等待10s..." except : print '文件加锁,无法执行,请稍后运行。' return None return lock_file if __name__ == "__main__": lockfile="/tmp/rsync_is_running" a=is_running(lockfile) if a is None : print "lock file failed , rsync is running .quit ..." sys.exit(0) else : print "lock file successed !!! " time.sleep(10) 测试例子: 会话一 会话二三 小结 其实还可以有很多其他的方式 比如 最容易想到的 application_name.pid 或者ps application_name | wc -l 来判断,不过使用ps 命令时,遇到和系统其他命令关键字一样的时候 ,就会不准。http://stackoverflow.com/中比较推荐使用pid ,各位读者朋友也可以提出自己的见解。欢迎讨论。
前言 和关系型数据库一样,Redis也有自己的高可用属性,主从复制,相比而言 redis的主从复制的搭建过程更为简单。一 redis 主从复制的特点 1 同一个master可以拥有多个slaves。 2 master下的Slave还可以接受同一架构中其它slave的链接与同步请求,实现数据的级联复制,即master->slave->Sslave模式; 3 master以非阻塞的方式同步数据至slave,这将意味着master会继续处理一个或多个slave的读写请求; 4 slave端同步数据也可以修改为非阻塞是的方式,当slave在执行新的同步时,它仍可以用旧的数据信息来提供查询;否则,当slave与master失去联系时,slave会返回一个错误给客户端; 5 redis的主从复制具有可扩展性,即多个slave专门提供只读查询与数据的冗余,master端专门提供写操作,实现读写分离,负载均衡; 6通过配置禁用Master数据持久化机制,将其数据持久化操作交给Slaves完成,避免在Master中要有独立的进程来完成此操作。二 redis 复制的原理以及注意事项 我们可以从slave redis 的log中查看redis 复制的主要过程 [3308] 24 May 23:09:44.133 * Connecting to MASTER 10.0.2.8:6379 [3308] 24 May 23:09:44.134 * MASTER <-> SLAVE sync started [3308] 24 May 23:09:44.134 * Non blocking connect for SYNC fired the event. [3308] 24 May 23:09:44.136 * Master replied to PING, replication can continue... [3308] 24 May 23:09:44.137 * Partial resynchronization not possible (no cached master) [3308] 24 May 23:09:44.137 * Full resync from master: 295a0c0dbf70e9b372ebdf6d14b0cae90072ac89:1 [3308] 24 May 23:09:44.143 * MASTER <-> SLAVE sync: receiving 40 bytes from master [3308] 24 May 23:09:44.144 * MASTER <-> SLAVE sync: Flushing old data [3308] 24 May 23:09:44.144 * MASTER <-> SLAVE sync: Loading DB in memory [3308] 24 May 23:09:44.144 * MASTER <-> SLAVE sync: Finished with success [3308] 24 May 23:24:44.033 * 1 changes in 900 seconds. Saving... [3308] 24 May 23:24:44.034 * Background saving started by pid 3385 [3385] 24 May 23:24:44.037 * DB saved on disk [3385] 24 May 23:24:44.038 * RDB: 0 MB of memory used by copy-on-write [3308] 24 May 23:24:44.134 * Background saving terminated with success 结合redis 主从同步的日志和官方文档,我们总结一下redis的同步过程原理: 1 当一个redis 实例加上slaveof master_ip port 方式启动时,无论是第一次连接还是重连,它会主向master发送一个SYNC command,请求同步连接。 2 当master 收到SYNC 命令之后,发送一个PING 命令给slave ,且在后台执行bgsave命令,将数据快照保存到数据文件中,同时会记录所有修改数据的命令并缓存在数据文件。 3 master后台进程把数据持久化到磁盘之后,就发送数据库文件给slave。 4 slave端将数据文件保存到硬盘上,然后将其在加载到内存中,完成第一次全量同步操作。 5 master会将之前收集到的修改数据的操作和新的修改数据的操作发送给Slave端。 6 slave 收到这些命令之后会在本地执行,类似于MySQL的sql_thread操作。从而达到主从数据最终一致。 7 如果master和slave之间的链接出现断连,slave可以自动重连master。根据版本的不同,断连后同步的方式也不同: 2.8之前:重连成功之后,一次全量同步操作将被自动执行. 2.8之后:重连成功之后,进行部分同步操作.Master持久化功能关闭时Replication的安全性 当我们部署redis主从复制的时候,一般都会强烈建议把master的持久化开关打开。即使为了避免持久化带来的延迟影响,不把持久化开关打开,那么也应该把master配置为不会自动启动的。因为master异常crash后再重启是非常危险的,会导致slave中的数据会被清空。 假设我们有一个redis节点A,设置为master,并且关闭持久化功能,另外两个节点B和C是它的slave,并从A复制数据。 如果A节点崩溃了导致所有的数据都丢失了,它会有重启系统来重启进程。但是由于持久化功能被关闭了,所以即使它重启了,它的数据集是空的。 而B和C依然会通过replication机制从A复制数据,所以B和C会从A那里复制到一份空的数据集,并用这份空的数据集将自己本身的非空的数据集替换掉。于是就相当于丢失了所有的数据。 即使使用一些HA工具,比如说sentinel来监控master-slaves集群,也会发生上述的情形,因为master可能崩溃后迅速恢复。速度太快而导致sentinel无法察觉到一个failure的发生。部分同步(partial resynchronization ) 这个特殊将会使用PSYNC 命令,注意该命令在2.8之后才支持PSYNC如果一个salve使用的是老的版本仅支持SYNC命令,那么将会用SYNC来同步无磁盘的复制 通常一个同步需要在磁盘上创建一个RDB文件,然后再重新加载这个文件来进行与slave数据同步 由于磁盘的读写是非常慢的,这对于redis master是一个非常有压力的操作,在2.8.18之后的第一个版来尝试使用无磁盘的复制,在这个设置里了进程直接把RDB 发送到slaves,而不需要使用磁盘来做中间的存储。 需要注意的是 不过这个功能目前处于实验阶段,还未正式发布。 三 部署主从复制 环境 : rac4 主库 10.0.2.8:6379 rac3 从库 10.0.2.6:6379 1 在两台机器上或者同一台机器起不同的端口,具体步骤请参考《redis 初探》 2 修改rac3 的redis.cnf 添加配置 ,指向rac4 slaveof 10.0.2.8 6379 也可以在rac3上启动redis之后执行 slaveof 10.0.2.8 6379 不过一旦slave重启之后 主从关系就消失了。建议在redis.cnf 配置 3 启动主库和从库 /usr/local/bin/redis-server /etc/redis/redis.conf 至此 搭建一个以rac4为主库 ,rac3为从库的redis 复制系统。四 应用案例 主库 root@rac4:~# >redis-cli 127.0.0.1:6379> select 0 OK 127.0.0.1:6379> set name yangyi OK 127.0.0.1:6379> get name "yangyi" 127.0.0.1:6379> 从库 root@rac3:~# >redis-cli 127.0.0.1:6379> select 0 OK 127.0.0.1:6379> get name "yangyi" 127.0.0.1:6379> 五 推荐文章 文章篇幅和时间有限,本文并未完整的介绍Redis的高可用特性,后续研究之后会陆续补充完整。 推荐《redis replication官方介绍》,留一个(小白的)问题 ,redis主从复制过程和持久化的方式之间有什么关系吗 ,基于rdb 或者 aof的持久化 对主从复制有什么影响?
写在前面 本系列文章基于5.7.12 版本讲述MySQL的新特性,从安装,文件结构,SQL ,优化 ,运维层面 复制,等几个方面展开介绍5.7 的新特性和功能,同时也建议大家跟踪官方blog和 文档 ,以尽快知悉其新的变化。 本文着重介绍5.7版本的innodb 相关的新特性。看了文档,只能使用目不暇接来形容5.7带来的新变化,废话不多说,进入正题--Innodb 功能增强。 3.1 动态修改varchar 长度大小。 可以可以通过ALTER TABLE 语句以in place方式修改varchar的大小且无需table-copy。但存在限制:表示varchar 长度的字节数不能变化(如果变更前使用1个字节表示长度,变更后也必须使用1个字节表示),即只支持0~255内的或者255以上的范围变更(增大),如果字段的长度从254增到256时就不能使用in-place算法,必须使用copy算法,否侧报错.需要注意的是 减小 varchar(N)长度的大小必须使用copy类型,如: mysql> alter table yy change column name name varchar(256) ,algorithm=inplace; ERROR 1846 (0A000): ALGORITHM=INPLACE is not supported. Reason: Cannot change column type INPLACE. Try ALGORITHM=COPY. mysql> alter table yy change column name name varchar(255) ,algorithm=inplace; Query OK, 0 rows affected (0.05 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> alter table yy change column name name varchar(256) ,algorithm=copy; Query OK, 1 row affected (0.10 sec) Records: 1 Duplicates: 0 Warnings: 0 mysql> alter table yy change column name name varchar(100) ,algorithm=inplace; ERROR 1846 (0A000): ALGORITHM=INPLACE is not supported. Reason: Cannot change column type INPLACE. Try ALGORITHM=COPY. 3.2 临时表性能优化 MySQL 5.7对临时表做了极大的改动,提升性能。 通过优化 CREATE TABLE, DROP TABLE, TRUNCATE TABLE,和ALTER TABLE 语句的执行逻辑,提升临时表的性能。(这个是从官网翻译的,还没找到除了alter之外的其他资料说明详细的优化过程) InnoDB临时表元数据不再存储于InnoDB系统表而是存储在INNODB_TEMP_TABLE_INFO,包含所有用户和系统创建的临时表信息。该表在第一次在其上运行select时被创建。 mysql> desc information_schema.INNODB_TEMP_TABLE_INFO; +----------------------+---------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +----------------------+---------------------+------+-----+---------+-------+ | TABLE_ID | bigint(21) unsigned | NO | | 0 | | | NAME | varchar(202) | YES | | NULL | | | N_COLS | int(11) unsigned | NO | | 0 | | | SPACE | int(11) unsigned | NO | | 0 | | | PER_TABLE_TABLESPACE | varchar(64) | YES | | NULL | | | IS_COMPRESSED | varchar(64) | YES | | NULL | | +----------------------+---------------------+------+-----+---------+-------+ 6 rows in set (0.00 sec) 3.3 新增临时表空间 为所有 非压缩的innodb临时表提供一个独立的表空间,默认的临时表空间文件为ibtmp1,位于数据目录。我们可通过innodb_temp_data_file_path参数指定临时表空间的路径和大小。 mysql> show global variables like 'innodb_temp_data_file_path'; +----------------------------+-----------------------+ | Variable_name | Value | +----------------------------+-----------------------+ | innodb_temp_data_file_path | ibtmp1:12M:autoextend | +----------------------------+-----------------------+ 1 row in set (0.00 sec) MySQL每次重新启动时,会重新创建临时表空间。注意 从5.7.5开始,新增一个系统选项 internal_tmp_disk_storage_engine 可定义磁盘临时表的引擎类型为 InnoDB,而在这以前,只能使用 MyISAM。而在5.6.3以后新增的参数default_tmp_storage_engine是控制create temporary table创建的临时表的存储引擎,在以前默认是MEMORY,不要把这二者混淆了。 mysql> show global variables like '%storage_engine%'; +----------------------------------+--------+ | Variable_name | Value | +----------------------------------+--------+ | default_storage_engine | InnoDB | | default_tmp_storage_engine | InnoDB | | disabled_storage_engines | | | internal_tmp_disk_storage_engine | InnoDB | +----------------------------------+--------+ 3.4 引入新的"non-redo" undo log 详见《innodb-temporary-table-undo-logs》 从MySQL 5.7.2 针对临时表及相关对象引入新的"non-redo" undo log,存放于临时表空间。该类型的undo log非 redolog, 因为临时表在数据库崩溃后不需要恢复,也就无需redo logs,避免了写relog相关的io,从而提高了性能。必须指出出操作临时表需要 undo log用于MySQL运行时的回滚、MVCC等。3.5 支持新的DATA_GEOMETRY空间类型的数据 InnoDB现在支持MySQL-supported空间数据类型。也即,之前的空间数据是以binary BLOB数据存储的,现在空间数据类型被映射到了一个InnoDB内部数据类型DATA_GEOMETRY.3.6 升级innochecksum innochecksum--离线的InnoDB文件校验工具,新增新的选择项或扩展的功能,如可指定特定的校验算法、可以只重写校验值而不进行验证、可指定允许的校验和不匹配量、显示各类页的个数、导出页类型信息、输出至日志、从标准输入读取数据等。从 5.7.2 起可支持校验超过2G的文件。详细的用法参考《innochecksum 官方文档》3.7 online DDL语句重建普通表和分区表 OPTIMIZE TABLE、ALTER TABLE … FORCE、ALTER TABLE … ENGINE=INNODB等操作支持支持使用inplace算法。减少了重建时间和对应用的影响。3.8 针对Fusion-io NVM 文件系统的优化 Linux系统中Fusion-io Non-Volatile Memory (NVM)文件系统提供了原子写能力,使InnoDB的doublewrite变得冗余。因此,MySQL5.7.4以后,如果Fusion-io设备支持原子写, MySQL系统会自动关闭doublewrite,减少IO,提升性能。小结 其实关于innodb的新特性和新功能 一篇远远不够,下一篇继续介绍关于 5.7 innodb 相关的知识。