MySQL数据库的事务隔离和MVCC

前言事务是访问数据库的一个操作序列,数据库应用系统通过事务集来完成对数据库的存取.1. 什么是事务?事务必须服从ISO/IEC所制定的ACID原则。ACID是原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性(durability)的缩写,这四种状态的意思是:原子性(Atomicity) 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。一致性(Consistency) 一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态.隔离性(Isolation) 在事务正确提交之前,不允许把事务对该数据的改变提供给任何其他事务,即在事务正确提交之前,它可能的结果不应该显示给其他事务.持久性(Durability) 持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。2. 事务的作用当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性.3. 遇到的并发问题第一类丢失更新:A事务撤销时,把已经提交的B事务的更新数据覆盖了.第二类丢失更新:A事务覆盖B事务已经提交的数据,造成B事务所做操作丢失.脏读:A事务读取了事务B中未提交的数据.不可重复读:A事务多次读取的值不同,因为该值被B事务修改并提交了.幻读:A事务两次读之间,B事务插入了数据.4. 如何解决上面的问题呢?为了解决上面的问题,开发者为MySQL数据库设计了以下四种事务隔离级别:Read Uncommitted(未提交读):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据.Read Committed(提交读):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读).Repeated Read(可重复读):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读,但是innoDB解决了幻读.Serializable(串行读):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞.隔离级别脏读不可重复度不幻读Read Uncommitted(未提交读)可能可能可能Read Committed(提交读)不可能可能可能Repeated Read(可重复读)不可能不可能可能Serializable(串行读)不可能不可能不可能5. 小尝试查看全局或会话的事务隔离级别SELECT @@global.tx_isolation, @@tx_isolation;修改全局或会话的事务隔离级别SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]6. MySQL默认Repeated Read隔离级别,按道理并不能解决幻读问题呀?以下将先介绍数据库所涉及的锁.7. 锁的基本叙述锁简介数据库中的锁是指一种软件机制,用来控制防止某个用户(进程会话)在已经占用了某种数据资源时,其他用户做出影响本用户数据操作或导致数据非完整性和非一致性问题发生的手段。锁的级别按照锁级别划分,锁可分为共享锁、排他锁。共享锁(读锁)针对同一块数据,多个读操作可以同时进行而不会互相影响。共享锁只针对UPDATE时候加锁,在未对UPDATE操作提交之前,其他事务只能够获取最新的记录但不能够UPDATE操作。排他锁(写锁)当前写操作没有完成前,阻断其他写锁和读锁。锁的粒度按锁的粒度划分,锁可分为表级锁、行级锁、页级锁。行级锁开销大,加锁慢,会出现死锁,锁定力度最小,发生锁冲突的概率最低,并发度高。表级锁开销小,加锁快,不会出现死锁,锁定力度大,发生冲突所的概率高,并发度低。页面锁开销和加锁时间介于表锁和行锁之间,会出现死锁,锁定力度介于表和行行级锁之间,并发度一般。8. 悲观锁和乐观锁8.1 悲观锁基本思想:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁.所以不管冲突是否真的发生,都会使用锁机制。悲观锁功能:锁住读取的记录,防止其它事务读取和更新这些记录。其它事务会一直阻塞,直到这个事务结束。悲观锁是在使用了数据库的事务隔离功能的基础上,独享占用的资源,以此保证读取数据一致性,避免修改丢失。悲观锁可以使用Repeatable Read事务,它完全满足悲观锁的要求。8.2 乐观锁基本思想:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量.解释:乐观锁是一种思想,乐观锁不会锁住任何东西,也就是说,它不依赖数据库的事务机制,乐观锁完全是应用系统层面的东西。所以它不是一种锁机制.如果使用乐观锁,那么数据库就必须加版本字段,否则就只能比较所有字段,但因为浮点类型不能比较,所以实际上没有版本字段是不可行的8.3 版本号机制一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。8.4 CAS算法核心思想:Compare and Swap,即比较再交换。过程:假设有A线程准备去修改内存中变量名为name的值,因此A线程会用以前自己读到的name变量值和此刻name的值做对比,如果一样,则表明在变量值没被修改过,因此可以更新修改,否则更新失败.9. 回到MySQL的重复读(Repeated Read)事务隔离级别前面说过,MySQL默认实现了可重复读的事务隔离级别,但是不能解决幻读的问题,然而在MySQL数据库使用可重复读的事务隔离条件下,并未发生幻读.MySQL使用MVCC(多版本并发控制)进行了控制.9.1名词简析:MVCC:是multiversion concurrency control的简称,也就是多版本并发控制,是个很基本的概念。MVCC的作用是让事务在并行发生时,在一定隔离级别前提下,可以保证在某个事务中能实现一致性读,也就是该事务启动时根据某个条件读取到的数据,直到事务结束时,再次执行相同条件,还是读到同一份数据,不会发生变化(不会看到被其他并行事务修改的数据)。read view:InnoDB MVCC使用的内部快照的意思。在不同的隔离级别下,事务启动时(有些情况下,可能是SQL语句开始时)看到的数据快照版本可能也不同。在上面介绍的几个隔离级别下会用到 read view。快照读: 就是所谓的根据read view去获取信息和数据,不会加任何的锁。当前读:前读会获取得到所有已经提交数据,按照逻辑上来讲的话,在一个事务中第一次当前读和第二次当前读的中间有新的事务进行DML操作,这个时候俩次当前读的结果应该是不一致的,但是实际的情况却是在当前读的这个事务还没提交之前,所有针对当前读的数据修改和插入都会被阻塞,主要是因为next-key lock解决了当前读可能会发生幻读的情况。next-key lock当使用主键索引进行当前读的时候,会降级为record lock(行锁)9.2 Read view详析InnoDB支持MVCC多版本控制,其中READ COMMITTED和REPEATABLE READ隔离级别是利用consistent read view(一致读视图)方式支持的。所谓的consistent read view就是在某一时刻给事务系统trx_sys打snapshot(快照),把当时的trx_sys状态(包括活跃读写事务数组)记下来,之后的所有读操作根据其事务ID(即trx_id)与snapshot中trx_sys的状态做比较,以此判断read view对事务的可见性。REPEATABLE READ隔离级别(除了GAP锁之外)和READ COMMITTED隔离级别的差别是创建snapshot时机不同。REPEATABLE READ隔离级别是在事务开始时刻,确切的说是第一个读操作创建read view的时候,READ COMMITTED隔离级别是在语句开始时刻创建read view的。这就意味着REPEATABLE READ隔离级别下面一个事务的SELECT操作只会获取一个read view,但是READ COMMITTED隔离级别下一个事务是可以获取多个read view的。创建/关闭read view需要持有trx_sys->mutex,会降低系统性能,5.7版本对此进行优化,在事务提交时session会cache只读事务的read view。9.3 read view 判断当前版本数据项是否可见在InnoDB中,创建一个新事务的时候,InnoDB会将当前系统中的活跃事务列表(trx_sys->trx_list)创建一个副本(read view),副本中保存的是系统当前不应该被本事务看到的其他事务id列表。当用户在这个事务中要读取该行记录的时候,InnoDB会将该行当前的版本号与该read view进行比较。具体的算法如下:设该行的当前事务id为trx_id,read view中最早的事务id为trx_id_min, 最迟的事务id为trx_id_max。如果trx_id< trx_id_min的话,那么表明该行记录所在的事务已经在本次新事务创建之前就提交了,所以该行记录的当前值是可见的。如果trx_id>trx_id_max的话,那么表明该行记录所在的事务在本次新事务创建之后才开启,所以该行记录的当前值不可见。如果trx_id_min <= trx_id <= trx_id_max, 那么表明该行记录所在事务在本次新事务创建的时候处于活动状态,从trx_id_min到trx_id_max进行遍历,如果trx_id等于他们之中的某个事务id的话,那么不可见,如图:从该行记录的DB_ROLL_PTR指针所指向的回滚段中取出最新的undo-log的版本号的数据,将该可见行的值返回。需要注意的是,新建事务(当前事务)与正在内存中commit 的事务不在活跃事务链表中。在具体多版本控制中我们先来看下源码:函数:read_view_sees_trx_id。read_view中保存了当前全局的事务的范围:【low_limit_id, up_limit_id】1. 当行记录的事务ID小于当前系统的最小活动id,就是可见的。 if (trx_id < view->up_limit_id) { return(TRUE); }2. 当行记录的事务ID大于当前系统的最大活动id(也就是尚未分配的下一个事务的id),就是不可见的。 if (trx_id >= view->low_limit_id) { return(FALSE); }3. 当行记录的事务ID在活动范围之中时,判断是否在活动链表中,如果在就不可见,如果不在就是可见的。 for (i = 0; i < n_ids; i++) { trx_id_t view_trx_id = read_view_get_nth_trx_id(view, n_ids - i - 1); if (trx_id <= view_trx_id) { return(trx_id != view_trx_id); } }Read view 图解:结语笔者水平有限,文中如有不妥,请大家多多指教,MySQL数据库事务机制还有很多需要深入研究的,我们仍需不断钻研。参考引用MySQL源码分析 ...

March 26, 2019 · 1 min · jiezi

MySQL编码utf8升级utf8mb4

上篇文章我们介绍了utf8和utf8mb4的区别,这篇文章我们主要介绍utf8升级utf8mb4的步骤utf8升级utf8mb4具体步骤:首先将我们数据库默认字符集由utf8更改为utf8mb4,然后将表默认字符集也更改为utf8mb4,最后再把存储表情的字段默认字符集也做相应的调整。SQL 语句# 修改数据库> ALTER DATABASE database_name CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;# 修改表> ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;# 修改表字段> ALTER TABLE table_name CHANGE column_name column_name VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;修改MySQL配置文件,新增如下参数:default-character-set = utf8mb4default-character-set = utf8mb4character-set-client-handshake = FALSEcharacter-set-server = utf8mb4collation-server = utf8mb4_unicode_ciinit_connect=‘SET NAMES utf8mb4’解释:– character_set_server:默认的内部操作字符集– character_set_client:客户端来源数据使用的字符集– character_set_connection:连接层字符集– character_set_results:查询结果字符集– character_set_database:当前选中数据库的默认字符集– character_set_system:系统元数据(字段名等)字符集– 还有以collation_开头的同上面对应的变量,用来描述字符序。检查环境变量 和测试 SQL 如下:SHOW VARIABLES WHERE Variable_name LIKE ‘character_set_%’ OR Variable_name LIKE ‘collation%’;注意:MySQL版本必须为5.5.3以上版本,否则不支持字符集utf8mb4建议:建议普通表使用utf8, 如果这个表需要支持emoji就使用utf8mb4新建mysql库或者表的时候还有一个排序规则utf8_unicode_ci比较准确,utf8_general_ci速度比较快。通常情况下 utf8_general_ci的准确性就够我们用的了,在我看过很多程序源码后,发现它们大多数也用的是utf8_general_ci,所以新建数据 库时一般选用utf8_general_ci就可以了如果是utf8mb4那么对应的就是 utf8mb4_general_ci utf8mb4_unicode_ciutf8_unicode_ci与utf8_general_ci的区别当前,utf8_unicode_ci校对规则仅部分支持Unicode校对规则算法。一些字符还是不能支持。并且,不能完全支持组合的记号。这主要影响越南和俄罗斯的一些少数民族语言,如:Udmurt 、Tatar、Bashkir和Mari。utf8_unicode_ci的最主要的特色是支持扩展,即当把一个字母看作与其它字母组合相等时。例如,在德语和一些其它语言中‘ß’等于‘ss’utf8_general_ci是一个遗留的校对规则,不支持扩展。它仅能够在字符之间进行逐个比较。这意味着utf8_general_ci校对规则进行的比较速度很快,但是与使用utf8_unicode_ci的校对规则相比,比较正确性较差例如,使用utf8_general_ci和utf8_unicode_ci两种 校对规则下面的比较相等:Ä = AÖ = OÜ = U两种校对规则之间的区别是,对于utf8_general_ci下面的等式成立:ß = s但是,对于utf8_unicode_ci下面等式成立:ß = ss对于一种语言仅当使用utf8_unicode_ci排序做的不好时,才执行与具体语言相关的utf8字符集 校对规则。例如,对于德语和法语,utf8_unicode_ci工作的很好,因此不再需要为这两种语言创建特殊的utf8校对规则。utf8_general_ci也适用与德语和法语,除了‘ß’等于‘s’,而不是‘ss’之外。如果你的应用能够接受这些,那么应该使用utf8_general_ci,因为它速度快。否则,使用utf8_unicode_ci,因为它比较准确。案例sql语句CREATE TABLE test_session ( sessionId varchar(255) NOT NULL, userId int(10) unsigned DEFAULT NULL, createAt datetime DEFAULT NULL)执行上面的代码会报一个错误:Specified key was too long; max key length is 767 bytes当使用utf8mb4编码后,主键id的长度设置255,太长,只能设置小于191的报错原因:utf8编码下,255长度的varchar长度约767,更改成utf8mb4后,最大只能支持191长度max key length is 767 bytesutf8: 767/3=255.6666666666667utf8mb4: 767/4=191.75欢迎订阅「K叔区块链」 - 专注于区块链技术学习 博客地址:http://www.jouypub.com简书主页:https://www.jianshu.com/u/756c9c8ae984segmentfault主页:https://segmentfault.com/blog/jouypub腾讯云主页:https://cloud.tencent.com/developer/column/72548 ...

March 25, 2019 · 1 min · jiezi

B树和哈希索引的比较

前言了解B树和哈希数据结构有助于预测查询在这些使用不同索引数据结构的存储引擎上的执行情况,特别是对于MEMORY存储引擎,它是允许您选择B树或哈希作为索引的存储引擎。1. B树指数特征B树索引可以在使用表达式中使用的对列的比较 =, >, >=, <, <=,或BETWEEN关键字。如果使用LIKE 或to LIKE且是一个不以通配符开头的常量字符串,则索引也可用于比较 。1.例如,以下SELECT语句将使用索引:SELECT * FROM tbl_name WHERE key_col LIKE ‘Patrick%’;SELECT * FROM tbl_name WHERE key_col LIKE ‘Pat%_ck%’;在第一个语句中 ‘Patrick’ <= key_col < ‘Patricl’,在第二个语句中’Pat’ <= key_col < ‘Pau'2.以下SELECT语句不使用索引:SELECT * FROM tbl_name WHERE key_col LIKE ‘%Patrick%’;SELECT * FROM tbl_name WHERE key_col LIKE other_col;在第一个语句中,LIKE 值以通配符开头。在第二个语句中,该LIKE值不是常量。如果使用了像’%string%‘且长度超过三个字符的字符串查询,那么MySQL将使用Turbo Boyer-Moore算法初始化这个模型,用这个模型来匹配速度会更快.不跨越子句中的所有AND级别的 任何索引 WHERE不用于优化查询。换句话说,为了能够使用索引,必须在每个AND组中使用索引的前缀 。3.以下WHERE子句使用索引:WHERE index_part1=1 AND index_part2=2 AND other_column=3 /* index = 1 OR index = 2 /WHERE index=1 OR A=10 AND index=2 / optimized like “index_part1=‘hello’” /WHERE index_part1=‘hello’ AND index_part3=5 / Can use index on index1 but not on index2 or index3 /WHERE index1=1 AND index2=2 OR index1=3 AND index3=3;4.这些WHERE子句 不使用索引: / index_part1 is not used /WHERE index_part2=1 AND index_part3=2 / Index is not used in both parts of the WHERE clause /WHERE index=1 OR A=10 / No index spans all rows */WHERE index_part1=1 OR index_part2=10有时MySQL不使用索引,即使有索引也是如此。发生这种情况的一种原因是,优化器估计使用索引将需要MySQL访问表中非常大比例的行。(在这种情况下,表扫描可能会快得多,因为它需要的搜索次数较少。)但是,如果这样的查询:例如LIMIT只用于检索某些行,那么MySQL无论如何都会使用索引,因为它可以更快地找到在结果中返回几行。2. 哈希指数特征散列索引与刚才讨论的特征有些不同:它们仅用于使用=或<=>(文章结尾有此符号说明) 运算符的相等比较 (但速度非常快)。它们不用于比较运算符,例如 <找到一系列值。依赖于这种类型的单值查找的系统被称为“ 键值存储 ” ; 要将MySQL用于键值查找类,请尽可能使用哈希索引。优化器无法使用哈希索引来加速 ORDER BY操作。(此类索引不能用于按顺序搜索下一个条目。)MySQL无法确定两个值之间大约有多少行(范围优化器使用它来决定使用哪个索引)。如果将 MyISAM或 InnoDB表更改为哈希索引 MEMORY表,则可能会影响某些查询。只有整个键可用于搜索行。(使用B树索引,键的任何最左边的前缀都可用于查找行。)附录解释=和<=>的区别:相同点: 像常规的=运算符一样,两个值进行比较,结果是0(不等于)或1(相等),换句话说:’A’<=>’B’得0和’a’<=>’a‘得1,都是值的比较。不同点: NULL的值是没有任何意义的。所以=号运算符不能把NULL作为有效的结果。所以:请使用<=>,‘a’ <=> NULL 得0 NULL<=> NULL 得出 1。和=运算符正相反,=号运算符规则是 ‘a’=NULL 结果是NULL 甚至NULL = NULL 结果也是NULL。顺便说一句,mysql上几乎所有的操作符和函数都是这样工作的,因为和NULL比较基本上都没有意义。用处当两个操作数中可能含有NULL时,你需要一个一致的语句,此时就可以用<=>. ...

March 25, 2019 · 1 min · jiezi

Xtrabackup数据库备份

wget -O /etc/yum.repos.d /epel.repo http://mirrors.aliyun.com/repo/epel-6.repo #配置epel源yum -y install perl perl-devel libaio libaio-devel perl-Time-HiRes perl-DBD-MySQL #安装Xtrabackup软件需要的基础环境包wget https://www.percona.com/downloads/XtraBackup/PerconaXtraBackup-2.4.4/binary/redhat/6/x86_64/percona-xtrabackup-24-2.4.4-1.el6.x86_64.rpmls -l percona-xtrabackup-24-2.4.4-1.el6.x86_64.rpmyum -y install percona-xtrabackup-24-2.4.4-1.el6.x86_64.rpmls -l which xtrabackup innobackupexmkdir /application/mysql/logs -pchown -R mysql.mysql /application/mysql/logsegrep -v “#|^$” /etc/my.cnf 向 /etc/my.cnf加入两行log_bin=/application/mysql/logs/bin-log #mysql5.6之前是log-bin之后是log_binexpire_logs_days=7Xtrabackup命令,专门用于对InnoDB和XtraDB等事务引擎的数据库热备份的工具,不能用于备份myIsam等其他类型的引擎数据,它的主要特点是备份数据时完全不用锁表。Innobackupex命令,将上述Xtrabackup命令使用perl脚本进行二次封装的工具,除了可以用于InnoDB和XtraDB等引擎之外,还可以备份MyISAM及多种引擎混合使用场景,它的主要特点是备份事务引擎数据而不用锁表,可以备份非事务引擎数据,但要锁表。mkdir /application/mysql/data #创建一个备份目录innobackupex –defaults-file=/etc/my.cnf –user=root –password=123456 –socket=/var/lib/mysql/mysql.sock –no-timestamp /application/mysql/data/full #全备最终结果如下:总用量 18468drwxr-x— 2 root root 12288 3月 20 14:33 3ideapc-rw-r—– 1 root root 417 3月 20 14:33 backup-my.cnf #配置文件备份-rw-r—– 1 root root 18874368 3月 20 14:32 ibdata1 #共享表空间备份drwxr-x— 2 root root 4096 3月 20 14:32 mysql drwxr-x— 2 root root 4096 3月 20 14:32 test-rw-r—– 1 root root 113 3月 20 14:33 xtrabackup_checkpoints #checkpoints信息-rw-r—– 1 root root 515 3月 20 14:33 xtrabackup_info #xtrabackup信息-rw-r—– 1 root root 2560 3月 20 14:33 xtrabackup_logfile #xtrabackup日志文件 ...

March 20, 2019 · 1 min · jiezi

Ubuntu16.04安装MySql8.0

一、通过APT方式安装【注意】通过APT方式安装的版本都是现在最新的版本。通过这种方式安装好之后开机自启动都已经配置好,和命令行上的环境变量,无需手动配置。1.1 下载官方提供的mysql-apt-config.deb包下载官方提供的mysql-apt-config.deb包进行APT源设置,下载地址:https://dev.mysql.com/downloa…1.2 安装deb包你下载的哪个包就安装哪个包sudo dpkg -i mysql-apt-config_0.8.12-1_all.deb运行之后会出现下面的界面进行选择一般每啥改的,默认就可以的。方向键选择OK就回车可以了。1.3 安装MySqlsudo apt-get install mysql-server中途可能会出现依赖不足的情况,如果出现就执行sudo apt-get install -f安装依赖后再进行安装。1.4 安装过程操作安装过程会提示输入数据库的登录名和密码,输入即可,如下所示:1.5 安装后的操作服务启动后端口查询sudo netstat -anp | grep mysql服务管理#启动sudo service mysql start#停止sudo service mysql stop#服务状态sudo service mysql status设置远程访问就把mysql.user中root账户的host改成%,然后重启MySql服务1.6 卸载MySql# 首先使用以下命令删除MySQL服务器sudo apt-get remove mysql-server# 然后,删除随MySQL服务器自动安装的任何其他软件sudo apt-get autoremove# 卸载其他组件sudo apt-get remove <<package-name>># 查看从MySQL APT存储库安装的软件包列表dpkg -l | grep mysql | grep ii

March 20, 2019 · 1 min · jiezi

DM 源码阅读系列文章(一)序

作者:杨非前言TiDB-DM 是由 PingCAP 开发的一体化数据同步任务管理平台,支持从 MySQL 或 MariaDB 到 TiDB 的全量数据迁移和增量数据同步,在 TiDB DevCon 2019 正式开源。作为一款连接 MySQL/MariaDB 生态和 TiDB 生态的中台类型产品,DM 获得了广泛的关注,很多公司、开发者和社区的伙伴已经在使用 DM 来进行数据迁移和管理。随着大家使用的广泛和深入,遇到了不少由于对 DM 原理不理解而错误使用的情况,也发现了一些 DM 支持并不完善的场景和很多可以改进的地方。在这样的背景下,我们希望开展 DM 源码阅读分享活动,通过对 DM 代码的分析和设计原理的解读,帮助大家理解 DM 的实现原理,和大家进行更深入的交流,也有助于我们和社区共同进行 DM 的设计、开发和测试。背景知识本系列文章会聚焦 DM 自身,读者需要有一些基本的知识,包括但不限于:Go 语言,DM 由 Go 语言实现,有一定的 Go 语言基础有助于快速理解代码。数据库基础知识,包括 MySQL、TiDB 的功能、配置和使用等;知道基本的 DDL、DML 语句和事务的基本常识;MySQL 数据备份、主从同步的原理等。基本的后端服务知识,比如后台服务进程管理、RPC 工作原理等。总体而言,读者需要有一定 MySQL/TiDB 的使用经验,了解 MySQL 数据备份和主从同步的原理,以及可以读懂 Go 语言程序。在阅读 DM 源码之前,可以先从阅读《TiDB-DM 架构设计与实现原理》入手,并且参考 使用文档 在本地搭建一个 DM 的测试环境,从基础原理和使用对 DM 有一个初步的认识,然后再进一步分析源码,深入理解代码的设计和实现。内容概要源码阅读系列将会从两条线进行展开,一条是围绕 DM 的系统架构和重要模块进行分析,另一条线围绕 DM 内部的同步机制展开分析。源码阅读不仅是对代码实现的分析,更重要的是深入的分析背后的设计思想,源码阅读和原理分析的覆盖范围包括但不限于以下列出的内容(因为目前 DM 仍处于快速迭代的阶段,会有新的功能和模块产生,部分模块在未来也会进行优化和重构,后续源码阅读的内容会随着 DM 的功能演进做适当的调整):整体架构介绍,包括 DM 有哪些模块,分别实现什么功能,模块之间交互的数据模型和 RPC 实现。DM-worker 内部组件设计原理(relay-unit, dump-unit, load-unit, sync-unit)和数据同步的并发模型设计与实现。基于 binlog 的数据同步模型设计和实现。relay log 的原理和实现。定制化数据同步功能的实现原理(包括库表路由,库表黑白名单,binlog event 过滤,列值转换)。DM 如何支持上游 online DDL 工具(pt-osc, gh-ost)的 DDL 同步场景。sharding DDL 处理的具体实现。checkpoint 的设计原理和实现,深入介绍 DM 如何在各类异常情况下保证上下游数据同步的一致性。DM 测试的架构和实现。代码简介DM 源代码完全托管在 GitHub 上,从 项目主页 可以看到所有信息,整个项目使用 Go 语言开发,按照功能划分了很多 package,下表列出了 DM 每个 package 的基本功能:PackageIntroductionchecker同步任务上下游数据库配置、权限前置检查模块cmd/dm-ctl, cmd/dm-master, cmd/dm-workerdmctl, DM-master, DM-worker 的 main 文件所在模块dm/config同步任务配置、子任务配置、前置检查配置定义模块dm/ctldmctl 所有 RPC 调用实现的模块dm/masterDM-master 的核心实现,包含了 DM-master 后台服务,对 dmctl 到 DM-master 的 RPC 调用的处理逻辑,对 DM-worker 的管理,对 sharding DDL 进行协调调度等功能dm/pb, dm/protodm/proto 定义了 DM-master 和 DM-worker 相关交互的 protobuf 协议,dm/pb 是对应的生成代码dm/unit定义了子任务执行的逻辑单元(包括 dump unit, load unit, sync unit, relay unit)接口,在每个不同逻辑单元对应的 package 内都有对应的 接口实现dm/workerDM-worker 的核心实现,实现 DM-worker 后台服务,管理维护每个任务的 relay 逻辑单元,管理、调度每个子任务的逻辑单元loader子任务 load 逻辑单元的实现,用于全量数据的导入mydumper子任务 dump 逻辑单元的实现,用于全量数据的导出pkg包含了一些基础功能的实现,例如 gtid 操作、SQL parser 封装、binlog 文件流读写封装等relay处理 relay log 同步的核心模块syncer子任务 sync 逻辑单元的实现,用于增量数据的同步对于理解代码最直接的手段就是从 DM-server, DM-worker 和 dmctl 三个 binary 对应的 main 文件入手,看 DM-worker, DM-master 是如何启动,DM-worker 如何管理一个上游实例和同步任务;如何从 dmctl 开始同步子任务;然后看一个同步子任务从全量状态,到增量同步状态,binlog 如何处理、sql 任务如何分发等。通过这样一个流程对 DM 的整体架构就会有全面的理解。进一步就可以针对每个使用细节去了解 DM 背后的设计逻辑和代码实现,可以从具体每个 package 入手,也可以从感兴趣的功能入手。实际上 DM 代码中使用了很多优秀的第三方开源代码,包括但不仅限于:借助 grpc 实现各组件之间的 RPC 通信借助 pingcap/parser 进行 DDL 的语法解析和语句还原借助 pingcap/tidb-tools 提供的工具实现复杂的数据同步定制借助 go-mysql 解析 MySQL/MariaDB binlog 等在源码阅读过程中对于比较重要的、与实现原理有很高相关度的第三方模块,我们会进行相应的扩展阅读。工具链工欲善其事,必先利其器,在阅读 DM 源码之前,我们先来介绍 DM 项目使用到的一些外部工具,这些工具通常用于 DM 的构建、部署、运行和测试,在逐步使用 DM,阅读代码、理解原理的过程中都会使用到这些工具。golang 工具链:构建 DM 需要 go >= 1.11.4,目前支持 Linux 和 MacOS 环境。gogoprotobuf:用于从 proto 描述文件生成 protobuf 代码,DM 代码仓库的 generate-dm.sh 文件封装了自动生成 DM 内部 protobuf 代码的脚本。Ansible:DM 封装了 DM-Ansible 脚本用于 DM 集群的自动化部署,部署流程可以参考 使用 ansible 部署 DM。pt-osc, gh-ost:用于上游 MySQL 进行 online-ddl 的同步场景。mydumper:DM 的全量数据 dump 阶段直接使用 mydumper 的 binary。MySQL, TiDB, sync_diff_inspector:这些主要用于单元测试和集成测试,可以参考 tests#preparations 这部分描述。小结本篇文章主要介绍了 DM 源码阅读的目的和源码阅读的规划,简单介绍了 DM 的源码结构和工具链。下一篇文章我们会从 DM 的整体架构入手,详细分析 DM-master、DM-worker 和 dmctl 三个组件服务逻辑的实现和功能抽象,RPC 数据模型和交互接口。更多的代码阅读内容会在后面的章节中逐步展开,敬请期待。 ...

March 20, 2019 · 2 min · jiezi

mysql blocked because of many connection errors

背景今天启动服务的时候,发现有一个服务一直启动不起来,报错如下:Caused by: java.sql.SQLException: null, message from server: “Host ‘192.168.0.10’ is blocked because of many connection errors; unblock with ‘mysqladmin flush-hosts’“分析这里的错误已经很明确了,就说某一个ip产生了大量的错误链接,然后这个IP就被锁了,如果要解除锁定,就用mysql自带的名命令’mysqladmin flush-hosts’解锁即可。解决方法一.通过提示,用命令’mysqladmin flush-hosts’。如果是远程机器,可以使用: mysqladmin flush-hosts -h 192.168.1.9 -P 3306 -u root -p方法二.进入mysql命令行,输入’flush hosts’也是可以的。如下图所示:最后别忘了:修改max_connection_errors参数,避免以后再出现这样的问题查看:show variables like ‘max_connect_errors’; 修改:set global max_connect_errors = 1000; 校验:show variables like ‘max_connect_errors’;

March 19, 2019 · 1 min · jiezi

MySQL中update修改数据与原数据相同会再次执行吗

背景本文主要测试MySQL执行update语句时,针对与原数据(即未修改)相同的update语句会在MySQL内部重新执行吗?测试环境MySQL5.7.25Centos 7.4binlog_format为ROW参数root@localhost : (none) 04:53:15> show variables like ‘binlog_row_image’;+——————+——-+| Variable_name | Value |+——————+——-+| binlog_row_image | FULL |+——————+——-+1 row in set (0.00 sec)root@localhost : (none) 04:53:49> show variables like ‘binlog_format’; +—————+——-+| Variable_name | Value |+—————+——-+| binlog_format | ROW |+—————+——-+1 row in set (0.00 sec)root@localhost : test 05:15:14> show variables like ’transaction_isolation’;+———————–+—————–+| Variable_name | Value |+———————–+—————–+| transaction_isolation | REPEATABLE-READ |+———————–+—————–+1 row in set (0.00 sec)测试步骤session1root@localhost : test 04:49:48> begin;Query OK, 0 rows affected (0.00 sec)root@localhost : test 04:49:52> select * from test where id =1;+—-+——+——+——+| id | sid | mid | name |+—-+——+——+——+| 1 | 999 | 871 | NW |+—-+——+——+——+1 row in set (0.00 sec)root@localhost : (none) 04:54:03> show engine innodb status\Gshow master status\G…—LOG—Log sequence number 12090390Log flushed up to 12090390Pages flushed up to 12090390Last checkpoint at 120903810 pending log flushes, 0 pending chkp writes33 log i/o’s done, 0.00 log i/o’s/second*************************** 1. row *************************** File: mysql-bin.000001 Position: 154 Binlog_Do_DB: Binlog_Ignore_DB: Executed_Gtid_Set: 1 row in set (0.00 sec)session2root@localhost : test 04:47:45> update test set sid=55 where id =1;Query OK, 1 row affected (0.01 sec)Rows matched: 1 Changed: 1 Warnings: 0root@localhost : (none) 04:54:03> show engine innodb status\Gshow master status\G…—LOG—Log sequence number 12091486Log flushed up to 12091486Pages flushed up to 12091486Last checkpoint at 120914770 pending log flushes, 0 pending chkp writes39 log i/o’s done, 0.00 log i/o’s/second*************************** 1. row *************************** File: mysql-bin.000001 Position: 500 Binlog_Do_DB: Binlog_Ignore_DB: Executed_Gtid_Set: 8392d215-4928-11e9-a751-0242ac110002:11 row in set (0.00 sec)session1root@localhost : test 04:49:57> update test set sid=55 where id =1; Query OK, 0 rows affected (0.00 sec)Rows matched: 1 Changed: 0 Warnings: 0root@localhost : (none) 04:54:03> show engine innodb status\Gshow master status\G…—LOG—Log sequence number 12091486Log flushed up to 12091486Pages flushed up to 12091486Last checkpoint at 120914770 pending log flushes, 0 pending chkp writes39 log i/o’s done, 0.00 log i/o’s/second*************************** 1. row *************************** File: mysql-bin.000001 Position: 500 Binlog_Do_DB: Binlog_Ignore_DB: Executed_Gtid_Set: 8392d215-4928-11e9-a751-0242ac110002:11 row in set (0.00 sec)root@localhost : test 04:52:05> select * from test where id =1;+—-+——+——+——+| id | sid | mid | name |+—-+——+——+——+| 1 | 999 | 871 | NW |+—-+——+——+——+1 row in set (0.00 sec)root@localhost : test 04:52:42> commit;Query OK, 0 rows affected (0.00 sec)root@localhost : test 04:52:52> select * from test where id =1;+—-+——+——+——+| id | sid | mid | name |+—-+——+——+——+| 1 | 55 | 871 | NW |+—-+——+——+——+1 row in set (0.00 sec)总结在binlog_format=row和binlog_row_image=FULL时,由于MySQL 需要在 binlog 里面记录所有的字段,所以在读数据的时候就会把所有数据都读出来,那么重复数据的update不会执行。即MySQL 调用了 InnoDB 引擎提供的“修改为 (1,55)”这个接口,但是引擎发现值与原来相同,不更新,直接返回binlog_format为STATEMENT参数root@localhost : (none) 04:53:15> show variables like ‘binlog_row_image’;+——————+——-+| Variable_name | Value |+——————+——-+| binlog_row_image | FULL |+——————+——-+1 row in set (0.00 sec)root@localhost : (none) 05:16:08> show variables like ‘binlog_format’;+—————+———–+| Variable_name | Value |+—————+———–+| binlog_format | STATEMENT |+—————+———–+1 row in set (0.00 sec)root@localhost : test 05:15:14> show variables like ’transaction_isolation’;+———————–+—————–+| Variable_name | Value |+———————–+—————–+| transaction_isolation | REPEATABLE-READ |+———————–+—————–+1 row in set (0.00 sec)测试步骤session1root@localhost : test 05:16:42> begin;Query OK, 0 rows affected (0.00 sec)root@localhost : test 05:16:44> select * from test where id =1;+—-+——+——+——+| id | sid | mid | name |+—-+——+——+——+| 1 | 111 | 871 | NW |+—-+——+——+——+1 row in set (0.00 sec)root@localhost : (none) 05:16:51> show engine innodb status\Gshow master status\G…—LOG—Log sequence number 12092582Log flushed up to 12092582Pages flushed up to 12092582Last checkpoint at 120925730 pending log flushes, 0 pending chkp writes45 log i/o’s done, 0.00 log i/o’s/second*************************** 1. row *************************** File: mysql-bin.000001 Position: 154 Binlog_Do_DB: Binlog_Ignore_DB: Executed_Gtid_Set: 1 row in set (0.00 sec)session2root@localhost : test 05:18:30> update test set sid=999 where id =1;Query OK, 1 row affected (0.00 sec)Rows matched: 1 Changed: 1 Warnings: 0root@localhost : (none) 05:18:47> show engine innodb status\Gshow master status\G…—LOG—Log sequence number 12093678Log flushed up to 12093678Pages flushed up to 12093678Last checkpoint at 120936690 pending log flushes, 0 pending chkp writes51 log i/o’s done, 0.14 log i/o’s/second*************************** 1. row *************************** File: mysql-bin.000001 Position: 438 Binlog_Do_DB: Binlog_Ignore_DB: Executed_Gtid_Set: 8392d215-4928-11e9-a751-0242ac110002:11 row in set (0.00 sec)session1root@localhost : test 05:16:47> update test set sid=999 where id =1;Query OK, 0 rows affected (0.00 sec)Rows matched: 1 Changed: 0 Warnings: 0root@localhost : (none) 05:20:03> show engine innodb status\Gshow master status\G…—LOG—Log sequence number 12094504Log flushed up to 12094504Pages flushed up to 12094504Last checkpoint at 120944950 pending log flushes, 0 pending chkp writes56 log i/o’s done, 0.00 log i/o’s/second*************************** 1. row *************************** File: mysql-bin.000001 Position: 438 Binlog_Do_DB: Binlog_Ignore_DB: Executed_Gtid_Set: 8392d215-4928-11e9-a751-0242ac110002:11 row in set (0.00 sec)root@localhost : test 05:19:33> select * from test where id =1; +—-+——+——+——+| id | sid | mid | name |+—-+——+——+——+| 1 | 999 | 871 | NW |+—-+——+——+——+1 row in set (0.00 sec)root@localhost : test 05:20:44> commit;Query OK, 0 rows affected (0.01 sec)root@localhost : test 05:20:57> select * from test where id =1;+—-+——+——+——+| id | sid | mid | name |+—-+——+——+——+| 1 | 999 | 871 | NW |+—-+——+——+——+1 row in set (0.00 sec)总结在binlog_format=statement和binlog_row_image=FULL时,InnoDB内部认真执行了update语句,即“把这个值修改成 (1,999)“这个操作,该加锁的加锁,该更新的更新。一站式开发者服务,海量学习资源0元起!阿里热门开源项目、机器学习干货、开发者课程/工具、小微项目、移动研发等海量资源;更有开发者福利Kindle、技术图书幸运抽奖,100%中–》https://www.aliyun.com/acts/product-section-2019/developer?utm_content=g_1000047140本文作者:powdba阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 19, 2019 · 4 min · jiezi

MySql通过查询结果集更新数据

表结构 现在有用户表和用户实名认证表,user_info,user_card。 user_info中的字段有user_id,user_name 。 user_card中的字段有user_id,user_card,user_name 。 其中user_name为用户实名认证的信息,user_info中做了字段冗余。 问题 用户表user_info中的user_name和user_card中的user_name不同步。 user_card中有值,user_info中没有值。 需要将user_card中的user_name同步到user_info中去。 解决方法1.通过代码查询出user_info中 user_name 为空的数据 ,然后通过user_id查询出用户实名认证的数据进行同步 。 select user_id from user_info where user_name = ’’ ; select * from user_card where user_id in (上面的结果集) ; 通过代码更新数据 2.联表查询后更新数据 SELECT c.user_id , c.user_name FROM user_info AS u LEFT JOIN user_card AS c ON u.user_id = c.user_id WHERE u.user_name = ‘’; 通过代码更新数据3.通过MySql内联更新数据 先写出更新语句 UPDATE user_info as u SET u.user_name = ‘结果集’ ; 再获取条件结果集 SELECT c.user_id , c.user_name FROM user_info AS u LEFT JOIN user_card AS c ON u.user_id = c.user_id WHERE u.user_name = ‘’; 最后内联更新 UPDATE user_info as u INNER JOIN ( SELECT c.user_id , c.user_name FROM user_info AS u LEFT JOIN user_card AS c ON u.user_id = c.user_id WHERE u.user_name = ‘’; ) as r ON u.user_id = r.user_id SET u.user_name = r.user_name ; ...

March 19, 2019 · 1 min · jiezi

现在最不缺的应该就是码农了,缺的是技术过硬又精通业务的工程师

昨天,一位分析界的老前辈对我很无奈地摇摇头,“这帮程序员,不食人间烟火哪!”我也深有感触,全世界的码农都一个鸟样。这让我想起了,同样也是他,在多年之前,对我提了警醒——要重视业务。从那之后,我一直狂奔在技术+业务的双修道路上。放在以前,码农这个族群一定是稀罕动物。但在今天,这个世界最不缺的应该就是码农了,未来最廉价的也将是码农。仅有泛泛一技,在未来并不吃香,因为那是要被机器人所取代的。这个世界,缺的是技术过硬又精通业务的工程师,缺的是真正能解决实际业务问题的人,缺的是复合型的人才。码农不是工程师,码农只是会写代码,只会明确需求和逻辑的情况下写代码。工程师则不一样,懂得用技术怎么解决实际业务问题,用技术驱动业务的发展。什么叫业务?先来明确这个问题。业务是一个很实在的东西,看得见感受得到,接地气儿。业务就是我们所能理解和感受的世界,就是这个世界或者某个行业的运转逻辑、流程与现状,是结果表象,是能够被看见和感受的,也是内在本质,是能够被洞察和感知的。业务就是这个世界发生了什么,什么时候,谁参与,怎么发生,结果如何。业务就是什么时候,谁在哪里,买了什么东西,花了多少钱,用什么支付。业务就是这个行业怎么发展起来的,现状如何,未来趋势如何,用了什么技术,有什么企业,商业模式如何,盈利能力如何,目前主要面临什么问题,消费者有什么特点,等等。世界很复杂,单个细分行业的业务也很复杂。为什么要了解业务?谈到这个,码农们一定有所不悦,“熟悉业务是需求分析师做的事,跟我们没有关系。”打个不恰当的比喻。有10个人经过一栋写字楼,突然从楼上掉下来几块砖头,砸中了9个人,其中就有7个码农,3个硕士,1个博士(原谅我又犯职业病,拿数据说话了)。而没被砸到的那个人,恰好因为了解到之前经常发生这样的事而绕道行走。如果你只会写代码,你不是不可替代的,而是可有可无的。因为这年头,会JAVA、C、Python的程序员,在大街上一抓一大把。现在已经开始提倡,编程从娃娃抓起了。10后都开始跟你抢饭碗了,你怕不怕?但话也不是那么极端,除非你的技术很牛逼,在国内或者某个行业内能够排上号的。但技术牛逼的人,也不是只是技术超群,还常常因为能够利用手中的技术解决某方面的业务问题,做了哪些突出的贡献。我们出来混,也是要拿成果说话的,做过什么项目,有什么价值。这种价值往往就是针对业务而说的。IT研发与业务需求方常常是一对冤家,常常因为一个业务功能实现争辩得耳红面赤。研发觉得这个功能很low,没什么技术含量,业务方却认为这个功能却很有用,需要花功夫做细做深做好。现实情况是,功能做出来了,却很难用,或者经常用不了,或者数据不对。研发想做点高大上的功能,业务方却认为太虚了,没什么用。(IT与业务方那点事就不多说了,大家都心知肚明~~)多年经验反复告诫我,鉴定一个功能是不是好功能,唯一的标准是看它能否支撑业务、改善业务、推动业务,也即应用效果。一个产品,只要有30%的功能,让业务用户用起来很爽,感觉帮助很大,就已经是一个不错的产品了。我们都认同,技术驱动业务。但我们不一定明白,正是由于业务的某些强烈需求,才推动技术的发展与落地。说这些,我是想说,作为技术人员,我们既要仰望星空,也要脚踏实地,既要追逐腾飞的技术,也要重视落地的业务。如果一个业务人员很懂技术,那将很可能是技术人员的灾难。因为那样的话,业务人员会很强势,又或者那样就没有技术人员什么事了。当然,也不难想象,一个真正懂看数据的测试人员,就好比一个真正懂用算法的业务人员一样难得。业务与数据的关系真实(而不是杜撰、模拟、伪造)、可量化、可被记录的数据一定会反映真实世界某方面的业务情形。而现实当中很多业务场景都可由数据体现出来。零售是业务场景最繁多且最贴近我们生活的行业,可以从中找到很多方便理解的例子。当你在一个酷热难耐的夏天上午10点,走进位于公司附近的全家便利店,使用微信支付,花了3.5元,买了一瓶无糖330ml摩登罐的可乐,而且刷会员卡攒了100积分,而收银员MM返回给了你一张POS单据,这时你所发生的这一切都已经通过收银记录在了全家的数据库里。更糟糕的是,店里的摄像头也已经把你在店里的一举一动录了下来了,转化成为一帧帧图像数据。这就是,业务数据化。店长通过数据分析发现,最近3.5元330ml摩登罐可乐的销量比上月增长了20%,而消费者中75%是20-35岁的男性,相比之下,300ml塑料瓶装的可乐销量却下滑40%。店长权衡比较了一下,300ml塑料瓶装可乐利润低,330ml摩登罐可乐目前更受年轻人欢迎,考虑到日渐增长的租金压力,做了一个大胆的决定——下架300ml塑料瓶装可乐,增加330ml摩登罐可乐的商品。(又拿数据说话了。)这就是,数据业务化。或者,数据驱动业务。当我开始接触一个行业时,我通常会花2-3周的时间去了解这个行业的业务,然后就大致清楚这个行业有什么样的数据,可以做哪方面的分析,解决什么问题。当遇到不好理解的分析结果时,我经常使用业务联想法,设身处地去体会结果所反映的业务场景是什么样的。如何了解业务?这个说大了,就是如何看这个世界。每个人有每个人的方法论,每个人有每个人的世界观,每个人有每个人的逻辑思维。我们都知道,观念的转变是最难的,也有很多不确定性。有些人可能因为自己的切身体会一天就改变了之前几十年根深蒂固的看法,有些人任由三姑六婆苦口婆心地劝说就是不肯改变自己的择偶观,却有可能因为自己年岁渐大不断降低自己的标准。但最好也及早要形成科学的思考方法,帮助正确地理解这个世界。以“面-线-点”的方式可以较为全面、系统、深入地了解一个行业,然后是某个垂直领域,最后再到具体业务场景。佛系文化的流行,使得年轻一代降低对这个世界的关注度,一切都无所谓,一切都漠不关心。这个世界从来没有变好过,但我们每个人都是这个世界的匆匆过客,都是行走在自己的人生路上不断领略这个世界的美与丑。这世间的风景,这世间的悲欢离合,如果我们积极地探索与领悟,也不枉来这世间走一遭。保持好奇心,可以驱动我们的思考,强化我们的认知,丰富我们的内在。这是我想说的第二个方面。怀有好奇心,就会渐渐地敏锐观察这个世界,多问自己一些为什么。我家附近原来有个沃尔玛超市,现在地产商将它装修一番,引入了不少餐厅,刚开张不久,我就去那里吃饭,吃的是烤鸭,一个多两个月后,再去那里吃饭,发现有一半的餐厅已经关门了。在去地铁站的那条路上,每天人流如梭,一点点,即使到了深夜,依然有很多人在门口排队买奶茶。然而,仅仅隔了一个店铺的喜茶,做不下去,关门了。两三个月前又换成粉店,路转粉。每天下班路过时,发现店里顾客不到10个,门可罗雀。为什么每家一点点奶茶店门口,不管是什么时候都是很多人,他们是托儿还是真的顾客?因为喜欢新鲜,不喜欢在冰箱里存太多菜,且附近没有菜市场,所以常去买菜的还是附近的钱大妈。但我却没怎么去更近的一家生活超市,店面比较大,除果肉蔬菜外,也卖油盐酱醋,还有生活用品,但奇怪的是顾客却不到钱大妈的1/10。为什么几乎所有潮州牛肉店都很多人,有很多甚至在门口排了很长的队?观察到这些,常常会陷入思考,为什么会发生这些,新零售到底改变了什么?再举个例子。去年拿保温杯泡着枸杞的中年男火了。关于这个,我又问了自己几个问题:拿着保温杯泡着枸杞的是不是都是中年男?如果是,这个特征能否被数据量化?可否考虑加入到算法模型当中,加以应用起来?虽然很多问题,我没有找到答案,但多问自己问题,会引发自己不断深入思考,不断激发自己好奇心,不断去研究。很多业务知识都是零散的,不可能在短时间内完全了解,可以在日常不断积累。关于日常积累业务知识,可以经常询问懂业务的人。这是我想说的第三个方面。刚进公司的时候,我以为业务很简单。很快,我就发现里面的坑不少。加上所在团队的成员也是刚入职不久的,问问题没处可问。过了一个月之后,我发现隔壁团队有两个十年左右的老员工,业务很熟,而且人特好。于是,我几乎一遇到业务问题,就跑过去“骚扰”他们,他们也很乐意解答,如果他们不清楚,他们也会告诉我应该去找谁了解。大约半年之后,我基本摸透了顺丰的数据和业务情况。我也和那两位老员工建立了不错的友谊,即使后来换了部门,我也经常过去找他们。跟懂业务的人搞好关系,遇到业务问题,多咨询他们,这是最有效最接地气的办法。多看书,这是我想说的第四个方面。比如说,从事新零售领域方面的工作,总得先了解新零售是怎么回事。你可以去听专家们忽悠,但这样的机会很少,而且时间也有限,说不定成本还很高。读书则不一样。读书,意味着主动了解,主动去构建自己的知识体系。读书的重要性,这里不多言了。如果您读这篇文章的时候,您恰好也是一位数据人。我还想告诫一句:我们不能脱离业务去看数据,而是要时刻从业务角度去理解数据。我们不敢期望可以完全理解这个世界,但也憧憬着我们不单可以在代码的世界里畅快驰骋,论剑江湖,也可以放下身段洞察芸芸众生之百态,领悟人间世俗之真情。如果真的可以的话,就没有需求分析师什么事了。硬实力这里说的硬实力,也就是技术上的真实积累。怎么来体现你的技术实力?我总的分为:技术深度和技术广度这两方面。技术广度通俗的讲,就是你熟悉该技术点的使用以及基本原理。一般面试官在面试首轮会问很多技术点,来考核你是否能正确使用。准备不充分的面试,完全是浪费时间,更是对自己的不负责(如果title很高,当我没说)。今天给大家分享下在跳槽时需要准备的Java面试大纲,其中大部分都是面试过程中的面试题,可以对照这查漏补缺,当然了,这里所列的肯定不可能覆盖全部方式。软实力软实力在面试过程中也尤为重要(有时候真的要更重要),主要是指和面试官的沟通,对一个问题的阐述方式和表达方式,逻辑思维能力等。面试过程全程微笑,项目描述需要严谨的表述,个人的优缺点基本要做到随口而出..等这些其实就是软实力的体现。知己知彼、百战不殆,面试也是如此,针对于上面的面试问到的知识点我总结出了互联网公司java程序员在面试中涉及到的绝大部分架构面试题及答案做成了文档和架构视频资料免费分享给大家(包括Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术资料),希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!资料领取方式:关注+转发后,私信关键词 【架构资料】即可获取!重要的事情说三遍,转发、转发、转发后再发私信,才可以拿到哦!

March 18, 2019 · 1 min · jiezi

leetcode SQL题目解析

前言这一篇文章, 是对leetcode上部分SQL题目(14/19)的解析, 所有的题目均使用MySQL的语法。这些题目的SQL的写法可能不是最优的, 但是它们都通过了leetcode上的所有的测试用例, 如果你有更好的SQL请务必联系我, ????: 1025873823。leetcode上的SQL类的题目不是很多, 只有19题。很遗憾, 我没有将它们全部攻克。未来我会尝试将它们全部解答出来。题目1: 组合两张表组合两张表, 题目很简单, 主要考察JOIN语法的使用。唯一需要注意的一点, 是题目中的这句话, “无论 person 是否有地址信息”。说明即使Person表, 没有信息我们也需要将Person表的内容进行返回。所以我选择使用左外查询, 当然你也可以选择RIGHT OUTER JOIN, 这取决于你查询语句的写法。解答SELECT Person.FirstName, Person.LastName, Address.City, Address.StateFROM Person LEFT OUTER JOIN Address ON Person.PersonId = Address.PersonId题目2: 第二高的薪水第二高的薪水, 题目本身并不难, 但是请注意, 题目中的描述"如果不存在第二高的薪水,那么查询应返回 null", 这意味着, 如果SQL没有查询到结果, SQL本身需要一个默认的返回值。如何才能做到, 即使没有结果也返回一个值。通过谷歌, 我查找到了解决方案[Returning a value even if no result](https://stackoverflow.com/que…。使用IFNULL函数, 并且将整个SQL语句作为IFNULL函数的参数。如果IFNULL函数第一个的参数为NULL, 则返回IFNULL函数的第二个参数, 否则返回第一个参数。解答SELECT IFNULL( ( SELECT Employee.Salary FROM Employee GROUP BY Employee.Salary ORDER BY Employee.Salary DESC LIMIT 1 OFFSET 1 ), NULL) AS SecondHighestSalary;题目3: 分数排名本题主要考察了, 如何在SQL查询中生成序号, 因为在表中本身是不含有RANK字段的。我通过谷歌, 在stackoverflow上找到了答案, Generate serial number in mysql query。为查询结果添加序号解答# 3. 通过INNER JOIN为没有去重的分数表添加名次的字段SELECT Scores.Score, RANKINDEX.rank AS RANKFROM Scores INNER JOIN (# 2. 为排序去重后分数表, 添加名次字段(序号) SELECT RANK.Score AS Score, @a:=@a+1 rank FROM (# 1. 首先排序并去重分数表 SELECT DISTINCT Scores.Score FROM Scores ORDER BY Scores.Score DESC ) RANK, (SELECT @a:=0) AS a) AS RANKINDEXON RANKINDEX.Score = Scores.ScoreORDER BY Scores.Score DESC题目4: 超过经理收入的员工非常简单的一道题目, 这里不在多做解释解答SELECT emp1.Name AS EmployeeFROM Employee AS emp1, Employee AS emp2WHERE emp1.ManagerId = emp2.Id AND emp1.Salary > emp2.Salary题目5: 查找重复的电子邮箱同样是非常简单的一道题目, 唯一可能需要了解的就是, GROUP BY Person.Email的字句, 可以对Person.Email字段起到去重的作用解答SELECT Person.Email AS EmailFROM PersonGROUP BY Person.EmailHAVING COUNT(Person.Email) > 1题目6: 从不订购的客户依然是非常简单的一道题目, 主要考察对子查询的使用解答SELECT Customers.Name AS CustomersFROM CustomersWHERE Customers.Id NOT IN ( SELECT Orders.CustomerId FROM Orders)题目7: 部门工资最高的员工部门工资最高的员工, 在对这一题目进行解答之前。我们需要明确知道一点。“除聚合, 计算语句外,SELECT语句中的每个列都必须在GROUP BY子句中给出”。也就是说, 我们并不能在求, 每一个部门工资的Max最大值的时候, 把员工的id也计算出来。对于这道题目,我们解答的步骤分为两步, 1. 求出每一个部门对应的最高工资, 并且将结果存储为派生表 2. 根据员工的部门id, 以及员工的工资, 与派生表联结, 比较对应员工的工资是否等于派生表的部门的最高工资。如果等于, 此人的工资就是部门的最高工资解答SELECT Department.Name AS Department, Employee.Name AS Employee, Employee.Salary AS SalaryFROM Employee INNER JOIN Department INNER JOIN (# 第一步求出每一个部门的最高工资, 并作为派生表使用 SELECT Max(Employee.Salary) AS Salary, Department.Id AS DepartmentId FROM Employee INNER JOIN Department ON Employee.DepartmentId = Department.Id GROUP BY Employee.DepartmentId) AS DepartmentBigSalary# 三张表进行联结ON Employee.DepartmentId = Department.Id AND Department.Id = DepartmentBigSalary.DepartmentId# 比较对应员工的工资是否等于派生表的部门的最高工资WHERE Employee.Salary = DepartmentBigSalary.Salary题目8: 删除重复的电子邮箱DELETE语句在不指定WHERE子句的时候, 默认是删除表中全部的行。题目指定了两个条件, “删除 Person 表中所有重复的电子邮箱,重复的邮箱里只保留 Id 最小 的那个”, WHERE同时也需要指定两个条件。两个条件, 请参考下面的代码。唯一值的注意的一点是, DELETE本身是更新操作, 所以在FROM需要新建一个派生表, 否则会产生错误(You can’t specify target table ‘Person’ for update in FROM clause)解答DELETEFROM PersonWHERE Person.Email IN ( # 条件1: 删除长度大于2的行 SELECT table1.Email FROM ( SELECT Person.Email AS Email FROM Person GROUP BY Person.Email HAVING COUNT(Person.Email) > 1 ) AS table1) AND Person.Id NOT IN ( # 条件1: 删除长度大于2的行, 但是不包含id最小的行 SELECT table2.id FROM ( SELECT MIN(Person.Id) AS id FROM Person GROUP BY Person.Email HAVING COUNT(Person.Email) > 1 ) AS table2)题目9: 上升的温度本题主要考察了对自联结的使用。如何判断两个相邻的RecordDate的Temperature的大小?通过对同一张表进行JOIN联结, JOIN的ON的条件修改为w1.RecordDate = DATE_SUB(w2.RecordDate,INTERVAL -1 DAY), w1表的RecordDate是w2表RecordDate前一天, w1的每一行关联的w2的每一行其实w1的后一天。解答SELECT w1.Id AS IdFROM Weather AS w1 INNER JOIN Weather AS w2ON w1.RecordDate = DATE_SUB(w2.RecordDate,INTERVAL -1 DAY)WHERE w1.Temperature > w2.Temperature题目10: 大的国家非常简单的一道题, 这里不在赘述解答SELECT World.Name AS Name, World.population AS population, World.area AS areaFROM WorldWHERE World.population > 25000000 OR World.area > 3000000题目11: 超过5名学生的课超过5名学生的课, 本道题目注意考察点在于对GROUP BY去重效果的认知上。首先子查询的采用嵌套分组。首先使用课程分组然后根据学生进行分组。可以有效去除课程, 学生重复的行。为什么不直接使用学生分组呢?因为这样做会丢失学生的课程信息。在外层的查询中只需要查找中COUNT大于5的课程即可。解答SELECT ClassLength.class FROM (# 排除了学生和课程重复的行 SELECT courses.class AS class FROM courses GROUP BY courses.class, courses.student) AS ClassLengthGROUP BY ClassLength.classHAVING COUNT(ClassLength.class) >= 5题目12: 有趣的电影本道题目也较为简单, 考察点在于对于奇偶数的判断上, 我们可以使用MySQL的MOD函数。MOD(N, M), MOD函数将返回N/M的余数解答SELECT cinema.id AS id, cinema.movie AS movie, cinema.description AS description, cinema.rating AS ratingFROM cinemaWHERE cinema.description <> ‘boring’ AND MOD(cinema.id, 2) = 1ORDER BY rating DESC题目13: 交换工资题目本身要求使用一个更新查询,并且没有中间临时表。所以SQL中避免不了需要使用逻辑判断, 这里使用MySQl的CASE WHEN语句解答UPDATE salarySET salary.sex = ( CASE WHEN salary.sex = ’m’ THEN ‘f’ WHEN salary.sex = ‘f’ THEN ’m’ ELSE ‘sex’ END)题目14: 连续出现的数字与"上升的温度"的题目类似, 合理的使用自联结, 就可以解答出本题解答SELECT Consecutive.ConsecutiveNumsFROM ( SELECT l1.Num AS ConsecutiveNums FROM Logs AS l1 INNER JOIN Logs AS l2 INNER JOIN Logs AS l3 ON l1.id = l2.id - 1 AND l2.id = l3.id - 1 AND l1.id = l3.id - 2 WHERE l1.Num = l2.Num AND l2.Num = l3.Num AND l1.Num = l3.Num) AS ConsecutiveGROUP BY Consecutive.ConsecutiveNums ...

March 18, 2019 · 3 min · jiezi

MySQL8.0新特性集锦

作者:偏执的工匠原文:https://www.jianshu.com/p/be2…1. 默认字符集由latin1变为utf8mb4在8.0版本之前,默认字符集为latin1,utf8指向的是utf8mb3,8.0版本默认字符集为utf8mb4,utf8默认指向的也是utf8mb4。2. MyISAM系统表全部换成InnoDB表系统表全部换成事务型的innodb表,默认的MySQL实例将不包含任何MyISAM表,除非手动创建MyISAM表。# MySQL 5.7mysql> select distinct(ENGINE) from information_schema.tables;+——————–+| ENGINE |+——————–+| MEMORY || InnoDB || MyISAM || CSV || PERFORMANCE_SCHEMA || NULL |+——————–+6 rows in set (0.00 sec) # MySQL 8.0mysql> select distinct(ENGINE) from information_schema.tables;+——————–+| ENGINE |+——————–+| NULL || InnoDB || CSV || PERFORMANCE_SCHEMA |+——————–+4 rows in set (0.00 sec)3. 自增变量持久化在8.0之前的版本,自增主键AUTO_INCREMENT的值如果大于max(primary key)+1,在MySQL重启后,会重置AUTO_INCREMENT=max(primary key)+1,这种现象在某些情况下会导致业务主键冲突或者其他难以发现的问题。自增主键重启重置的问题很早就被发现(https://bugs.mysql.com/bug.ph…),一直到8.0才被解决,8.0版本将会对AUTO_INCREMENT值进行持久化,MySQL重启后,该值将不会改变。4. DDL原子化InnoDB表的DDL支持事务完整性,要么成功要么回滚,将DDL操作回滚日志写入到data dictionary 数据字典表 mysql.innodb_ddl_log 中用于回滚操作,该表是隐藏的表,通过show tables无法看到。通过设置参数,可将ddl操作日志打印输出到mysql错误日志中。mysql> set global log_error_verbosity=3;mysql> set global innodb_print_ddl_logs=1;mysql> create table t1(c int) engine=innodb; # MySQL错误日志:2018-06-26T11:25:25.817245+08:00 44 [Note] [MY-012473] [InnoDB] InnoDB: DDL log insert : [DDL record: DELETE SPACE, id=41, thread_id=44, space_id=6, old_file_path=./db/t1.ibd]2018-06-26T11:25:25.817369+08:00 44 [Note] [MY-012478] [InnoDB] InnoDB: DDL log delete : by id 412018-06-26T11:25:25.819753+08:00 44 [Note] [MY-012477] [InnoDB] InnoDB: DDL log insert : [DDL record: REMOVE CACHE, id=42, thread_id=44, table_id=1063, new_file_path=db/t1]2018-06-26T11:25:25.819796+08:00 44 [Note] [MY-012478] [InnoDB] InnoDB: DDL log delete : by id 422018-06-26T11:25:25.820556+08:00 44 [Note] [MY-012472] [InnoDB] InnoDB: DDL log insert : [DDL record: FREE, id=43, thread_id=44, space_id=6, index_id=140, page_no=4]2018-06-26T11:25:25.820594+08:00 44 [Note] [MY-012478] [InnoDB] InnoDB: DDL log delete : by id 432018-06-26T11:25:25.825743+08:00 44 [Note] [MY-012485] [InnoDB] InnoDB: DDL log post ddl : begin for thread id : 442018-06-26T11:25:25.825784+08:00 44 [Note] [MY-012486] [InnoDB] InnoDB: DDL log post ddl : end for thread id : 44来看另外一个例子,库里只有一个t1表,drop table t1,t2; 试图删除t1,t2两张表,在5.7中,执行报错,但是t1表被删除,在8.0中执行报错,但是t1表没有被删除,证明了8.0 DDL操作的原子性,要么全部成功,要么回滚。# MySQL 5.7mysql> show tables;+—————+| Tables_in_db |+—————+| t1 |+—————+1 row in set (0.00 sec)mysql> drop table t1, t2;ERROR 1051 (42S02): Unknown table ‘db.t2’mysql> show tables;Empty set (0.00 sec) # MySQL 8.0mysql> show tables;+—————+| Tables_in_db |+—————+| t1 |+—————+1 row in set (0.00 sec)mysql> drop table t1, t2;ERROR 1051 (42S02): Unknown table ‘db.t2’mysql> show tables;+—————+| Tables_in_db |+—————+| t1 |+—————+1 row in set (0.00 sec)5. 参数修改持久化MySQL 8.0版本支持在线修改全局参数并持久化,通过加上PERSIST关键字,可以将修改的参数持久化到新的配置文件(mysqld-auto.cnf)中,重启MySQL时,可以从该配置文件获取到最新的配置参数。 例如执行: set PERSIST expire_logs_days=10 ; 系统会在数据目录下生成一个包含json格式的 mysqld-auto.cnf 的文件,格式化后如下所示,当 my.cnf 和 mysqld-auto.cnf 同时存在时,后者具有更高优先级。{ “Version”: 1, “mysql_server”: { “expire_logs_days”: { “Value”: “10”, “Metadata”: { “Timestamp”: 1529657078851627, “User”: “root”, “Host”: “localhost” } } }}6. 新增降序索引MySQL在语法上很早就已经支持降序索引,但实际上创建的仍然是升序索引,如下MySQL 5.7 所示,c2字段降序,但是从show create table看c2仍然是升序。8.0可以看到,c2字段降序。# MySQL 5.7mysql> create table t1(c1 int,c2 int,index idx_c1_c2(c1,c2 desc));Query OK, 0 rows affected (0.03 sec)mysql> show create table t1\G*************************** 1. row *************************** Table: t1Create Table: CREATE TABLE t1 ( c1 int(11) DEFAULT NULL, c2 int(11) DEFAULT NULL, KEY idx_c1_c2 (c1,c2)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb41 row in set (0.00 sec) # MySQL 8.0mysql> create table t1(c1 int,c2 int,index idx_c1_c2(c1,c2 desc));Query OK, 0 rows affected (0.06 sec)mysql> show create table t1\G*************************** 1. row *************************** Table: t1Create Table: CREATE TABLE t1 ( c1 int(11) DEFAULT NULL, c2 int(11) DEFAULT NULL, KEY idx_c1_c2 (c1,c2 DESC)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC1 row in set (0.00 sec)再来看看降序索引在执行计划中的表现,在t1表插入10万条随机数据,查看select * from t1 order by c1 , c2 desc;的执行计划。从执行计划上可以看出,5.7的扫描数100113远远大于8.0的5行,并且使用了filesort。DELIMITER ;;CREATE PROCEDURE test_insert ()BEGINDECLARE i INT DEFAULT 1;WHILE i<100000DOinsert into t1 select rand()100000, rand()100000;SET i=i+1;END WHILE ;commit;END;;DELIMITER ;CALL test_insert(); # MySQL 5.7mysql> explain select * from t1 order by c1 , c2 desc limit 5;+—-+————-+——-+————+——-+—————+———–+———+——+——–+———-+—————————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+———–+———+——+——–+———-+—————————–+| 1 | SIMPLE | t1 | NULL | index | NULL | idx_c1_c2 | 10 | NULL | 100113 | 100.00 | Using index; Using filesort |+—-+————-+——-+————+——-+—————+———–+———+——+——–+———-+—————————–+1 row in set, 1 warning (0.00 sec) # MySQL 8.0mysql> explain select * from t1 order by c1 , c2 desc limit 5;+—-+————-+——-+————+——-+—————+———–+———+——+——+———-+————-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+———–+———+——+——+———-+————-+| 1 | SIMPLE | t1 | NULL | index | NULL | idx_c1_c2 | 10 | NULL | 5 | 100.00 | Using index |+—-+————-+——-+————+——-+—————+———–+———+——+——+———-+————-+1 row in set, 1 warning (0.00 sec)降序索引只是对查询中特定的排序顺序有效,如果使用不当,反而查询效率更低,比如上述查询排序条件改为 order by c1 desc, c2 desc,这种情况下,5.7的执行计划要明显好于8.0的,如下:# MySQL 5.7mysql> explain select * from t1 order by c1 desc , c2 desc limit 5;+—-+————-+——-+————+——-+—————+———–+———+——+——+———-+————-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+———–+———+——+——+———-+————-+| 1 | SIMPLE | t1 | NULL | index | NULL | idx_c1_c2 | 10 | NULL | 5 | 100.00 | Using index |+—-+————-+——-+————+——-+—————+———–+———+——+——+———-+————-+1 row in set, 1 warning (0.01 sec) # MySQL 8.0mysql> explain select * from t1 order by c1 desc , c2 desc limit 5;+—-+————-+——-+————+——-+—————+———–+———+——+——–+———-+—————————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+———–+———+——+——–+———-+—————————–+| 1 | SIMPLE | t1 | NULL | index | NULL | idx_c1_c2 | 10 | NULL | 100429 | 100.00 | Using index; Using filesort |+—-+————-+——-+————+——-+—————+———–+———+——+——–+———-+—————————–+1 row in set, 1 warning (0.01 sec)7. group by 不再隐式排序mysql 8.0 对于group by 字段不再隐式排序,如需要排序,必须显式加上order by 子句。# 表结构mysql> show create table tb1\G************************* 1. row *************************** Table: tb1Create Table: CREATE TABLE tb1 ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(50) DEFAULT NULL, group_own int(11) DEFAULT ‘0’, PRIMARY KEY (id)) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC1 row in set (0.00 sec) # 表数据mysql> select * from tb1;+—-+——+———–+| id | name | group_own |+—-+——+———–+| 1 | 1 | 0 || 2 | 2 | 0 || 3 | 3 | 0 || 4 | 4 | 0 || 5 | 5 | 5 || 8 | 8 | 1 || 10 | 10 | 5 |+—-+——+———–+7 rows in set (0.00 sec) # MySQL 5.7mysql> select count(id), group_own from tb1 group by group_own;+———–+———–+| count(id) | group_own |+———–+———–+| 4 | 0 || 1 | 1 || 2 | 5 |+———–+———–+3 rows in set (0.00 sec) # MySQL 8.0.11mysql> select count(id), group_own from tb1 group by group_own;+———–+———–+| count(id) | group_own |+———–+———–+| 4 | 0 || 2 | 5 || 1 | 1 |+———–+———–+3 rows in set (0.00 sec) # MySQL 8.0.11显式地加上order by进行排序mysql> select count(id), group_own from tb1 group by group_own order by group_own;+———–+———–+| count(id) | group_own |+———–+———–+| 4 | 0 || 1 | 1 || 2 | 5 |+———–+———–+3 rows in set (0.00 sec)8. JSON特性增强MySQL 8 大幅改进了对 JSON 的支持,添加了基于路径查询参数从 JSON 字段中抽取数据的 JSON_EXTRACT() 函数,以及用于将数据分别组合到 JSON 数组和对象中的 JSON_ARRAYAGG() 和 JSON_OBJECTAGG() 聚合函数。在主从复制中,新增参数 binlog_row_value_options,控制JSON数据的传输方式,允许对于Json类型部分修改,在binlog中只记录修改的部分,减少json大数据在只有少量修改的情况下,对资源的占用。9. redo & undo 日志加密增加以下两个参数,用于控制redo、undo日志的加密。innodb_undo_log_encryptinnodb_undo_log_encrypt10. innodb select for update跳过锁等待select … for update,select … for share(8.0新增语法) 添加 NOWAIT、SKIP LOCKED语法,跳过锁等待,或者跳过锁定。 在5.7及之前的版本,select…for update,如果获取不到锁,会一直等待,直到innodb_lock_wait_timeout超时。在8.0版本,通过添加nowait,skip locked语法,能够立即返回。如果查询的行已经加锁,那么nowait会立即报错返回,而skip locked也会立即返回,只是返回的结果中不包含被锁定的行。# session1:mysql> begin;mysql> select * from t1 where c1 = 2 for update;+——+——-+| c1 | c2 |+——+——-+| 2 | 60530 || 2 | 24678 |+——+——-+2 rows in set (0.00 sec) # session2:mysql> select * from t1 where c1 = 2 for update nowait;ERROR 3572 (HY000): Statement aborted because lock(s) could not be acquired immediately and NOWAIT is set.mysql> select * from t1 where c1 = 2 for update skip locked;Empty set (0.00 sec)11. 增加SET_VAR语法在sql语法中增加SET_VAR语法,动态调整部分参数,有利于提升语句性能。select /+ SET_VAR(sort_buffer_size = 16M) / id from test order id ;insert /+ SET_VAR(foreign_key_checks=OFF) / into test(name) values(1);12. 支持不可见索引使用INVISIBLE关键字在创建表或者进行表变更中设置索引是否可见。索引不可见只是在查询时优化器不使用该索引,即使使用force index,优化器也不会使用该索引,同时优化器也不会报索引不存在的错误,因为索引仍然真实存在,在必要时,也可以快速的恢复成可见。# 创建不可见索引create table t2(c1 int,c2 int,index idx_c1_c2(c1,c2 desc) invisible );# 索引可见alter table t2 alter index idx_c1_c2 visible;# 索引不可见alter table t2 alter index idx_c1_c2 invisible;13. 支持直方图优化器会利用column_statistics的数据,判断字段的值的分布,得到更准确的执行计划。可以使用 ANALYZE TABLE table_name [UPDATE HISTOGRAM on col_name with N BUCKETS |DROP HISTOGRAM ON clo_name] 来收集或者删除直方图信息。直方图统计了表中某些字段的数据分布情况,为优化选择高效的执行计划提供参考,直方图与索引有着本质的区别,维护一个索引有代价。每一次的insert、update、delete都会需要更新索引,会对性能有一定的影响。而直方图一次创建永不更新,除非明确去更新它。所以不会影响insert、update、delete的性能。# 添加/更新直方图mysql> analyze table t1 update histogram on c1, c2 with 32 buckets;+——–+———–+———-+———————————————–+| Table | Op | Msg_type | Msg_text |+——–+———–+———-+———————————————–+| db.t1 | histogram | status | Histogram statistics created for column ‘c1’. || db.t1 | histogram | status | Histogram statistics created for column ‘c2’. |+——–+———–+———-+———————————————–+2 rows in set (2.57 sec) # 删除直方图mysql> analyze table t1 drop histogram on c1, c2;+——–+———–+———-+———————————————–+| Table | Op | Msg_type | Msg_text |+——–+———–+———-+———————————————–+| db.t1 | histogram | status | Histogram statistics removed for column ‘c1’. || db.t1 | histogram | status | Histogram statistics removed for column ‘c2’. |+——–+———–+———-+———————————————–+2 rows in set (0.13 sec)14. 新增innodb_dedicated_server参数能够让InnoDB根据服务器上检测到的内存大小自动配置innodb_buffer_pool_size,innodb_log_file_size,innodb_flush_method三个参数。15. 日志分类更详细在错误信息中添加了错误信息编号[MY-010311]和错误所属子系统[Server]# MySQL 5.72018-06-08T09:07:20.114585+08:00 0 [Warning] ‘proxies_priv’ entry ‘@ root@localhost’ ignored in –skip-name-resolve mode.2018-06-08T09:07:20.117848+08:00 0 [Warning] ’tables_priv’ entry ‘user mysql.session@localhost’ ignored in –skip-name-resolve mode.2018-06-08T09:07:20.117868+08:00 0 [Warning] ’tables_priv’ entry ‘sys_config mysql.sys@localhost’ ignored in –skip-name-resolve mode. # MySQL 8.02018-06-21T17:53:13.040295+08:00 28 [Warning] [MY-010311] [Server] ‘proxies_priv’ entry ‘@ root@localhost’ ignored in –skip-name-resolve mode.2018-06-21T17:53:13.040520+08:00 28 [Warning] [MY-010330] [Server] ’tables_priv’ entry ‘user mysql.session@localhost’ ignored in –skip-name-resolve mode.2018-06-21T17:53:13.040542+08:00 28 [Warning] [MY-010330] [Server] ’tables_priv’ entry ‘sys_config mysql.sys@localhost’ ignored in –skip-name-resolve mode.16. undo空间自动回收innodb_undo_log_truncate参数在8.0.2版本默认值由OFF变为ON,默认开启undo日志表空间自动回收。innodb_undo_tablespaces参数在8.0.2版本默认为2,当一个undo表空间被回收时,还有另外一个提供正常服务。innodb_max_undo_log_size参数定义了undo表空间回收的最大值,当undo表空间超过这个值,该表空间被标记为可回收。17. 增加资源组MySQL 8.0新增了一个资源组功能,用于调控线程优先级以及绑定CPU核。 MySQL用户需要有 RESOURCE_GROUP_ADMIN权限才能创建、修改、删除资源组。 在Linux环境下,MySQL进程需要有 CAP_SYS_NICE 权限才能使用资源组完整功能。[root@localhost~]# sudo setcap cap_sys_nice+ep /usr/local/mysql8.0/bin/mysqld[root@localhost~]# getcap /usr/local/mysql8.0/bin/mysqld/usr/local/mysql8.0/bin/mysqld = cap_sys_nice+ep默认提供两个资源组,分别是USR_default,SYS_default创建资源组: create resource group test_resouce_group type=USER vcpu=0,1 thread_priority=5; 将当前线程加入资源组: SET RESOURCE GROUP test_resouce_group; 将某个线程加入资源组: SET RESOURCE GROUP test_resouce_group FOR thread_id; 查看资源组里有哪些线程: select * from Performance_Schema.threads where RESOURCE_GROUP=‘test_resouce_group’; 修改资源组: alter resource group test_resouce_group vcpu = 2,3 THREAD_PRIORITY = 8; 删除资源组 : drop resource group test_resouce_group;# 创建资源组mysql>create resource group test_resouce_group type=USER vcpu=0,1 thread_priority=5;Query OK, 0 rows affected (0.03 sec)mysql> select * from RESOURCE_GROUPS;+———————+———————+————————+———-+—————–+| RESOURCE_GROUP_NAME | RESOURCE_GROUP_TYPE | RESOURCE_GROUP_ENABLED | VCPU_IDS | THREAD_PRIORITY |+———————+———————+————————+———-+—————–+| USR_default | USER | 1 | 0-3 | 0 || SYS_default | SYSTEM | 1 | 0-3 | 0 || test_resouce_group | USER | 1 | 0-1 | 5 |+———————+———————+————————+———-+—————–+3 rows in set (0.00 sec)# 把线程id为60的线程加入到资源组test_resouce_group中,线程id可通过Performance_Schema.threads获取mysql> SET RESOURCE GROUP test_resouce_group FOR 60;Query OK, 0 rows affected (0.00 sec)# 资源组里有线程时,删除资源组报错mysql> drop resource group test_resouce_group;ERROR 3656 (HY000): Resource group test_resouce_group is busy.# 修改资源组mysql> alter resource group test_resouce_group vcpu = 2,3 THREAD_PRIORITY = 8;Query OK, 0 rows affected (0.10 sec)mysql> select * from RESOURCE_GROUPS;+———————+———————+————————+———-+—————–+| RESOURCE_GROUP_NAME | RESOURCE_GROUP_TYPE | RESOURCE_GROUP_ENABLED | VCPU_IDS | THREAD_PRIORITY |+———————+———————+————————+———-+—————–+| USR_default | USER | 1 | 0-3 | 0 || SYS_default | SYSTEM | 1 | 0-3 | 0 || test_resouce_group | USER | 1 | 2-3 | 8 |+———————+———————+————————+———-+—————–+3 rows in set (0.00 sec)# 把资源组里的线程移出到默认资源组USR_defaultmysql> SET RESOURCE GROUP USR_default FOR 60;Query OK, 0 rows affected (0.00 sec)# 删除资源组mysql> drop resource group test_resouce_group;Query OK, 0 rows affected (0.04 sec)18. 增加角色管理角色可以认为是一些权限的集合,为用户赋予统一的角色,权限的修改直接通过角色来进行,无需为每个用户单独授权。# 创建角色mysql> create role role_test;Query OK, 0 rows affected (0.03 sec) # 给角色授予权限mysql> grant select on db.* to ‘role_test’;Query OK, 0 rows affected (0.10 sec) # 创建用户mysql> create user ‘read_user’@’%’ identified by ‘123456’;Query OK, 0 rows affected (0.09 sec) # 给用户赋予角色mysql> grant ‘role_test’ to ‘read_user’@’%’;Query OK, 0 rows affected (0.02 sec) # 给角色role_test增加insert权限mysql> grant insert on db.* to ‘role_test’;Query OK, 0 rows affected (0.08 sec) # 给角色role_test删除insert权限mysql> revoke insert on db.* from ‘role_test’;Query OK, 0 rows affected (0.10 sec) # 查看默认角色信息mysql> select * from mysql.default_roles;+——+———–+——————-+——————-+| HOST | USER | DEFAULT_ROLE_HOST | DEFAULT_ROLE_USER |+——+———–+——————-+——————-+| % | read_user | % | role_test |+——+———–+——————-+——————-+1 row in set (0.00 sec) # 查看角色与用户关系mysql> select * from mysql.role_edges;+———–+———–+———+———–+——————-+| FROM_HOST | FROM_USER | TO_HOST | TO_USER | WITH_ADMIN_OPTION |+———–+———–+———+———–+——————-+| % | role_test | % | read_user | N |+———–+———–+———+———–+——————-+1 row in set (0.00 sec) # 删除角色mysql> drop role role_test;Query OK, 0 rows affected (0.06 sec) ...

March 18, 2019 · 8 min · jiezi

一次 group by + order by 性能优化分析

原文:我的个人博客 https://mengkang.net/1302.html工作了两三年,技术停滞不前,迷茫没有方向,不如看下我的直播 PHP 进阶之路 (金三银四跳槽必考,一般人我不告诉他)最近通过一个日志表做排行的时候发现特别卡,最后问题得到了解决,梳理一些索引和MySQL执行过程的经验,但是最后还是有5个谜题没解开,希望大家帮忙解答下主要包含如下知识点用数据说话证明慢日志的扫描行数到底是如何统计出来的从 group by 执行原理找出优化方案排序的实现细节gdb 源码调试背景需要分别统计本月、本周被访问的文章的 TOP10。日志表如下CREATE TABLE article_rank ( id int(11) unsigned NOT NULL AUTO_INCREMENT, aid int(11) unsigned NOT NULL, pv int(11) unsigned NOT NULL DEFAULT ‘1’, day int(11) NOT NULL COMMENT ‘日期 例如 20171016’, PRIMARY KEY (id), KEY idx_day_aid_pv (day,aid,pv), KEY idx_aid_day_pv (aid,day,pv)) ENGINE=InnoDB DEFAULT CHARSET=utf8准备工作为了能够清晰的验证自己的一些猜想,在虚拟机里安装了一个 debug 版的 mysql,然后开启了慢日志收集,用于统计扫描行数安装下载源码编译安装创建 mysql 用户初始化数据库初始化 mysql 配置文件修改密码如果你兴趣,具体可以参考我的博客,一步步安装 https://mengkang.net/1335.html开启慢日志编辑配置文件,在[mysqld]块下添加slow_query_log=1slow_query_log_file=xxxlong_query_time=0log_queries_not_using_indexes=1性能分析发现问题假如我需要查询2018-12-20 ~ 2018-12-24这5天浏览量最大的10篇文章的 sql 如下,首先使用explain看下分析结果mysql> explain select aid,sum(pv) as num from article_rank where day>=20181220 and day<=20181224 group by aid order by num desc limit 10;+—-+————-+————–+————+——-+——————————-+—————-+———+——+——–+———-+———————————————————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+————–+————+——-+——————————-+—————-+———+——+——–+———-+———————————————————–+| 1 | SIMPLE | article_rank | NULL | range | idx_day_aid_pv,idx_aid_day_pv | idx_day_aid_pv | 4 | NULL | 404607 | 100.00 | Using where; Using index; Using temporary; Using filesort |+—-+————-+————–+————+——-+——————————-+—————-+———+——+——–+———-+———————————————————–+系统默认会走的索引是idx_day_aid_pv,根据Extra信息我们可以看到,使用idx_day_aid_pv索引的时候,会走覆盖索引,但是会使用临时表,会有排序。我们查看下慢日志里的记录信息# Time: 2019-03-17T03:02:27.984091Z# User@Host: root[root] @ localhost [] Id: 6# Query_time: 56.959484 Lock_time: 0.000195 Rows_sent: 10 Rows_examined: 1337315SET timestamp=1552791747;select aid,sum(pv) as num from article_rank where day>=20181220 and day<=20181224 group by aid order by num desc limit 10;为什么扫描行数是 1337315我们查询两个数据,一个是满足条件的行数,一个是group by统计之后的行数。mysql> select count() from article_rank where day>=20181220 and day<=20181224;+———-+| count() |+———-+| 785102 |+———-+mysql> select count(distinct aid) from article_rank where day>=20181220 and day<=20181224;+———————+| count(distinct aid) |+———————+| 552203 |+———————+发现满足条件的总行数(785102)+group by 之后的总行数(552203)+limit 的值 = 慢日志里统计的 Rows_examined。要解答这个问题,就必须搞清楚上面这个 sql 到底分别都是如何运行的。执行流程分析索引示例为了便于理解,我按照索引的规则先模拟idx_day_aid_pv索引的一小部分数据dayaidpvid20181220123123420181220321231201812204112122018122072122120181221151257201812211011251201812211181258因为索引idx_day_aid_pv最左列是day,所以当我们需要查找2018122020181224之间的文章的pv总和的时候,我们需要遍历2018122020181224这段数据的索引。查看 optimizer trace 信息# 开启 optimizer_traceset optimizer_trace=‘enabled=on’;# 执行 sql select aid,sum(pv) as num from article_rank where day>=20181220 and day<=20181224 group by aid order by num desc limit 10;# 查看 trace 信息select trace from information_schema.optimizer_trace\G;摘取里面最后的执行结果如下{ “join_execution”: { “select#”: 1, “steps”: [ { “creating_tmp_table”: { “tmp_table_info”: { “table”: “intermediate_tmp_table”, “row_length”: 20, “key_length”: 4, “unique_constraint”: false, “location”: “memory (heap)”, “row_limit_estimate”: 838860 } } }, { “converting_tmp_table_to_ondisk”: { “cause”: “memory_table_size_exceeded”, “tmp_table_info”: { “table”: “intermediate_tmp_table”, “row_length”: 20, “key_length”: 4, “unique_constraint”: false, “location”: “disk (InnoDB)”, “record_format”: “fixed” } } }, { “filesort_information”: [ { “direction”: “desc”, “table”: “intermediate_tmp_table”, “field”: “num” } ], “filesort_priority_queue_optimization”: { “limit”: 10, “rows_estimate”: 1057, “row_size”: 36, “memory_available”: 262144, “chosen”: true }, “filesort_execution”: [ ], “filesort_summary”: { “rows”: 11, “examined_rows”: 552203, “number_of_tmp_files”: 0, “sort_buffer_size”: 488, “sort_mode”: “<sort_key, additional_fields>” } } ] }}分析临时表字段mysql gdb 调试更多细节 https://mengkang.net/1336.html通过gdb调试确认临时表上的字段是aid和numBreakpoint 1, trace_tmp_table (trace=0x7eff94003088, table=0x7eff94937200) at /root/newdb/mysql-server/sql/sql_tmp_table.cc:2306warning: Source file is more recent than executable.2306 trace_tmp.add(“row_length”,table->s->reclength).(gdb) p table->s->reclength$1 = 20(gdb) p table->s->fields$2 = 2(gdb) p ((table->field+0))->field_name$3 = 0x7eff94010b0c “aid”(gdb) p ((table->field+1))->field_name$4 = 0x7eff94007518 “num”(gdb) p ((table->field+0))->row_pack_length()$5 = 4(gdb) p ((table->field+1))->row_pack_length()$6 = 15(gdb) p ((table->field+0))->type()$7 = MYSQL_TYPE_LONG(gdb) p ((table->field+1))->type()$8 = MYSQL_TYPE_NEWDECIMAL(gdb)通过上面的打印,确认了字段类型,一个aid是MYSQL_TYPE_LONG,占4字节,num是MYSQL_TYPE_NEWDECIMAL,占15字节。The SUM() and AVG() functions return a DECIMAL value for exact-value arguments (integer or DECIMAL), and a DOUBLE value for approximate-value arguments (FLOAT or DOUBLE). (Before MySQL 5.0.3, SUM() and AVG() return DOUBLE for all numeric arguments.)但是通过我们上面打印信息可以看到两个字段的长度加起来是19,而optimizer_trace里的tmp_table_info.reclength是20。通过其他实验也发现table->s->reclength的长度就是table->field数组里面所有字段的字段长度和再加1。总结执行流程尝试在堆上使用memory的内存临时表来存放group by的数据,发现内存不够;创建一张临时表,临时表上有两个字段,aid和num字段(sum(pv) as num);从索引idx_day_aid_pv中取出1行,插入临时表。插入规则是如果aid不存在则直接插入,如果存在,则把pv的值累加在num上;循环遍历索引idx_day_aid_pv上2018122020181224之间的所有行,执行步骤3;对临时表根据num的值做优先队列排序;取出最后留在堆(优先队列的堆)里面的10行数据,作为结果集直接返回,不需要再回表;补充说明优先队列排序执行步骤分析:在临时表(未排序)中取出前 10 行,把其中的num和aid作为10个元素构成一个小顶堆,也就是最小的 num 在堆顶。取下一行,根据 num 的值和堆顶值作比较,如果该字大于堆顶的值,则替换掉。然后将新的堆做堆排序。重复步骤2直到第 552203 行比较完成。优化方案1 使用 idx_aid_day_pv 索引# Query_time: 4.406927 Lock_time: 0.000200 Rows_sent: 10 Rows_examined: 1337315SET timestamp=1552791804;select aid,sum(pv) as num from article_rank force index(idx_aid_day_pv) where day>=20181220 and day<=20181224 group by aid order by num desc limit 10;扫描行数都是1337315,为什么执行消耗的时间上快了12倍呢?索引示例为了便于理解,同样我也按照索引的规则先模拟idx_aid_day_pv索引的一小部分数据aiddaypvid12018122023123412018122151257320181220212313201812222213313201812241314314201812201121272018122021221102018122111251112018122181258group by 不需要临时表的情况为什么性能上比 SQL1 高了,很多呢,原因之一是idx_aid_day_pv索引上aid是确定有序的,那么执行group by的时候,则不会创建临时表,排序的时候才需要临时表。如果印证这一点呢,我们通过下面的执行计划就能看到使用idx_day_aid_pv索引的效果:mysql> explain select aid,sum(pv) as num from article_rank force index(idx_day_aid_pv) where day>=20181220 and day<=20181224 group by aid order by null limit 10;+—-+————-+————–+————+——-+——————————-+—————-+———+——+——–+———-+——————————————-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+————–+————+——-+——————————-+—————-+———+——+——–+———-+——————————————-+| 1 | SIMPLE | article_rank | NULL | range | idx_day_aid_pv,idx_aid_day_pv | idx_day_aid_pv | 4 | NULL | 404607 | 100.00 | Using where; Using index; Using temporary |+—-+————-+————–+————+——-+——————————-+—————-+———+——+——–+———-+——————————————-+注意我上面使用了order by null表示强制对group by的结果不做排序。如果不加order by null,上面的 sql 则会出现Using filesort使用idx_aid_day_pv索引的效果:mysql> explain select aid,sum(pv) as num from article_rank force index(idx_aid_day_pv) where day>=20181220 and day<=20181224 group by aid order by null limit 10;+—-+————-+————–+————+——-+——————————-+—————-+———+——+——+———-+————————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+————–+————+——-+——————————-+—————-+———+——+——+———-+————————–+| 1 | SIMPLE | article_rank | NULL | index | idx_day_aid_pv,idx_aid_day_pv | idx_aid_day_pv | 12 | NULL | 10 | 11.11 | Using where; Using index |+—-+————-+————–+————+——-+——————————-+—————-+———+——+——+———-+————————–+查看 optimizer trace 信息# 开启optimizer_traceset optimizer_trace=‘enabled=on’;# 执行 sql select aid,sum(pv) as num from article_rank force index(idx_aid_day_pv) where day>=20181220 and day<=20181224 group by aid order by num desc limit 10;# 查看 trace 信息select trace from information_schema.optimizer_trace\G;摘取里面最后的执行结果如下{ “join_execution”: { “select#”: 1, “steps”: [ { “creating_tmp_table”: { “tmp_table_info”: { “table”: “intermediate_tmp_table”, “row_length”: 20, “key_length”: 0, “unique_constraint”: false, “location”: “memory (heap)”, “row_limit_estimate”: 838860 } } }, { “filesort_information”: [ { “direction”: “desc”, “table”: “intermediate_tmp_table”, “field”: “num” } ], “filesort_priority_queue_optimization”: { “limit”: 10, “rows_estimate”: 552213, “row_size”: 24, “memory_available”: 262144, “chosen”: true }, “filesort_execution”: [ ], “filesort_summary”: { “rows”: 11, “examined_rows”: 552203, “number_of_tmp_files”: 0, “sort_buffer_size”: 352, “sort_mode”: “<sort_key, rowid>” } } ] }}执行流程如下创建一张临时表,临时表上有两个字段,aid和num字段(sum(pv) as num);读取索引idx_aid_day_pv中的一行,然后查看是否满足条件,如果day字段不在条件范围内(2018122020181224之间),则读取下一行;如果day字段在条件范围内,则把pv值累加(不是在临时表中操作);读取索引idx_aid_day_pv中的下一行,如果aid与步骤1中一致且满足条件,则pv值累加(不是在临时表中操作)。如果aid与步骤1中不一致,则把之前的结果集写入临时表;循环执行步骤2、3,直到扫描完整个idx_aid_day_pv索引;对临时表根据num的值做优先队列排序;根据查询到的前10条的rowid回表(临时表)返回结果集。补充说明优先队列排序执行步骤分析:在临时表(未排序)中取出前 10 行,把其中的num和rowid作为10个元素构成一个小顶堆,也就是最小的 num 在堆顶。取下一行,根据 num 的值和堆顶值作比较,如果该字大于堆顶的值,则替换掉。然后将新的堆做堆排序。重复步骤2直到第 552203 行比较完成。该方案可行性实验发现,当我增加一行20181219的数据时,虽然这行记录不满足我们的需求,但是扫描索引的也会读取这行。因为我做这个实验,只弄了20181220~201812245天的数据,所以需要扫描的行数正好是全表数据行数。那么如果该表的数据存储的不是5天的数据,而是10天的数据呢,更或者是365天的数据呢?这个方案是否还可行呢?先模拟10天的数据,在现有时间基础上往后加5天,行数与现在一样785102行。drop procedure if exists idata;delimiter ;;create procedure idata()begin declare i int; declare aid int; declare pv int; declare post_day int; set i=1; while(i<=785102)do set aid = round(rand()*500000); set pv = round(rand()*100); set post_day = 20181225 + i%5; insert into article_rank (aid,pv,day) values(aid, pv, post_day); set i=i+1; end while;end;;delimiter ;call idata();# Query_time: 9.151270 Lock_time: 0.000508 Rows_sent: 10 Rows_examined: 2122417SET timestamp=1552889936;select aid,sum(pv) as num from article_rank force index(idx_aid_day_pv) where day>=20181220 and day<=20181224 group by aid order by num desc limit 10;这里扫描行数2122417是因为扫描索引的时候需要遍历整个索引,整个索引的行数就是全表行数,因为我刚刚又插入了785102行。当我数据量翻倍之后,这里查询时间明显已经翻倍。所以这个优化方式不稳定。方案2 扩充临时表空间上限大小默认的临时表空间大小是16MBmysql> show global variables like ‘%table_size’;+———————+———-+| Variable_name | Value |+———————+———-+| max_heap_table_size | 16777216 || tmp_table_size | 16777216 |+———————+———-+https://dev.mysql.com/doc/ref…https://dev.mysql.com/doc/ref...max_heap_table_sizeThis variable sets the maximum size to which user-created MEMORY tables are permitted to grow. The value of the variable is used to calculate MEMORY table MAX_ROWS values. Setting this variable has no effect on any existing MEMORY table, unless the table is re-created with a statement such as CREATE TABLE or altered with ALTER TABLE or TRUNCATE TABLE. A server restart also sets the maximum size of existing MEMORY tables to the global max_heap_table_size value.tmp_table_size The maximum size of internal in-memory temporary tables. This variable does not apply to user-created MEMORY tables.The actual limit is determined from whichever of the values of tmp_table_size and max_heap_table_size is smaller. If an in-memory temporary table exceeds the limit, MySQL automatically converts it to an on-disk temporary table. The internal_tmp_disk_storage_engine option defines the storage engine used for on-disk temporary tables.也就是说这里临时表的限制是16M,max_heap_table_size大小也受tmp_table_size大小的限制。所以我们这里调整为32MB,然后执行原始的SQLset tmp_table_size=33554432;set max_heap_table_size=33554432;# Query_time: 5.910553 Lock_time: 0.000210 Rows_sent: 10 Rows_examined: 1337315SET timestamp=1552803869;select aid,sum(pv) as num from article_rank where day>=20181220 and day<=20181224 group by aid order by num desc limit 10;方案3 使用 SQL_BIG_RESULT 优化告诉优化器,查询结果比较多,临时表直接走磁盘存储。# Query_time: 6.144315 Lock_time: 0.000183 Rows_sent: 10 Rows_examined: 2122417SET timestamp=1552802804;select SQL_BIG_RESULT aid,sum(pv) as num from article_rank where day>=20181220 and day<=20181224 group by aid order by num desc limit 10;扫描行数是 2x满足条件的总行数(785102)+group by 之后的总行数(552203)+limit 的值。顺便值得一提的是: 当我把数据量翻倍之后,使用该方式,查询时间基本没变。因为扫描的行数还是不变的。实际测试耗时6.197484总结方案1优化效果不稳定,当总表数据量与查询范围的总数相同时,且不超出内存临时表大小限制时,性能达到最佳。当查询数据量占据总表数据量越大,优化效果越不明显;方案2需要调整临时表内存的大小,可行;不过当数据库超过32MB时,如果使用该方式,还需要继续提升临时表大小;方案3直接声明使用磁盘来放临时表,虽然扫描行数多了一次符合条件的总行数的扫描。但是整体响应时间比方案2就慢了0.1秒。因为我们这里数据量比较,我觉得这个时间差还能接受。所以最后对比,选择方案3比较合适。问题与困惑# SQL1select aid,sum(pv) as num from article_rank where day>=20181220 and day<=20181224 group by aid order by num desc limit 10;# SQL2select aid,sum(pv) as num from article_rank force index(idx_aid_day_pv) where day>=20181220 and day<=20181224 group by aid order by num desc limit 10;SQL1 执行过程中,使用的是全字段排序最后不需要回表为什么总扫描行数还要加上10才对得上?SQL1 与 SQL2 group by之后得到的行数都是552203,为什么会出现 SQL1 内存不够,里面还有哪些细节呢?trace 信息里的creating_tmp_table.tmp_table_info.row_limit_estimate都是838860;计算由来是临时表的内存限制大小16MB,而一行需要占的空间是20字节,那么最多只能容纳floor(16777216/20) = 838860行,而实际我们需要放入临时表的行数是785102。为什么呢?SQL1 使用SQL_BIG_RESULT优化之后,原始表需要扫描的行数会乘以2,背后逻辑是什么呢?为什么仅仅是不再尝试往内存临时表里写入这一步会相差10多倍的性能?通过源码看到 trace 信息里面很多扫描行数都不是实际的行数,既然是实际执行,为什么 trace 信息里不输出真实的扫描行数和容量等呢,比如filesort_priority_queue_optimization.rows_estimate在SQL1中的扫描行数我通过gdb看到计算规则如附录图 1有没有工具能够统计 SQL 执行过程中的 I/O 次数?附录 ...

March 18, 2019 · 6 min · jiezi

MySQL基础知识

– ———————————————————- 主机: 127.0.0.1– 服务器版本: 5.5.55 - MySQL Community Server (GPL)– 服务器操作系统: Win64– HeidiSQL 版本: 9.3.0.4984– ——————————————————–开始登录MySQLmysql -h 127.0.0.1 -u 用户名 -pmysql -D 所选择的数据库名 -h 主机名 -u 用户名 -pmysql> exit # 退出 使用 “quit;” 或 “q;” 一样的效果mysql> status; # 显示当前mysql的version的各种信息mysql> select version(); # 显示当前mysql的version信息mysql> show global variables like ‘port’; # 查看MySQL端口号创建一个数据库对于表的操作需要先进入库 use 库名– 创建一个名为 samp_db 的数据库,数据库字符编码指定为 utf8 或 gbkcreate database samp_db character set utf8;show databases; – 显示所有数据库 列表use samp_db; – 选中创建的数据库samp_dbshow tables; – 显示samp_db下面所有的表名字describe 表名; – 显示数据表的结构delete from 表名; – 清空表中所有数据记录删除一个数据库drop database samp_db; – 删除 库名为samp_db的库在数据库中创建一个表(表结构)使用 create table 语句可完成对表的创建, create table 的常见形式: 语法:create table 表名称(列声明);CREATE TABLE IF NOT EXISTS Prescription (id int(11) NOT NULL AUTO_INCREMENT,year varchar(50) DEFAULT NULL COMMENT ‘年’,month varchar(50) DEFAULT NULL COMMENT ‘月’,Prescription varchar(50) DEFAULT NULL COMMENT ‘物流’,PRIMARY KEY (id)) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT=‘电商物流时效指数’;数据类型的属性解释* NULL:数据列可包含NULL值;* NOT NULL:数据列不允许包含NULL值;* DEFAULT:默认值;* PRIMARY:KEY 主键;* AUTO_INCREMENT:自动递增,适用于整数类型;* UNSIGNED:是指数值类型只能为正数;* CHARACTER SET name:指定一个字符集;* COMMENT:对表或者字段说明;增删改查SELECT(查)SELECT 语句用于从表中选取数据。语法:SELECT 列名称 FROM 表名称语法:SELECT * FROM 表名称– 从表 Persons 选取 LastName 列的数据SELECT LastName FROM Persons– 从表 users 选取 id=3 的数据,并只拉一条数据(据说能优化性能)SELECT * FROM users where id=3 limit 1UPDATE(改)Update 语句用于修改表中的数据。语法:UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值– 更新(改)表 orders 中 id=1 的那一行数据更新它的 title 字段UPDATE orders set title=‘这里是标题’ WHERE id=1;INSERT(增)INSERT INTO 语句用于向表格中插入新的行。语法:INSERT INTO 表名称 VALUES (值1, 值2,….)语法:INSERT INTO 表名称 (列1, 列2,…) VALUES (值1, 值2,….)– 向表 prescription 插入一条字段 aa = JSLite 字段 bb = shanghaiINSERT INTO prescription (aa, bb) VALUES (‘JSLite’, ‘shanghai’);– 向表 prescription 插入 字段 aa=1 和字段 bb=2INSERT INTO prescription SET aa=1,bb=2;– SQL实现将一个表的数据插入到另外一个表的代码– 如果只希望导入指定字段,可以用这种方法:– INSERT tab 目标表 (字段hh, 字段hh, …) SELECT 字段bb, 字段aa, … FROM 来源表prescription;INSERT INTO tab (hh, hh) SELECT m.bb, m.aa FROM prescription m where m.id=1;– 为表中的字 段插入(写入)数据id可以省略 prescription 目标表 id 字段 数据对应字段INSERT IGNORE INTO prescription (year, month, prescription) VALUES(‘2016’, ‘09’, ‘115’),(‘2017’, ‘10’, ‘120’),(‘2018’, ‘12’, ‘109’);– 向表 prescription 插入一条数据,已存在就对表 prescription 更新 year,month 字段;INSERT INTO prescription (id,year,month,aa) VALUES (3,2,‘2017-05-18 11:06:17’,‘2017-05-18 11:06:17’) ON DUPLICATE KEY UPDATE year=VALUES(year), month=VALUES(month), aa=VALUES(aa);DELETE(删)DELETE 语句用于删除表中的行。语法:DELETE FROM 表名称 WHERE 列名称 = 值– 在不删除table_name表的情况下删除所有的行,清空表数据DELETE FROM tab– 删除 Person表字段 LastName = ‘JSLite’,带有此字段值的数据都会被清空DELETE FROM Person WHERE LastName = ‘JSLite’– 删除 表meeting id 为2和3的两条数据DELETE from meeting where id in (2,3);WHERE(查询语句)WHERE 子句用于规定选择的标准。语法:SELECT 列名称 FROM 表名称 WHERE 列 运算符 值– 从表 Persons 中选出 year 字段大于 2016 的数据 ,返回整条数据SELECT * FROM Persons WHERE year>2016操作符ADN和ORAND - 如果第一个条件和第二个条件都成立;OR - 如果第一个条件和第二个条件中只要有一个成立;ADN(且)– 删除 meeting 表字段– id=2 并且 user_id=5 的数据 和– id=3 并且 user_id=6 的数据DELETE from prescription where id in (2,3) and month in (5,6);– 使用 AND 来显示表Persons所有姓为 “Carter” 并且名为 “Thomas” 的人:SELECT * FROM Persons WHERE FirstName=‘Thomas’ AND LastName=‘Carter’;OR(或)– 使用 OR 来显示所有姓为 “Carter” 或者名为 “Thomas” 的人:SELECT * FROM Persons WHERE firstname=‘Thomas’ OR lastname=‘Carter’;ORDER BY(排序)语句默认按照升序对记录进行排序。ORDER BY - 语句用于根据指定的列对结果集进行排序。DESC - 按照降序对记录进行排序。ASC - 按照顺序对记录进行排序。– Company在表prescription中为字母,则会以字母顺序显示公司名称SELECT Company, OrderNumber FROM prescription ORDER BY Company– 后面跟上 DESC 则为降序显示SELECT Company, OrderNumber FROM prescription ORDER BY Company DESC– Company以降序显示公司名称,并OrderNumber以顺序显示SELECT Company, OrderNumber FROM prescription ORDER BY Company DESC, OrderNumber ASCIN(查询多个值)IN - 操作符允许我们在 WHERE 子句中规定多个值。IN - 操作符用来指定范围,范围中的每一条,都进行匹配。IN取值规律,由逗号分割,全部放置括号中。 语法:SELECT “字段名"FROM “表格名"WHERE “字段名” IN (‘值一’, ‘值二’, …);– 从表 Persons 选取 字段 LastName 等于 Adams、CarterSELECT * FROM Persons WHERE bb IN (‘Adams’,‘Carter’);NOTNOT - 操作符总是与其他操作符一起使用,用在要过滤的前面。– 表prescription 字段 month 和字段bb 值等于5SELECT month, bb FROM prescription WHERE NOT month = ‘5’ ORDER BY bb;UNION(合并SELECT查询结果)UNION - 操作符用于合并两个或多个 SELECT 语句的结果集– 列出所有在表(prescription)和(tab)的不同的值SELECT bb FROM prescription UNION SELECT bb FROM tab– 列出 prescription 表中的 bb,– tab 表中的 number_station 别名设置成 bb 避免字段不一样报错– 按更新时间排序SELECT id,bb FROM prescription UNION ALL SELECT id,bb AS bb FROM tab ORDER BY bb;– 通过 UNION 语法同时查询了 products 表 和 comments 表的总记录数,并且按照 count 排序SELECT ‘product’ AS type, count() as count FROM products union select ‘comment’ as type, count() as count FROM comments order by count;ASas - 可理解为:用作、当成,作为;别名一般是重命名列名或者表名。语法:select column_1 as 列1,column_2 as 列2 from table as 表SELECT * FROM prescription AS emp– 这句意思是查找所有prescription 表里面的数据,并把prescription表格命名为 emp。– 当你命名一个表之后,你可以在下面用 emp 代替 prescription.– 例如 SELECT * FROM emp.– 列出表 prescription 字段 month 列最大值,– 结果集列不显示 month 显示 LargestOrderPriceSELECT MAX(month) AS LargestOrderPrice FROM prescription– 显示表 prescription 中的 bb 列SELECT t.bb from (SELECT * from prescription a) AS t;– 表 user_accounts 命名别名 ua,表 users_profile 命名别名 up– 满足条件 表 user_accounts 字段 id 等于 表 users_profile 字段 user_id– 结果集只显示mobile、name两列SELECT ua.mobile,up.name FROM user_accounts as ua INNER JOIN users_profile as up ON ua.id = up.user_id;JOIN用于根据两个或多个表中的列之间的关系,从这些表中查询数据。* JOIN: 如果表中有至少一个匹配,则返回行* INNER JOIN:在表中存在至少一个匹配时,INNER JOIN 关键字返回行。* LEFT JOIN: 即使右表中没有匹配,也从左表返回所有的行* RIGHT JOIN: 即使左表中没有匹配,也从右表返回所有的行* FULL JOIN: 只要其中一个表中存在匹配,就返回行(MySQL 是不支持的,通过 LEFT JOIN + UNION + RIGHT JOIN 的方式 来实现)SELECT Persons.LastName, Persons.FirstName, Orders.OrderNoFROM PersonsINNER JOIN OrdersON Persons.Id_P = Orders.Id_PORDER BY Persons.LastName;SQL函数COUNTCOUNT 让我们能够数出在表格中有多少笔资料被选出来。语法:SELECT COUNT(“字段名”) FROM “表格名”;– 获取 prescription 表的总数SELECT COUNT(1) AS totals FROM prescription;– 获取表 prescription 字段 bb 相同的总数select bb, count() as totals from prescription group by bb;MAXMAX 函数返回一列中的最大值。NULL 值不包括在计算中。语法:SELECT MAX(“字段名”) FROM “表格名”– 列出表 Orders 字段 OrderPrice 列最大值,– 结果集列不显示 OrderPrice 显示 LargestOrderPriceSELECT MAX(OrderPrice) AS LargestOrderPrice FROM Orders触发器语法: create trigger <触发器名称> { before | after} # 之前或者之后出发 insert | update | delete # 指明了激活触发程序的语句的类型 on <表名> # 操作哪张表 for each row # 触发器的执行间隔,for each row 通知触发器每隔一行执行一次动作,而不是对整个表执行一次。 <触发器SQL语句>delimiter $CREATE TRIGGER set_userdate BEFORE INSERTon messagefor EACH ROWBEGINset @statu = new.status; – 声明复制变量 statuif @statu = 0 then – 判断 statu 是否等于 0UPDATE user_accounts SET status=1 WHERE openid=NEW.openid;end if;END$DELIMITER ; – 恢复结束符号OLD和NEW不区分大小写 NEW 用NEW.col_name,没有旧行。在DELETE触发程序中,仅能使用OLD.col_name,没有新行。* OLD 用OLD.col_name来引用更新前的某一行的列添加索引普通索引(INDEX)– –直接创建索引CREATE INDEX index_user ON user(title)– –修改表结构的方式添加索引ALTER TABLE table_name ADD INDEX index_name ON (column(length))– 给 user 表中的 name 字段 添加普通索引(INDEX)ALTER TABLE user ADD INDEX index_name (name)– –创建表的时候同时创建索引CREATE TABLE user (id int(11) NOT NULL AUTO_INCREMENT ,title char(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,content text CHARACTER SET utf8 COLLATE utf8_general_ci NULL ,time int(10) NULL DEFAULT NULL ,PRIMARY KEY (id),INDEX index_name (title(length)))– –删除索引DROP INDEX index_name ON table主键索引(PRIMAPY Key)语法:ALTER TABLE 表名字 ADD PRIMARY KEY ( 字段名字 )– 给 user 表中的 id字段 添加主键索引(PRIMARY key)ALTER TABLE user ADD PRIMARY key (id);唯一索引(UNIQUE)语法:ALTER TABLE 表名字 ADD UNIQUE (字段名字)– 给 user 表中的 creattime 字段添加唯一索引(UNIQUE)ALTER TABLE user ADD UNIQUE (creattime);全文索引(FULLTEXT)语法:ALTER TABLE 表名字 ADD FULLTEXT (字段名字)– 给 user 表中的 creattime 字段添加唯一索引(UNIQUE)ALTER TABLE user ADD UNIQUE (creattime);添加多列索引语法: ALTER TABLE table_name ADD INDEX index_name ( column1, column2, column3)– 给 user 表中的 name、city、age 字段添加名字为name_city_age的普通索引(INDEX)ALTER TABLE user ADD INDEX name_city_age (name(10),city,age);建立索引的时机在WHERE和JOIN中出现的列需要建立索引,但也不完全如此:* MySQL只对<,<=,=,>,>=,BETWEEN,IN使用索引* 某些时候的LIKE也会使用索引。* 在LIKE以通配符%和_开头作查询时,MySQL不会使用索引。– 此时就需要对city和age建立索引,– 由于mytable表的userame也出现在了JOIN子句中,也有对它建立索引的必要。SELECT t.NameFROM mytable t LEFT JOIN mytable m ON t.Name=m.usernameWHERE m.age=20 AND m.city=‘上海’;SELECT * FROM mytable WHERE username like’admin%’; – 而下句就不会使用:SELECT * FROM mytable WHERE Name like’%admin’; – 因此,在使用LIKE时应注意以上的区别索引的注意事项* 索引不会包含有NULL值的列* 使用短索引* 不要在列上进行运算 索引会失效创建后表的修改添加列语法:alter table 表名 add 列名 列数据类型 [after 插入位置];– 在表students的最后追加列 address:alter table students add address char(60);– 在名为 age 的列后插入列 birthday:alter table students add birthday date after age;– 在名为 number_people 的列后插入列 weeks:alter table students add column weeks varchar(5) not null default "” after number_people;修改列语法:alter table 表名 change 列名称 列新名称 新数据类型;– 将表 tel 列改名为 telphone:alter table students change tel telphone char(13) default “-”;– 将 name 列的数据类型改为 char(16):alter table students change name name char(16) not null;– 修改 COMMENT 前面必须得有类型属性alter table students change name name char(16) COMMENT ‘这里是名字’;– 修改列属性的时候 建议使用modify,不需要重建表– change用于修改列名字,这个需要重建表alter table meeting modify weeks varchar(20) NOT NULL DEFAULT ’’ COMMENT ‘开放日期 周一到周日:0~6,间隔用英文逗号隔开’;– user表的id列,修改成字符串类型长度50,不能为空,FIRST放在第一列的位置alter table user modify COLUMN id varchar(50) NOT NULL FIRST ;删除列语法:alter table 表名 drop 列名称;– 删除表students中的 birthday 列:alter table students drop birthday;重命名表语法:alter table 表名 rename 新表名;– 重命名 students 表为 workmates:alter table students rename workmates;清空表数据方法一:delete from 表名; 方法二:truncate from “表名”;* DELETE:1. DML语言;2. 可以回退;3. 可以有条件的删除;* TRUNCATE:1. DDL语言;2. 无法回退;3. 默认所有的表内容都删除;4. 删除速度比delete快– 清空表为 workmates 里面的数据,不删除表。delete from workmates;– 删除workmates表中的所有数据,且无法恢复truncate table workmates;删除整张表语法:drop table 表名;– 删除 workmates 表:drop table workmates;删除整个数据库语法:drop database 数据库名;– 删除 samp_db 数据库:drop database samp_db;参考文档http://www.runoob.com/mysql/m… ...

March 18, 2019 · 6 min · jiezi

数据库备份之冷备

#! /bin/bashDB_HOST=“127.0.0.1"DB_NANE=“testdatabase"DB_USER=“root"DB_PASS=““DATE=date +%Y%m%d%h%m%sMYSQL_PATH=/root/back_up(){ BAKUP_FILE=${MYSQL_PATH}${DB_NAME}${DATE}.sql SQL_OPT="-u${DB_USER} -p${DB_PASS} -h${DB_HOST} ${DB_NAME}” mysqldump ${SQL_OPT} > ${BAKUP_FILE} if [ $? -eq 0 ];then echo ‘ok’; else echo ’error’; fi}back_up;

March 18, 2019 · 1 min · jiezi

mysql 实时查看性能

mysqladmin -P3306 -uasd -pasdasd -hmysql.com -r -i 1 ext |\ awk -F"|" \ “BEGIN{ count=0; }”\ ‘{ if($2 ~ /Variable_name/ && ((++count)%20 == 1)){\ print “———-|———|— MySQL Command Status –|—– Innodb row operation —-|– Buffer Pool Read –”;\ print “—Time—|—QPS—|select insert update delete| read inserted updated deleted| logical physical”;\ }\ else if ($2 ~ /Queries/){queries=$3;}\ else if ($2 ~ /Com_select /){com_select=$3;}\ else if ($2 ~ /Com_insert /){com_insert=$3;}\ else if ($2 ~ /Com_update /){com_update=$3;}\ else if ($2 ~ /Com_delete /){com_delete=$3;}\ else if ($2 ~ /Innodb_rows_read/){innodb_rows_read=$3;}\ else if ($2 ~ /Innodb_rows_deleted/){innodb_rows_deleted=$3;}\ else if ($2 ~ /Innodb_rows_inserted/){innodb_rows_inserted=$3;}\ else if ($2 ~ /Innodb_rows_updated/){innodb_rows_updated=$3;}\ else if ($2 ~ /Innodb_buffer_pool_read_requests/){innodb_lor=$3;}\ else if ($2 ~ /Innodb_buffer_pool_reads/){innodb_phr=$3;}\ else if ($2 ~ /Uptime / && count >= 2){\ printf(" %s |%9d",strftime("%H:%M:%S"),queries);\ printf("|%6d %6d %6d %6d",com_select,com_insert,com_update,com_delete);\ printf("|%6d %8d %7d %7d",innodb_rows_read,innodb_rows_inserted,innodb_rows_updated,innodb_rows_deleted);\ printf("|%10d %11d\n",innodb_lor,innodb_phr);\ }}’ ...

March 18, 2019 · 1 min · jiezi

Java到底要做到什么程度才能适应市场的需求(本人的面试经历)

前言:从过年前就萌生出要跳槽的想法,到过年来公司从月初提出离职到~~号正式离职,上班的时间也出去面试过几家公司,后来总觉的在职找工作总是得请假,便决心离职后找工作。到3月10号找到了一家互联网公司成功应聘上,中间也经历了很多公司,有外包的、创业的、互联网的等等各种类型,也收到了很多offer,也有面试不顺序的…今天来记录一下自己面试中的问题,围绕着java到底应该具备什么样的水平才能适应现在市场的要求的主题来谈一谈。本篇文章目录:一:面试中的问题二: 面试中要注意的问题三:关于最后的选择四:两年java到底应该具备什么样的水平一:面试中的问题java集合框架:1:介绍一下java的集合框架2:HashMap遇见哈希冲突会如何怎么办?HashMap是线程安全的吗?HashMap在高并发下会有什么问题?然后引入ConcurrentHashMap的原理?3:Hahtable和concurrentHashMap的区别?4:数组和ArrayList的区别?Arraylist是如何扩容的?5:线程池中的阻塞队列一般会选择哪种队列?为什么?6:RetreenLock的原理?AQS的原理?7:HashMap的容量为什么推荐是2的幂次方?框架类:1:mybatis的二级缓存有什么问题?2:mybaits中的mapper的#{}和${}有什么区别?哪种可以防止sql注入?2:我们知道mybatis的mapper和接口之间是没有对象的,那么它是如何映射的?4:说说springmvc的注解有哪些?他们的原理是什么?5:springmvc的控制器是单例的吗?是线程安全的吗?6:struts1和struts2的区别?是线程安全的吗?7:spring如何解析它的xml文件?8:spring的核心是什么?Aop的原理是什么?redis相关:1:redis数据类型有哪些?2:zset数据类型是如何排序的?3:redis如何做项目的中间缓存层?4:redis的Hash的时间复杂度是多少?数据库:1:数据库索引分为哪几种?组合索引有什么要注意的问题?2:什么是悲观锁 什么是乐观锁?如何实现悲观锁?3: 数据库关键字的执行顺序是什么?4:如何进行sql优化?5:有没有进行过分库分表操作?分库之后如何保持事务一致?分布式和微服务:1:微服务要克服那些问题?微服务系统是怎样通信的?2:分布式环境下如何解决session不一致的问题?3:分布式下如何保证id一致?4:你在dubbo的使用过程中遇到什么问题?5: zookeeper的负载均衡算法有哪些?jdk源码相关1:synchronized的原理?它该怎么用?如何一个方法是synchronized的,其他的非synchronzied线程能进入吗?2:cvs中的ABA问题如何解决?3:volatile的原理是什么?volatile一定是线程安全的吗?4:ThreadLocal是什么?它的原理是什么?5:CountDowanLatch有没有用过?适合在什么样的场景下用?设计模式相关:1:实现两种单例模式2:讲一下观察者模式3:spring中都用到哪些设计模式?4:动态代理模式是如何实现的?5:你在项目中用到哪些设计模式了?讲解一下业务场景算法相关:1:快速排序的时间复杂度?手写快速排序(注意递归式和非递归式的实现方式)2:手写二分查找3:手写堆排序4:一个int数组如何进行奇数和偶数分离?5:用算法实现String转doublejvm相关:1: jvm的垃圾回收算法有哪些?分别解释一下?2: 新生代为什么要设置两个survior区?3:如何通过一个.class文件获取它的jdk版本?4:jvm的内存模型?哪些是线程私有的?哪些是公共的?关于自己的项目(问的时间最长)1:简述一下自己的项目?你在其中主要是做什么的?2:你在项目中都遇到了哪些难题?最后都是怎么解决的?3:项目有多大规模?周期多久(这个很多都问到的)4:讲一下某一模块的具体实现方式?然后从中挑刺5:如何解决某一时刻的高并发请求?6:如何解决订单支付回调的超时问题?轮询应该怎么写?其他:1:秒杀场景如何削峰?2:http和udp的区别是什么?3:ajax的跨域问题4:nio与io的区别?什么情况下适合用nio5: 说说常见的linux命令,linux查看内存的命令是什么?7:git遇见代码冲突了怎么办?8:说几个常见的maven命令,maven如何排除一个jar包的冲突?二: 面试中要注意的问题2.1:一定要有自己的实际项目经验按照我这么多面试经验?其实有的公司会侧重于问自己做的项目经验,有的公司侧重于问问题,一般互联网公司会对技术要求比较高,既要求项目经验又要要求技术水平2.2:可以适当渲染,但是不要夸大其词面试的过程中最忌讳的就是夸夸其谈,高屋建瓴很厉害,但是一到实际细节都不知所云了,在技术总监面前,其实你吹牛或者是真的会他是一目了然的。不懂装懂,有的面试官又给你台阶下,不然你就卡带了,这很容易造成面试的不好印象2.3:要会自我介绍面试的时候一般的话都会让你做一个自我介绍,这个要分对象,是技术官还是Hr,如果是技术官侧重于综述一下自己的项目的实际技术栈和技术路线,如果是Hr的话不要用过多的技术语言,而要说一些自己的实际工作经历或者自己上家公司的运营情况2.4:关于简历简历切记不可太啰嗦,但是不可太简单,作为技术的简历一般起码得在3页,不然HR会觉得你的求职态度不怎么好,不管如何求职结果如何,一个良好的简历会给人留下好的第一印象(有简历模板)三:关于最后的选择说实话也接受到很多HR的offer邀请,但是我一般会选择说考虑一下一天以后再给回复,切不可直接把话说死,不然后面就尴尬了。实际提供的offer的有一家外包公司,三家创业公司,两家互联网公司,最终选择了一家互联网公司,虽然实际上班地点有点远(下了地铁还得座公交,后来还是选择骑单车了),但是互联网公司会给你快的成长速度,并且互联网技术栈都比较新..相比于传统企业会有更多的技术挑战。而外包公司的话,可能环境不怎么好,我记得自己当初还是个小白的时候,去了外包,那里的优点就是会有不断的活,新人进去的话收获还是挺多的,但是作为已经有两年经验的我,外包很显然不适合我的后期职业发展。缺点:技术更新迭代的太慢,没有归属感,最后的选择我个人的意见是选择技术优先,毕竟以后软件路还长,技术才是王道四:两年java到底应该具备什么样的水平两年java的面试过程中遇到了很多挑战,也遇到了一些不谈技术的公司,从上面的面试题可以看出,目前对于java的要求越来越高,水涨船高,毕竟这个行业的人数越来越多,而保持自己的竞争力的唯一方法就是找对方向,不断学习,注意这里我提到的第一点是方向,然后才是学习。给自己制定一个职业规划,按照这个路线往前走,我其实还在想分布式微服务这块以后再深入学习,可是按照市场要求,现在已经刻不容缓了,一些技术架构比如:springcloud、duboo都得保持学习,这样才能有竞争力!作为一名两年的javaSir,你必须具备以下技能1:阅读源码的能力,多用Intelj idea这个开发工具,而不是eclipse。它是直接支持反编译class文件的,多读jdk源码,吸收优秀的源码并加以复用2:做到能够手写常见的排序算法,比如快速排序和堆排序、冒泡排序、选择排序、二分查找这些都是必须的3:对java的框架有很深入的认识,现在基本流行的ssm框架很多人都会,可是知道一些原理的人就不多了,得不断研究这些框架本身,它们都是经过无数次锤炼 出来的优秀框架4:多用redismongodb,传统的关系型数据库已经无法市场需求了,这些东西也是面试中的一部分,虽不是重点,但也是加分的选项5:对于微服务和分布式,这个是有一定难度的,我在面试人人车的时候,一面很顺利,二面被技术总监给pass了,问题就是分布式不是特别熟悉!要想进入好的互联网公司,分布式和微服务是很必须的6:jvm的底层,这里要推荐的书就是周志明的《深入jvm虚拟机》这本书了,我总在闲暇时间读它,所以jvm的问题还是信手拈来的最后送上福利:腾讯课堂的的Java工程化、高性能及分布式、高性能、高架构、性能调优、Spring、MyBatis、Netty源码分析视频、java高并发处理视频(在腾讯买得花1800块左右)如果有谁想要,可以私信我,免费分享哦,我花了些钱买的

March 17, 2019 · 1 min · jiezi

koa2+vue+mysql 全栈开发记录

koa2+vue2+mysql 全栈开发记录基于想要自己制作一个个人项目为由,于是有了这么一个开发记录(梳理开发过程也是一个知识巩固的过程)koa2+vue2+mysql 个人的一个通用DEMO(本篇文章的范例)koa2+vue2+mysql GITHUB地址前端工具vuevue-routervuexaxioselement ui 页面UI组件echartsjs 百度强大的图表展示vue-admin-template 花裤衩大佬的一个实用管理后台模版 配套教程vue-i18n 国际化scss后端工具koakoa-bodyparser 解析 PUT / POST 请求中的 bodykoa-convertkoa-jsonkoa-jwt jwt鉴权koa-loggerkoa-mysql-sessionkoa-onerrorkoa-routerkoa-session-minimalkoa-statickoa-viewskoa2-cors 处理跨域md5 加密moment 时间处理mysql前端篇前端这边其实没什么好写的,主要是在vue-admin-template基础上做了一些修改src/utils/request.js的修改request拦截器 // request拦截器 service.interceptors.request.use( config => { if (store.getters.token) { // config.headers[‘X-Token’] = getToken() // 因为是jwt方式鉴权 所以在header头中改为如下写法 config.headers[‘Authorization’] = ‘Bearer ’ + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 } return config }, error => { // Do something with request error console.log(error) // for debug Promise.reject(error) } )response 拦截器 // response 拦截器 service.interceptors.response.use( response => { /** * code为非0是抛错 可结合自己业务进行修改 / const res = response.data if (res.code !== 0) { // 因为后台返回值为0则是成功,所以将原来的20000改为了0 Message({ message: res.message, type: ’error’, duration: 5 * 1000 }) // 70002:非法的token; 50012:其他客户端登录了; 50014:Token 过期了; if (res.code === 70002 || res.code === 50012 || res.code === 50014) { MessageBox.confirm( ‘你已被登出,可以取消继续留在该页面,或者重新登录’, ‘确定登出’, { confirmButtonText: ‘重新登录’, cancelButtonText: ‘取消’, type: ‘warning’ } ).then(() => { store.dispatch(‘FedLogOut’).then(() => { location.reload() // 为了重新实例化vue-router对象 避免bug }) }) } return Promise.reject(’error’) } else { return response.data } }, error => { console.log(’err’ + error) // for debug Message({ message: error.message, type: ’error’, duration: 5 * 1000 }) return Promise.reject(error) } )封装了一个Echart组件具体参考我的另外一个文章 vue中使用echarts 使用记录后端篇构建项目目录通过项目生成器生成koa-generatornpm install -g koa-generatorkoa2 /server && cd /servernpm install安装组件npm i jsonwebtoken koa-jwt koa-mysql-session koa-session-minimal koa2-cors md5 moment mysql save –save配置app.js const Koa = require(‘koa’) const jwt = require(‘koa-jwt’) const app = new Koa() const views = require(‘koa-views’) const json = require(‘koa-json’) const onerror = require(‘koa-onerror’) const bodyparser = require(‘koa-bodyparser’) const logger = require(‘koa-logger’) const convert = require(‘koa-convert’); var session = require(‘koa-session-minimal’) var MysqlStore = require(‘koa-mysql-session’) var config = require(’./config/default.js’) var cors = require(‘koa2-cors’) const users = require(’./routes/users’) const account = require(’./routes/account’) // error handler onerror(app) // 配置jwt错误返回 app.use(function(ctx, next) { return next().catch(err => { if (401 == err.status) { ctx.status = 401 ctx.body = ApiErrorNames.getErrorInfo(ApiErrorNames.INVALID_TOKEN) // ctx.body = { // // error: err.originalError ? err.originalError.message : err.message // } } else { throw err } }) }) // Unprotected middleware app.use(function(ctx, next) { if (ctx.url.match(/^/public/)) { ctx.body = ‘unprotected\n’ } else { return next() } }) // Middleware below this line is only reached if JWT token is valid app.use( jwt({ secret: config.secret, passthrough: true }).unless({ path: [//register/, //user/login/] }) ) // middlewares app.use(convert(bodyparser({ enableTypes:[‘json’, ‘form’, ’text’] }))) app.use(convert(json())) app.use(convert(logger())) app.use(require(‘koa-static’)(__dirname + ‘/public’)) app.use(views(__dirname + ‘/views’, { extension: ‘pug’ })) // logger app.use(async (ctx, next) => { const start = new Date() await next() const ms = new Date() - start console.log(${ctx.method} ${ctx.url} - ${ms}ms) }) // cors app.use(cors()) // routes app.use(users.routes(), users.allowedMethods()) app.use(account.routes(), account.allowedMethods()) // error-handling app.on(’error’, (err, ctx) => { console.error(‘server error’, err, ctx) }); module.exports = app新建config文件夹用于存放数据库连接等操作default.js// 数据库配置 const config = { port: 3000, database: { DATABASE: ‘xxx’, //数据库 USERNAME: ‘root’, //用户 PASSWORD: ‘xxx’, //密码 PORT: ‘3306’, //端口 HOST: ‘127.0.0.1’ //服务ip地址 }, secret: ‘jwt_secret’ } module.exports = config数据库相关(mysql)createTables.js 一个简单的用户角色权限表 用户、角色、权限表的关系(mysql)// 数据库表格创建const createTable = { users: CREATE TABLE IF NOT EXISTS user_info ( id INT PRIMARY KEY NOT NULL AUTO_INCREMENT COMMENT '(自增长)', user_id VARCHAR ( 100 ) NOT NULL COMMENT '账号', user_name VARCHAR ( 100 ) NOT NULL COMMENT '用户名', user_pwd VARCHAR ( 100 ) NOT NULL COMMENT '密码', user_head VARCHAR ( 225 ) COMMENT '头像', user_mobile VARCHAR ( 20 ) COMMENT '手机', user_email VARCHAR ( 64 ) COMMENT '邮箱', user_creatdata TIMESTAMP NOT NULL DEFAULT NOW( ) COMMENT '注册日期', user_login_time TIMESTAMP DEFAULT NOW( ) COMMENT '登录时间', user_count INT COMMENT '登录次数' ) ENGINE = INNODB charset = utf8;, role: CREATE TABLE IF NOT EXISTS role_info ( id INT PRIMARY KEY NOT NULL AUTO_INCREMENT COMMENT '(自增长)', role_name VARCHAR ( 20 ) NOT NULL COMMENT '角色名', role_description VARCHAR ( 255 ) DEFAULT NULL COMMENT '描述' ) ENGINE = INNODB charset = utf8;, permission: CREATE TABLE IF NOT EXISTS permission_info ( id INT PRIMARY KEY NOT NULL AUTO_INCREMENT COMMENT '(自增长)', permission_name VARCHAR ( 20 ) NOT NULL COMMENT '权限名', permission_description VARCHAR ( 255 ) DEFAULT NULL COMMENT '描述' ) ENGINE = INNODB charset = utf8;, userRole: CREATE TABLE IF NOT EXISTS user_role ( id INT PRIMARY KEY NOT NULL AUTO_INCREMENT COMMENT '(自增长)', user_id INT NOT NULL COMMENT '关联用户', role_id INT NOT NULL COMMENT '关联角色', KEY fk_user_role_role_info_1 ( role_id ), KEY fk_user_role_user_info_1 ( user_id ), CONSTRAINT fk_user_role_role_info_1 FOREIGN KEY ( role_id ) REFERENCES role_info ( id ) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT fk_user_role_user_info_1 FOREIGN KEY ( user_id ) REFERENCES user_info ( id ) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE = INNODB charset = utf8;, rolePermission: CREATE TABLE IF NOT EXISTS role_permission ( id INT PRIMARY KEY NOT NULL AUTO_INCREMENT COMMENT '(自增长)', role_id INT NOT NULL COMMENT '关联角色', permission_id INT NOT NULL COMMENT '关联权限', KEY fk_role_permission_role_info_1 ( role_id ), KEY fk_role_permission_permission_info_1 ( permission_id ), CONSTRAINT fk_role_permission_role_info_1 FOREIGN KEY ( role_id ) REFERENCES role_info ( id ) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT fk_role_permission_permission_info_1 FOREIGN KEY ( permission_id ) REFERENCES permission_info ( id ) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE = INNODB charset = utf8;}module.exports = createTable创建lib文件夹 用于存储数据库查询语句mysql.jsconst mysql = require(‘mysql’)const config = require(’../config/default’)const createTables = require(’../config/createTables.js’)var pool = mysql.createPool({ host: config.database.HOST, user: config.database.USERNAME, password: config.database.PASSWORD, database: config.database.DATABASE})let query = function(sql, values) { return new Promise((resolve, reject) => { pool.getConnection(function(err, connection) { if (err) { resolve(err) } else { connection.query(sql, values, (err, rows) => { if (err) { reject(err) } else { resolve(rows) } connection.release() }) } }) })}let createTable = function(sql) { return query(sql, [])}// 建表// createTable(createTables.users)// createTable(createTables.role)// createTable(createTables.permission)// createTable(createTables.userRole)// createTable(createTables.rolePermission)// 查询用户是否存在let findUser = async function(id) { let _sql = SELECT * FROM user_info where user_id="${id}" limit 1; let result = await query(_sql) if (Array.isArray(result) && result.length > 0) { result = result[0] } else { result = null } return result}// 查询用户以及用户角色let findUserAndRole = async function(id) { let _sql = SELECT u.*,r.role_name FROM user_info u,user_role ur,role_info r where u.id=(SELECT id FROM user_info where user_id="${id}" limit 1) and ur.user_id=u.id and r.id=ur.user_id limit 1; let result = await query(_sql) if (Array.isArray(result) && result.length > 0) { result = result[0] } else { result = null } return result}// 更新用户登录次数和登录时间let UpdataUserInfo = async function(value) { let _sql = ‘UPDATE user_info SET user_count = ?, user_login_time = ? WHERE id = ?;’ return query(_sql, value)}module.exports = { //暴露方法 createTable, findUser, findUserAndRole, UpdataUserInfo, getShopAndAccount}koa 路由配置接口报错信息统一方法 创建error文件夹ApiErrorNames.js/* * API错误名称 /var ApiErrorNames = {};ApiErrorNames.UNKNOW_ERROR = “UNKNOW_ERROR”;ApiErrorNames.SUCCESS = “SUCCESS”;/ 参数错误:10001-19999 /ApiErrorNames.PARAM_IS_INVALID = ‘PARAM_IS_INVALID’;ApiErrorNames.PARAM_IS_BLANK = ‘PARAM_IS_BLANK’;ApiErrorNames.PARAM_TYPE_BIND_ERROR = ‘PARAM_TYPE_BIND_ERROR’;ApiErrorNames.PARAM_NOT_COMPLETE = ‘PARAM_NOT_COMPLETE’;/ 用户错误:20001-29999*/ApiErrorNames.USER_NOT_LOGGED_IN = ‘USER_NOT_LOGGED_IN’;ApiErrorNames.USER_LOGIN_ERROR = ‘USER_LOGIN_ERROR’;ApiErrorNames.USER_ACCOUNT_FORBIDDEN = ‘USER_ACCOUNT_FORBIDDEN’;ApiErrorNames.USER_NOT_EXIST = ‘USER_NOT_EXIST’;ApiErrorNames.USER_HAS_EXISTED = ‘USER_HAS_EXISTED’;/* 业务错误:30001-39999 /ApiErrorNames.SPECIFIED_QUESTIONED_USER_NOT_EXIST = ‘SPECIFIED_QUESTIONED_USER_NOT_EXIST’;/ 系统错误:40001-49999 /ApiErrorNames.SYSTEM_INNER_ERROR = ‘SYSTEM_INNER_ERROR’;/ 数据错误:50001-599999 /ApiErrorNames.RESULE_DATA_NONE = ‘RESULE_DATA_NONE’;ApiErrorNames.DATA_IS_WRONG = ‘DATA_IS_WRONG’;ApiErrorNames.DATA_ALREADY_EXISTED = ‘DATA_ALREADY_EXISTED’;/ 接口错误:60001-69999 /ApiErrorNames.INTERFACE_INNER_INVOKE_ERROR = ‘INTERFACE_INNER_INVOKE_ERROR’;ApiErrorNames.INTERFACE_OUTTER_INVOKE_ERROR = ‘INTERFACE_OUTTER_INVOKE_ERROR’;ApiErrorNames.INTERFACE_FORBID_VISIT = ‘INTERFACE_FORBID_VISIT’;ApiErrorNames.INTERFACE_ADDRESS_INVALID = ‘INTERFACE_ADDRESS_INVALID’;ApiErrorNames.INTERFACE_REQUEST_TIMEOUT = ‘INTERFACE_REQUEST_TIMEOUT’;ApiErrorNames.INTERFACE_EXCEED_LOAD = ‘INTERFACE_EXCEED_LOAD’;/ 权限错误:70001-79999 /ApiErrorNames.PERMISSION_NO_ACCESS = ‘PERMISSION_NO_ACCESS’;ApiErrorNames.INVALID_TOKEN = ‘INVALID_TOKEN’;/* * API错误名称对应的错误信息 /const error_map = new Map();error_map.set(ApiErrorNames.SUCCESS, { code: 0, message: ‘成功’ });error_map.set(ApiErrorNames.UNKNOW_ERROR, { code: -1, message: ‘未知错误’ });/ 参数错误:10001-19999 /error_map.set(ApiErrorNames.PARAM_IS_INVALID, { code: 10001, message: ‘参数无效’ });error_map.set(ApiErrorNames.PARAM_IS_BLANK, { code: 10002, message: ‘参数为空’ });error_map.set(ApiErrorNames.PARAM_TYPE_BIND_ERROR, { code: 10003, message: ‘参数类型错误’ });error_map.set(ApiErrorNames.PARAM_NOT_COMPLETE, { code: 10004, message: ‘参数缺失’ });/ 用户错误:20001-29999*/error_map.set(ApiErrorNames.USER_NOT_LOGGED_IN, { code: 20001, message: ‘用户未登录’ });error_map.set(ApiErrorNames.USER_LOGIN_ERROR, { code: 20002, message: ‘账号不存在或密码错误’ });error_map.set(ApiErrorNames.USER_ACCOUNT_FORBIDDEN, { code: 20003, message: ‘账号已被禁用’ });error_map.set(ApiErrorNames.USER_NOT_EXIST, { code: 20004, message: ‘用户不存在’ });error_map.set(ApiErrorNames.USER_HAS_EXISTED, { code: 20005, message: ‘用户已存在’ });/* 业务错误:30001-39999 /error_map.set(ApiErrorNames.SPECIFIED_QUESTIONED_USER_NOT_EXIST, { code: 30001, message: ‘某业务出现问题’ });/ 系统错误:40001-49999 /error_map.set(ApiErrorNames.SYSTEM_INNER_ERROR, { code: 40001, message: ‘系统繁忙,请稍后重试’ });/ 数据错误:50001-599999 /error_map.set(ApiErrorNames.RESULE_DATA_NONE, { code: 50001, message: ‘数据未找到’ });error_map.set(ApiErrorNames.DATA_IS_WRONG, { code: 50002, message: ‘数据有误’ });error_map.set(ApiErrorNames.DATA_ALREADY_EXISTED, { code: 50003, message: ‘数据已存在’ });/ 接口错误:60001-69999 /error_map.set(ApiErrorNames.INTERFACE_INNER_INVOKE_ERROR, { code: 60001, message: ‘内部系统接口调用异常’ });error_map.set(ApiErrorNames.INTERFACE_OUTTER_INVOKE_ERROR, { code: 60002, message: ‘外部系统接口调用异常’ });error_map.set(ApiErrorNames.INTERFACE_FORBID_VISIT, { code: 60003, message: ‘该接口禁止访问’ });error_map.set(ApiErrorNames.INTERFACE_ADDRESS_INVALID, { code: 60004, message: ‘接口地址无效’ });error_map.set(ApiErrorNames.INTERFACE_REQUEST_TIMEOUT, { code: 60005, message: ‘接口请求超时’ });error_map.set(ApiErrorNames.INTERFACE_EXCEED_LOAD, { code: 60006, message: ‘接口负载过高’ });/ 权限错误:70001-79999 /error_map.set(ApiErrorNames.PERMISSION_NO_ACCESS, { code: 70001, message: ‘无访问权限’ });error_map.set(ApiErrorNames.INVALID_TOKEN, { code: 70002, message: ‘无效token’ });//根据错误名称获取错误信息ApiErrorNames.getErrorInfo = (error_name) => { var error_info; if (error_name) { error_info = error_map.get(error_name); } //如果没有对应的错误信息,默认’未知错误’ if (!error_info) { error_name = UNKNOW_ERROR; error_info = error_map.get(error_name); } return error_info;}//返回正确信息ApiErrorNames.getSuccessInfo = (data) => { var success_info; let name = ‘SUCCESS’; success_info = error_map.get(name); if (data) { success_info.data = data } return success_info;}module.exports = ApiErrorNames;创建controller文件夹对路由users进行逻辑编写const mysqlModel = require(’../lib/mysql’) //引入数据库方法const jwt = require(‘jsonwebtoken’)const config = require(’../config/default.js’)const ApiErrorNames = require(’../error/ApiErrorNames.js’)const moment = require(‘moment’)/* * 普通登录 /exports.login = async (ctx, next) => { const { body } = ctx.request try { const user = await mysqlModel.findUser(body.username) if (!user) { // ctx.status = 401 ctx.body = ApiErrorNames.getErrorInfo(ApiErrorNames.USER_NOT_EXIST) return } let bodys = await JSON.parse(JSON.stringify(user)) // 匹配密码是否相等 if ((await user.user_pwd) === body.password) { let data = { user: user.user_id, // 生成 token 返回给客户端 token: jwt.sign( { data: user.user_id, // 设置 token 过期时间 exp: Math.floor(Date.now() / 1000) + 60 * 60 // 60 seconds * 60 minutes = 1 hour }, config.secret ) } ctx.body = ApiErrorNames.getSuccessInfo(data) } else { ctx.body = ApiErrorNames.getErrorInfo(ApiErrorNames.USER_LOGIN_ERROR) } } catch (error) { ctx.throw(500) }}/* * 获取用户信息 /exports.info = async (ctx, next) => { const { body } = ctx.request // console.log(body) try { const token = ctx.header.authorization let payload if (token) { payload = await jwt.verify(token.split(’ ‘)[1], config.secret) // 解密,获取payload const user = await mysqlModel.findUserAndRole(payload.data) if (!user) { ctx.body = ApiErrorNames.getErrorInfo(ApiErrorNames.USER_NOT_EXIST) } else { let cont = user.user_count + 1 let updateInfo = [ cont, moment().format(‘YYYY-MM-DD HH:mm:ss’), user.id ] await mysqlModel .UpdataUserInfo(updateInfo) .then(res => { let data = { avatar: ‘https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', name: user.user_id, // roles: [user.user_admin === 0 ? ‘admin’ : ‘’] roles: [user.role_name] } ctx.body = ApiErrorNames.getSuccessInfo(data) }) .catch(err => { ctx.body = ApiErrorNames.getErrorInfo(ApiErrorNames.DATA_IS_WRONG) }) } } else { ctx.body = ApiErrorNames.getErrorInfo(ApiErrorNames.INVALID_TOKEN) } } catch (error) { ctx.throw(500) }}/* * 退出登录 */exports.logout = async (ctx, next) => { try { // ctx.status = 200 ctx.body = ApiErrorNames.getSuccessInfo() } catch (error) { ctx.throw(500) }}routes中的users.jsconst router = require(‘koa-router’)() //引入路由函数const userControl = require(’../controller/users’) //引入逻辑// const config = require(’../config/default.js’)router.get(’/’, async (ctx, next) => { ‘use strict’ ctx.redirect(’/user/login’)})// 路由中间间,页面路由到/,就是端口号的时候,(网址),页面指引到//user/loginrouter.get(’/user/info’, userControl.info)router.post(’/user/logout’, userControl.logout)router.post(’/user/login’, userControl.login)module.exports = router//将页面暴露出去备注ctx.request 获取post请求中的bodyctx.query.xx 获取get请求中的参数router.prefix(’/account’) 给router实例添加前缀 ...

March 17, 2019 · 8 min · jiezi

数据一致性(二)

我们流连于事物的表象,满足浅尝辄止的片刻欢愉,却几乎从不久留。我们在人生的道路上争先恐后,却吝于用片刻思考目标和方向。概述至今没有接触过MySQL多主的情况,即存在多个MySQL实例同时负责读写请求(抛弃只读库)。思考后认为:没有这么实现的技术难点在于:数据的一致性得不到保证。此外,还会涉及:MySQL采用自增主键索引的话,多主之间的数据同步简直是灾难。内部锁机制的优势大打折扣,跨主库间的锁应该也是灾难级别的吧。那么支持分布式的其他数据库又是怎么搞定这个问题的呢?比如Cassandra,多个节点之间可以同时处理读写请求,那么它是如何处理节点间数据同步以保证一致性的呢?MySQL数据的一致性We think this is an unacceptable burden to place ondevelopers and that consistency problems should be solved at the database level细细想想,MySQL自身实现的数据一致性也是相当复杂的。以Innodb举例,如果通过普通索引执行查询,首先获取到的仅仅是主键索引,后面还需要通过主键索引来获取完整的记录。查询如此,更新亦如此。Master-Slave模式通常情况下,MySQL部署都是一主多从。Master作为更新DB的入口,而Slave的数据通过binlog来进行同步。所以大胆想一想,有没有可能出现一种情况(假设id=1记录原始的name值为neojos):## 第一次同步数据update s-1 set name=“neojos-1” where id = 1; ## 失败update s-2 set name=“neojos-1” where id = 1; ## 成功update s-3 set name=“neojos-1” where id = 1; ## 成功## 第二次同步数据update s-1 set name=“neojos-2” where id = 1; ## 成功update s-2 set name=“neojos-2” where id = 1; ## 失败update s-3 set name=“neojos-2” where id = 1; ## 成功最后,数据库从某一个时间点开始,Master和Salve的数据会变得不一致了当然不可能,MySQL在数据同步上做了非常硬的约束。包括Slave_IO_Running、Slave_SQL_Running以及Seconds_Behind_Master等。并发下的数据一致性MySQL并发下的数据一致性是通过锁来保证的。并发的请求,谁先拿到X锁,谁就有修改的权限。锁类似扮演了一个操作版本号的作用。XIXSIS XConflictConflictConflictConflictIXConflictCompatibleConflictCompatibleSConflictConflictCompatibleCompatibleISConflictCompatibleCompatibleCompatible理解冲突以数据读取和写入为切入点,引申出两个工作中可能可能遇到的冲突问题,并通过加锁以及设置版本号来避免冲突的发生。Go的并发问题下面是一个简单的Go Test代码问题:求1-100的累加和。我们通过Goroutine和最普通的两个方式分别计算。同时,在代码的末尾对两种方式的计算结果进行了比较并打印输出。// 输出结果每次都是变化的。其中一次:5499 != 5050func TestSum1To100(t *testing.T) { result1 := 0 result2 := 0 // 并发的进行计算 var wg sync.WaitGroup for i := 1; i <= 100; i++ { wg.Add(1) go func(m int) { defer wg.Done() result1 += m }(i) } // 正常的For循环 for i := 1; i <= 100; i++ { result2 += i } wg.Wait() if result1 != result2 { t.Fatalf(" %d != %d", result1, result2) }}并发情况下,每个goroutine在读取result1到result1=result1+1的过程中,无法保证result1不被别的goroutine所修改。从MySQL解决问题的思路来考虑:加锁。我们要对读取result1到result1=result1+1的过程进行加锁,保证这个过程是同步的。一对多情况在国内第三方支付(微信/支付宝)场景中,用户是否支付了某个商品,是通过服务端接受第三方异步回调通知的方式,来作为判断依据的。而回调通知存在相应的重试策略,而且都要求幂等处理。假设下面一个场景,我们创建了以user_id为唯一索引的表(user_pay)用于统计该用户支付成功的次数,以及用户支付明细表(user_pay_detail),两者是一对多的对应关系。服务端每次收到第三方的支付回调,都在user_pay_detail追加一条新记录,同时相应的调整user_pay的信息。如果在回调过程中,存在这样一个场景:在03-02号收到了支付回调通知,对数据进行了调整。而在03-15号的时候却又收到了02-01的回调通知(该通知已经在02-01处理过了)。如何保证user_pay中的数据不会被多加一次?当然,解决办法非常简单。其中一个解决办法便是:在user_pay中记录上一次回调通知的时间戳,以此作为这行记录的版本号,后续也只有大于该版本号的通知才会被处理。CAP了解一下分布式的环境下的CAP定理,这里主要强调一下:Consistency。在分布式系统中,存在多节点同时对外提供读写服务,数据存储多份副本的情况。那么,这些节点在同步数据的过程中,可能会因为网络或者机器的原因导致数据同步失败,从而造成各个节点数据不一致的情况发生。Last-write-winsLast-write-wins表示在对一条记录应用多个修改的时候,最后的改动会覆盖掉之前的操作,返回给客户端的记录都以最后一次的改动为准。这也是分布式系统解决冲突的一个策略。基于timestamp的版本控制系统,比如HBase。每次操作都会给记录附加一个timestamp的版本号。这样一来,当某些数据发生冲突时,我们就可以简单的认为最新的记录是准确的。但实际上,基于Last-write-wins的策略并不一定是正确的。比如多个节点对同一条记录进行修改。首先,节点服务上的时间钟不是严格相等的;其次,客户端发出的请求时间,跟到达节点服务的时间也是没有任何联系的。vector clock先说一下需要用到向量时钟的场景。我们在写数据时候,经常希望数据不要存储在单点。如db1,db2都可以同时提供写服务,并且都存有全量数据。而client不管是写哪一个db都不用担心数据写乱问题。但是现实场景中往往会碰到并行同时修改。导致db1和db2数据不一致。于是乎就有人想出一些解决策略。向量时钟算是其中一种。简单易懂。但是并没有彻底解决冲突问题,现实分布式存储补充了很多额外技巧。文章vector clock 向量时钟算法解释的实在是太完美了,这里就不冗余解释了。下图是一个分布式服务的示例,各个节点都可以提供读写服务。Cassandra的思路KV类型的分布式数据库在存储对象时,存储的是对象序列化的结果。举个例子:有一个jbellis的对象,初始值为{’email’: ‘jbellis@example.com’, ‘phone’: ‘555-5555’},我们认为这个初始值为V0之后修改了jbellis的邮件地址,这时候值记作V1,{’email’: ‘jbellis@illustration.com’, ‘phone’: ‘555-5555’}。但因为网络或其他问题,在同步数据到其他节点的时候失败了,导致该修改仅仅被成功写到了其中一个节点上接着,我们更新jbellis中的电话信息。但我们读取到的jbellis是V0,所以,修改后的V3为{’email’: ‘jbellis@example.com’, ‘phone’: ‘444-4444’}从Last-write-wins的角度考虑,我们采纳了V2的值,而丢弃了V1。简单直接,但不一定正确;从vector clock的角度来看,当同步V2到其他节点时,就会发生数据冲突,因为当前节点的版本为[V0, V2],而其他节点的版本是[V0, V1],这时候就需要依靠具体的冲突解决策略。而Cassandra在存储数据结构上做了处理,将对象中email和phone单独存储,并给每个column都指定一个独立的timestamp作为版本号。这样,当冲突发生时,就可以简单运用Last-write-wins策略了。A column is the basic data structure of Cassandra with three values, namely key or column name, value, and a timestamp. Given below is the structure of a column.总结实事求是,具体问题具体分析。请记住,对你而言,上面这些方法可能都不合适。参考文章:vector clock 向量时钟算法Why Cassandra doesn’t need vector clocksCassandra - Data Model ...

March 17, 2019 · 1 min · jiezi

Python进阶:如何将字符串常量转化为变量?

前几天,我们Python猫交流学习群 里的 M 同学提了个问题。这个问题挺有意思,经初次讨论,我们认为它无解。然而,我认为它很有价值,应该继续思考怎么解决,所以就在私密的知识星球上记录了下来。万万没想到的是,在第二天,有两位同学接连给出了解决方法!由此,群内出现了一轮热烈的技术交流。本文将相关的内容要点作了梳理,并由此引申到更进一步的学习话题,希望对你有所帮助。1、如何动态生成变量名?M 同学的问题如下:打扰一下大家,请教一个问题,已知 list = [‘A’, ‘B’, ‘C’, ‘D’] , 如何才能得到以 list 中元素命名的新列表 A = [], B = [], C = [], D = [] 呢?简单理解,这个问题的意思是,将字符串内容作为其它对象的变量名。 list 中的元素是字符串,此处的 ‘A’-‘D’ 是常量 ,而在要求的结果中,A-D 是变量 。如果强行直接将常量当做变量使用,它会报错:>>> ‘A’ = []…SyntaxError: can’t assign to literal报错中的literal 指的是字面量 ,这是计算机科学中常见的一个概念,用于表达源代码中的固定值。 例如,整数、浮点数、字符串等基本类型,就是字面量。字面量指的就是一个量本身,可以理解为一种原子性的实体,当然不能再被赋值了。所以,取出的字符串内容,并不能直接用作变量名,需要另想办法。有初学者可能会想,list[0] = [] 行不行?当然不行,因为没有出现 A 。那 A = list[0] ,接着 A = [] 呢?那也不行,因为这里的 A 是你凭空定义出来的,而不是从已有条件中生成的。当时,群里只有两三个同学参与了讨论,我们没想到解决办法。但是,我觉得这个题目很有意思,值得玩味。因为,如果能解决这个问题,那就意味着可以不作预先定义,而是动态地生成变量名,这不仅能减少给变量取名的麻烦,还实现了自动编码!可以设想一下未来,人工智能在编写代码的时候,如果能根据已知条件,动态生成变量名,那编写代码的过程不就顺利多了么?(据说,现在已经有人工智能可以编写代码了,不知它在取变量名时,是用的什么方法?)2、办法总是有的最近,学习群里蒙混进来了几个打广告的,为此,我决定提高审核门槛,例如,用群里的问题来作个考核。万万没想到的是,第一个被考核到的 Q 同学,几乎不假思索地就说出了一个解决上述问题的思路。而偏偏就是那么巧 ,几乎在同时,群内的 J 同学给出了另外一个解决方法(他没看到群内的讨论,而是看到了知识星球的记录,才知道这个问题的)。也就是说,前一晚还以为无解的问题,在第二天竟得到了两种不同的解决方法!那么,他们的答案是什么呢?# J 同学的解答>>> list1 = [‘A’, ‘B’, ‘C’, ‘D’]>>> for i in list1:>>> globals()[i] = []>>> A[]这个方法通过修改全局命名空间,巧妙地“定义”出了新的变量。globals() 方法取出来的是一个字典,字符串 ‘A’ 是其中一个键值(key),而这个键值恰恰是全局命名空间中的一个变量,这就实现了从常量到变量的转化。在数据结构层面上,空列表 [] 作为一个值(value)跟它的字符串键值绑定在一起,而在运用层面上,它作为变量内容而跟变量名绑定在一起。看到这个回答的时候,我就突然想起来了,上个月转载过一篇《Python 动态赋值的陷阱》,讲的正是动态地进行变量赋值 的问题啊!我似乎只关注了 globals() 与 locals() 用法的区别,却没有真正地掌握它们的原初用途。J 同学说,他正是看了那篇文章,才学得了这个方法。这就有意思了,我分享了一个自己囫囵吞枣的知识,然后它被 J 同学吸收掌握,最后反馈回来解决了我的难题。我真切地感受到了知识分享的魅力:知识在流动中获得生命,在碰撞中锃亮色泽。 同时,我也真切地明白了一个互助的学习团体的好处:利人者也利己,互助者共同进步。3、动态执行代码的方法新进群的 Q 同学,提供了一个不同的答案:# Q 同学的解答>>> list1 = [‘A’, ‘B’, ‘C’, ‘D’]>>> for i in list1:>>> exec(f"{i} = []")>>> A[]他的写法用到了 Python 3.6 才引入的 f-strings 特性,事实上,在较低版本中,也是可以实现的,只需要保证 exec() 方法接收的参数是包含了变量 i 的字符串即可,例如这样写:# 以下代码可替换上例的第 4 行exec(i + " = []")# 或者:exec("{} = []".format(i))# 或者:exec(’ ‘.join([i, ‘= []’]))这几种写法的区别只是字符串拼接法的区别,关于如何拼接字符串,以及不同方法之间的区别,可参看《详解Python拼接字符串的七种方式》。Q 同学这个答案的核心在于 exec() 方法,它是内置的,用途是执行储存在字符串或文件中的代码段。 它的基础用法如下:>>> exec(‘x = 1 + 2’)>>> x3# 执行代码段>>> s = “”">>> x = 10>>> y = 20>>> sum = x + y>>> print(sum)>>> “”">>> exec(s)30看完了 exec() 的用法,我们再回来看 Q 同学的答案。for-循环中取出来的 i 是字符串,而拼接后的字符串经过 exec() 的处理,就获得了动态编写代码的效果。也就是说,因为字符串常量的内容被当做有效代码而执行了,其中的 ‘A’-‘D’ 元素,就取得了新的身份,变成了最终的 A-D 变量名。这个方法看起来很简单啊,可是由于 exec() 方法太生僻了,直到 Q 同学提出,我们才醒悟过来。注意:在 Python3 中,exec() 是个内置方法;而在 Python2 中,exec 是个语句(statement),另外有个 execfile() 方法,两者相合并,就成了 Python3 中的 exec() 方法。本文使用的是 Python3。4、总结抽象一下最初的问题,它实际问的是“如何将字符串内容作为其它对象的变量名”,更进一步地讲是——“如何将常量转化为变量 ”。使用直接进行赋值的静态方法,行不通。两位同学提出的方法都是间接的动态方法:一个是动态地进行变量赋值,通过修改命名空间而植入变量;一个是动态地执行代码,可以说是通过“走后门”的方式,安插了变量。 两种方法殊途同归,不管是白猫还是黑猫,它们都抓到了老鼠。这两种方法已经给我们带来了很有价值的启发,同时,因为它们,群内小伙伴们更是发散地讨论一些相关联的话题,例如:S 同学提出了另一种修改命名空间中变量的写法、L 同学提到了 eval() 的意义、eval() 与 exec() 的区别、我查到了为什么要慎用 eval() 、C 与 H 同学提到了 eval() 的安全用法……虽然,某些话题无法在群聊中充分展开,但是,这些话题知识的延展联系,大大地丰富了本文开头的问题,这一个微小的问题,牵连出来了两个大的知识体系。最后,真得感谢群内的这些爱学习的优秀的同志们!除了文中提及的,还有一些同学也做了积极贡献,大家都很给力!相关链接: 《Python 动态赋值的陷阱》《详解Python拼接字符串的七种方式》eval()、exec()及其相关函数:https://www.tuicool.com/wx/vE…公众号【Python猫】, 专注Python技术、数据科学和深度学习,力图创造一个有趣又有用的学习分享平台。本号连载优质的系列文章,有喵星哲学猫系列、Python进阶系列、好书推荐系列、优质英文推荐与翻译等等,欢迎关注哦。PS:后台回复“爱学习”,免费获得一份学习大礼包。 ...

March 17, 2019 · 1 min · jiezi

MySql(二)——字符集和比较规则

MySql(二)——字符集和比较规则一些重要的字符集ASCII共收录128个字符,包括空格、标点符号、数字、大小写字母和一些不可见字符。由于总共才128个字符,所以可以使用1个字节来进行编码ISO 8859-1共收录256个字符,是在ASCII字符集的基础上又扩充了128个西欧常用字符(包括德法两国的字母),也可以使用1个字节来进行编码。这个字符集也有一个别名latin1GB2312收录汉字6763个,其他文字符号682个。同时这种字符集又兼容ASCII字符集如果该字符在ASCII字符集中,则采用1字节编码,否则采用2字节编码这种表示一个字符需要的字节数可能不同的编码方式称为:变长编码方式GBK在收录字符范围上对GB2312字符集作了扩充,编码方式上兼容GB2312utf8收录地球上能想到的所有字符,而且还在不断扩充。这种字符集兼容ASCII字符集,采用变长编码方式,编码一个字符需要使用1~4个字节utf8只是Unicode字符集的一种编码方案,Unicode字符集可以采用utf8、utf16、utf32这几种编码方案,utf8使用1~4个字节编码一个字符,utf16使用2个或4个字节编码一个字符,utf32使用4个字节编码一个字符。MySQL中支持的字符集和排序规则MySQL中的utf8和utf8mb4utf8mb3:阉割过的utf8字符集,只使用1~3个字节表示字符utf8mb4:正宗的utf8字符集,使用1~4个字节表示字符在MySQL中utf8是utf8mb3的别名字符集的查看SHOW (CHARACTER SET|CHARSET) [LIKE 匹配的模式]比较规则的查看SHOW COLLATION [LIKE 匹配的模式]后缀英文释义描述_aiaccent insensitive不区分重音_asaccent sensitive区分重音_cicase insensitive不区分大小写_cscase sensitive区分大小写_binbinary以二进制方式比较字符集和比较规则的应用1. MySQL有4个级别的字符集和比较规则服务器级别数据库级别表级别列级别2. 服务器级别服务器级别的字符集:SHOW VARIABLES LIKE ‘character_set_server’服务器级别的比较规则:SHOW VARIABLES LIKE ‘collation_server’写入配置文件:[server]character_set_server=utf8collation_server=utf8_general_ci3. 数据库级别当前数据库的字符集:SHOW VARIABLES LIKE ‘character_set_database’当前数据库的比较规则:SHOW VARIABLES LIKE ‘collation_database’创建和修改数据库的时候数据库的字符集和比较规则CREATE DATABASE 数据库名 [[DEFAULT] CHARACTER SET 字符集名称] [[DEFAULT] COLLATE 比较规则名称];ALTER DATABASE 数据库名 [[DEFAULT] CHARACTER SET 字符集名称] [[DEFAULT] COLLATE 比较规则名称];创建数据库不指定字符集和比较规则,则默认使用服务器级别的字符集和比较规则4. 表级别CREATE TABLE 表名 (列的信息) [[DEFAULT] CHARACTER SET 字符集名称] [COLLATE 比较规则名称]]ALTER TABLE 表名 [[DEFAULT] CHARACTER SET 字符集名称] [COLLATE 比较规则名称]如果创建和修改表的语句中没有指明字符集和比较规则,将使用该表所在数据库的字符集和比较规则作为该表的字符集和比较规则5.列级别CREATE TABLE 表名( 列名 字符串类型 [CHARACTER SET 字符集名称] [COLLATE 比较规则名称], 其他列…);ALTER TABLE 表名 MODIFY 列名 字符串类型 [CHARACTER SET 字符集名称] [COLLATE 比较规则名称];对于某个列来说,如果在创建和修改的语句中没有指明字符集和比较规则,将使用该列所在表的字符集和比较规则作为该列的字符集和比较规则6.仅修改字符集或仅修改比较规则只修改字符集,则比较规则将变为修改后的字符集默认的比较规则只修改比较规则,则字符集将变为修改后的比较规则对应的字符集7.各级别字符集和比较规则小结如果创建或修改列时没有显式的指定字符集和比较规则,则该列默认用表的字符集和比较规则如果创建或修改表时没有显式的指定字符集和比较规则,则该表默认用数据库的字符集和比较规则如果创建或修改数据库时没有显式的指定字符集和比较规则,则该数据库默认用服务器的字符集和比较规则客户端和服务器通信中的字符集从发送请求到返回结果这个过程中伴随着多次字符集的转换,在这个过程中会用到3个系统变量系统变量描述character_set_client服务器解码请求时使用的字符集character_set_connection服务器运行过程中使用的字符集character_set_results服务器向客户端返回数据时使用的字符集服务器认为客户端发送过来的请求是用character_set_client编码的服务器将把得到的结果集使用character_set_results编码后发送给客户端character_set_connection只是服务器在处理请求时使用的字符集,它是什么其实没多重要,但是一定要注意,该字符集包含的字符范围一定涵盖请求以及结果集中的字符,要不然会出现无法将请求中的字符编码成character_set_connection字符集或者无法编码结果集中的字符SET NAMES 字符集名等价于:SET character_set_client = 字符集名;SET character_set_connection = 字符集名;SET character_set_results = 字符集名;如果想写进配置文件:[client]default-character-set=utf8 ...

March 16, 2019 · 1 min · jiezi

服务器系统优化

1、数据库物理机采购CPU: 64位CPU,一台机器2-16颗CPU。至少2-4颗,L2(缓存)越大越好 内存: 96-128G,MySQL 3-4个实例。32-64G,1-2实例 硬盘:机械:选SAS,数量越多越好,转速越高越好15k 性能:SSD(高并发) > SAS(普通业务线上) >SATA(线下) 选SSD:使用SSD或者PCIe SSD设备,可提升上千倍的IOPS效率。 随机IO:SAS单盘能力300IOPS SSD随机IO:单盘能力可达35000IOPS Flashcache HBA卡raid磁盘阵列: 4快盘:RAID0>RAID1(推荐)>RAID5(少用)>RAID1 主库选择raid10,从库可选raid5/raid0/raid10,从库配置等于或大于主库 网卡:使用多块网卡bond,以及buffer,tcp优化 千兆网卡及千兆、万兆交换机 提示: 数据库属于IO密集型服务,硬件尽量不要使用虚拟化。 Slave硬件要等于或大于Master的性能2、企业案例:百度:某部门IBM服务器为48核CPU,内存96GB,一台服务器跑34个实例: sina:服务器是DELL R510居多,CPU是E5210,48GB内存,硬盘12*300G SAS,做RAID103、服务器硬件配置调整(1)服务器BIOS调整: 提升CPU效率参考设置: a.打开Perfirmance Per Watt Optimeized(DAPC)模式,发挥CPU最大性能,数据库通常需要高运算量 b.打开CIE和C States等选项,目的也是为了提升CPU效率 c. Memory Frequency(内存频率)选择Maximum Performance(最佳性能) d.内存设置菜单中,启动Node Interleaving,避免NUMA问题 (2)阵列卡调整: a.购置阵列卡同时配备CACHE及BBU模块(机械盘) b.设置阵列写策略为WEB,甚至OFRCE WB (对数据安全要求高)(wb指raid卡的写策略:会写(write back)) c.严禁使用WT策略,并且关闭阵列预读策略2操作系统层面优化1.操作系统及MySQL实例选择1.一定要选择x86_64系统,推荐使用CentOS6.8 linux,关闭NUMA特性 2.将操作系统和数据分开,不仅仅是逻辑上,还包括物理上 3.避免使用Swap交换分区 4.避免使用软件磁盘阵列 5.避免使用LVM逻辑卷 6.删除服务器上未使用的安装包和守护进程2.文件系统层优化(1)调整磁盘Cache mode启用WCE=1(Write Cache Enable),RCD=0(Read Cache Disable)模式命令:sdparm -s WCE=1,RCD=0 -S /dev/sdb(2)采用Linux I/O scheduler算法deadlinedeadline调度参数 对于Centos Linux建议 read_expire = 1/2 write_expireecho 500 > /sys/block/sdb/queue/iosched/read_expireecho 1000 > /sys/block/sdb/queue/iosched/write_expireLinux I/O调度方法 Linux deadline io 调度算法(3)采用xfs文件系统 业务量不是很大也可采用ext4,业务量很大,推荐使用xfs:调整XFS文件系统日志和缓冲变量 XFS高性能设置 (4)mount挂载文件系统 增加:async,noatime,nodiratime,nobarrier等noatime访问文件时不更新inode的时间戳,高并发环境下,推线显示应用该选项,可以提高系统I/O性能async写入时数据会先写到内存缓冲区,只到硬盘有空档才会写入磁盘,这样可以提升写入效率!风险为若服务器宕机或不正常,会损失缓冲区中未写入磁盘的数据 解决办法:服务器主板电池或加UPS不间断电源nodiratime不更新系统上的directory inode时间戳,高并发环境,推荐显示该应用,可以提高系统I/O性能 nobarrier不使用raid卡上电池 (5)Linux 内核参数优化 1.将vm,swappiness设置为0-10 2.将vm,dirty_background_ratio设置为5-10,将vm,dirty_ratio设置为它的两倍左右,以确保能持续将脏数据刷新到磁盘,避免瞬间I/O写,产生严重等待3.优化TCP协议栈减少TIME_WAIT,提高TCP效率net.ipv4.tcp_tw_recyle=1net.ipv4.tcp_tw_reuse=1减少处于FIN-WAIT-2连接状态的时间,使系统可以处理更多的连接net.ipv4.tcp_fin_timeout=2减少TCP KeepAlived连接侦测的时间,使系统可以处理更多的连接。net.ipv4.tcp_keepalived_time=600提高系统支持的最大SYN半连接数(默认1024)net.ipv4.tcp_max_syn_backlog = 16384减少系统SYN连接重试次数(默认5)net,ipv4.tcp_synack_retries = 1net.ipv4.tcp_sync_retries = 1在内核放弃建立的连接之前发送SYN包的数量net.ipv4.ip_local_prot_range = 4500 65535允许系统打开的端口范围4.网络优化优化系统套接字缓冲区Increase TCP max buffer sizenet.core.rmem_max=16777216 #最大socket读buffernet.core.wmem_max=16777216 #最大socket写buffernet.core.wmem_default = 8388608 #该文件指定了接收套接字缓冲区大小的缺省值(以字节为单位)net.core.rmem_default = 8388608优化TCP接收/发送缓冲区Increase Linux autotuning TCP buffer limitsnet.ipv4.tcp_rmem=4096 87380 16777216net.ipv4.tcp_wmem=4096 65536 16777216net.ipv4.tcp_mem = 94500000 915000000 927000000优化网络设备接收队列net.core.netdev_max_backlog=30005.其他优化net.ipv4.tcp_timestamps = 0net.ipv4.tcp_max_orphans = 3276800net.ipv4.tcp_max_tw_buckets = 360000提示:面试的时候说框架,然后说一两个小的优化参数即可 更多内核可以参考“跟老男孩学运维书的第三章”以及我们的博客,近期将会更新3MySQL数据库层面优化my.cnf参数优化此优化主要针对innodb引擎如果采用MyISAM引擎,需要key_buffer_size加大。强烈推荐采用innodb引擎,default-storage-engine=Innodb调整innodb_buffer_pool_size大小,考虑设置为物理内存的50%-60%左右根据实际需要设置inno_flush_log_at_trx_commit,sync_binlog的值。如果要需要数据不能丢失,那么两个都设为1.如果允许丢失大一点数据,则可分别设为2和0,在slave上可设为0设置innodb_file_per_table = 1,使用独立表空间设置innodb_data_file_path = ibdata1:1G:autoextend,不要使用默认的10%设置innodb_log_file_size=256M,设置innodb_log_files_in_group=2,基本可满足90%以上的场景;不要将innodb_log_file_size参数设置太大,这样可以更快同时又更多的磁盘空间,丢掉多的日志通常是好的,在数据库崩溃后可以降低恢复数据库的事件设置long_query_time = 1记录那些执行较慢的SQL,用于后续的分析排查;根据业务实际需要,适当调整max_connection(最大连接数max_connection_error(最大错误数,建议设置为10万以上,而open_files_limit、innodb_open_files、table_open_cache、table_definition_cache这几个参数则可设为约10倍于max_connection的大小;)不要设置太大,会将数据库撑爆tmp_table_szie、max_heap_table_size、sort_buffer_size、join_buffer_size、read_buffer_size、read_rnd_buffer_size等都是每个连接session分配的,因此不能设置过大建议关闭query cache功能或降低设置不要超过512M更多内核参数:my-innodb-heavy-4G.cnf 配置文件参数介绍 MySQL工具mysqlreport 我们可以使用工具来分析MySQL的性能 如何才能做到网站高并发访问? 生产环境中对于防范DDOS攻击的讨论 面试可能会问到DOOS 攻击防护关于库表的设计规范推荐utf-8字符集,虽然有人说谈没有latin1快固定字符串的列尽可能多用定长char,少用varchar存储可变长度的字符串使用VARCHAR而不是CAHR—节省空间,因为固定长度的CHAR,而VARCHAR长度不固定(UTF8不愁此影响)所有的InnoDB表都设计一个无业务的用途的自增列做主键字段长度满足需求前提下,尽可能选择长度小的字段属性尽量都加NOT NULL约束对于某些文本字段,例如“省份”或者“性别”我们可以将他们定义为ENUM类型尽可能不使用TEXT/BLOB类型,确实需要的话,建议拆分到子表中,不要和主表放在一起,避免SELECT *的时候读性能太差。读取数据时,只选取所需要的列,不要每次都SELECT * 避免产生严重的随机读问题,尤其是读到一些TEXT/BLOB类型,确实需要的话,建议拆分到子表中,不要和主表放在一起,避免SELECT 的时候读性能太差对一个VARCHAR(N)列创建索引时,通常取其50%(甚至更小)左右长度创建前缀索引就足以满足80%以上的查询需求了,没必要创建整列的全长度索引。多用符合索引,少用多个独立索引,尤其是一些基础(Cardinality)太小(如果说:该列的唯一值总数少于255)的列就不要创建独立索引了。4SQL语句的优化索引优化 1)白名单机制一百度,项目开发啊,DBA参与,减少上线后的慢SQL数据 抓出慢SQL,配置my.cnflong_query_time = 2log-slow-queries=/data/3306/slow-log.loglog_queries_not_using_indexs按天轮询:slow-log.log2)慢查询的日志分析工具——mysqlsla或pt-query-digest(推荐)pt-quey-diges,mysqldumpslow,mysqlsla,myprofi,mysql-explain-slow-log,mysqllogfileter3)每天晚上0点定时分析慢查询,发到核心开发,DBA分析,及高级运维,CTO的邮箱里 DBA分析给出优化建议–>核心开发确认更新–>DBA线上操作处理 4)定期使用pt-duplicate-key-checker检查并删除重复的索引 定期使用pt-index-usage工具检查并删除使用频率很低的索引 5)使用pt-online-schema-change来完成大表的ONLINE DDL需求 6)有时候MySQL会使用错误的索引,对于这种情况使用USE INDEX 7)使用explain及set profile优化SQL语句大的复杂的SQL语句拆分成多个小的SQL语句 子查询,JOIN连表查询,某个表4000万条记录 数据库是存储数据的地方,但不是计算数据的地方 对数据计算,应用类处理,都要拿到前端应用解决。禁止在数据库上处理 搜索功能,like ‘%oldboy%’ 一般不要用MySQL数据库 使用连接(JOIN)来代替子查询(Sub_Queries) 避免在整个表上使用cout(),它可能锁住整张表 多表联接查询时,关联字段类型尽量一致,并且都要有索引。 在WHERE子句中使用UNION代替子查询 多表连接查询时,把结果集小的表(注意,这里是指过滤后的结果集,不一样是全表数据量小的)作为驱动表爬虫获取数据的过程5网站集群架构上的优化1.服务器上跑多实例,2-4个(具体需要看服务器的硬件信息)2.主从复制一主五从,采用mixed模式,尽量不要跨机房同步(进程远程读本地写)3.定期使用pt-table-checksum、pt-table-sync来检查并修复mysql主从复制的数据差异4.业务拆分:搜索功能,like ‘%oldboy% ’ 一般不要用MySQL数据库5.业务拆分:某些业务应用使用nosql持久化存储,例如:memcached、redis、ttserver例如粉丝关注,好友关系等6.数据库前端必须要加cache,例如:memcached,用户登录,商品查询7.动态的数据库静态化,整个文件静态化,页面片段静态化8.数据库集群与读写分离。一主多从,通过程序或dbproxy进行集群读写分离9.单表超过800万,拆库拆表。人工拆表拆库(登录、商品、订单)10.百度、阿里国内前三公司,会选择从库进行备份,对数据库进行分库分表6MySQL 流程、制度控制优化任何一次人为数据库记录的更新,都要走一个流程:a.人的流程:开发–>核心开发–>运维或DBAb.测试流程:内网测试–>IDC测试–>线上执行c.客户端管理,phpmyadminMySQL基础安全1.启动程序700,属主和用户组为MySQL2.为MySQL超级用户root设置密码3.如果要求严格可以删除root用户,创建其他管理用户,例如admin4.登录时尽量不要在命令行暴露密码,备份脚本中如果有密码,给设置700,属主和密码组为mysql或root5.删除默认存在的test库6.初始删除无用的用户,只保留| root | 127.0.0.1 || root | localhost |7.不要一个用户管理所有的库,尽量专库专用户8.清理mysql操作日志文件/.mysql_history(权限600,可以不删)9.禁止开发获得到web连接的密码,禁止开发连接操作生产对外的库10.phpmyadmin安全11.服务器禁止设置外网IP12.防SQL注入(WEB)php.ini或web开发插件监控,waf控制 ...

March 16, 2019 · 1 min · jiezi

MySQL 查询in操作,查询结果按in集合顺序显示

MySQL 查询in操作,查询结果按in集合顺序显示 复制代码 代码如下:select * from test where id in(3,1,5) order by find_in_set(id,‘3,1,5’); select * from test where id in(3,1,5) order by substring_index(‘3,1,2’,id,1);

March 16, 2019 · 1 min · jiezi

ECS实例搭建阿里云RDS实例的从库

准备工作1、安装mysql,版本要和RDS实例的版本一致; 2、安装mysql备份恢复工具percona-xtrabackup,下载地址为https://www.percona.com/downl…3、rds上创建一个普通账户,无需分配任何库的权限,用于从库同步使用; 4、添加ecs实例IP至RDS白名单;5、下载RDS的全量备份至ECS实例,全量备份坐标见下图;从备份数据恢复1、添加下述参数至mysql配置文件的[mysqld]标签下;server-id = 2156239584log-bin=mysql-binrelay-log=relay-bingtid_mode=onenforce_gtid_consistency=onbinlog_format=row log_slave_updates=12、解压下载的全量备份至临时目录,自己可以随意指定,本文指定为/data/tmp3、恢复数据innobackupex –apply-log /data/tmp/innobackupex –copy-back /data/tmp/4、修改mysql配置文件为跳过授权,即添加下述配置至[mysqld]标签下skip-grant-tables如下图所示5、修改mysql数据文件夹的属主为mysql用户,我的mysql数据是放在/data/mysql,如果不特殊指定的话是在/var/lib/mysqlchown -R mysql. /data/mysql6、启动mysql服务service mysqld start7、清空mysql库下的slave相关表信息use mysql;truncate slave_gtid_info;truncate slave_master_info;truncate slave_relay_log_info;truncate slave_worker_info;8、修改root账户密码update mysql.user set password=password(‘Your_password’) where user=‘root’;9、注释或删除跳过授权表的配置并重新启动mysql,如下图所示service mysqld restart8、配置主从再次登录mysql数据库时请指定-h 127.0.0.1reset slave;change master to master_host=‘rm-xxxxxxxx.mysql.rds.aliyuncs.com’,master_port=3306,master_user=‘xxxxxxxx’,master_password=‘xxxxxxxx’,master_auto_position=0;主库的二进制文件名及当前备份的position编号记录在/data/tmp/xtrabackup_slave_filename_info,如下图复制下述语句时注意去除MATSER_LOG_POS后面值两边的单引号CHANGE MASTER TO MASTER_LOG_FILE=‘mysql-bin.000409’, MASTER_LOG_POS=95322;最后一步就是启动同步,并查看同步状态start slave;show slave status\G;确认Slave_IO_Running、Slave_SQL_Running的值均为Yes即可,如下图所示注意点按照上述步骤操作完成后,mysql这个库是不会和RDS同步的,也就意味着从库的账户和授权和RDS没有任何关系,原因为恢复后slave的配置中配置忽略mysql库,如下图所示

March 15, 2019 · 1 min · jiezi

复选框checked

为了选中复选框,添加dto字段checked来作为标签属性SELECT *,NOT ISNULL(bb.id) checked FROM bid_filetype bf LEFT JOIN bid_bidprojectfilet bb ON bb.filetype=bf.id AND bb.bidid = #{value} where bf.id !=1其实这个不单单运作于此,还有用比如jfinal代码实现的put进对象中,然后通过这个键值对判断按钮是否需要显示什么内容(同时实现什么样的事件)

March 15, 2019 · 1 min · jiezi

一次mysql版本过高导致的bug

起因接了一个毕设,经过漫长的规划,开发,测试后,终于可以交给用户了。准备好java8,mysql8,tomcat8。并将war包导入到tomcat并启动项目,然后就一直连接不上数据库## 解决方法首先检查数据库是否开启。开启的然后检查数据库连接,驱动,用户名,密码,没问题最后才想到的版本。因为在我的电脑上是能跑起来的,我的电脑mysql是5.7。给用户装的mysql8.重装数据库。解决

March 14, 2019 · 1 min · jiezi

数据库备份之lvm快照热备

使用的是虚拟机环境,先在虚拟机中添加一块儿新的硬盘。然后给硬盘分区fdisk -l #查看分区信息pvcreate mysql_pv /dev/sdb #创建物理卷vgcreate vgmysql /dev/sdb #创建卷组lvcreate -L 1024M -n lv0 vgmysql #创建逻辑卷mkfs.ext4 /dev/vgmysql/lv0 #格式化挂载mount /dev/vgmysql/lv0 /mnt #挂载逻辑卷service mysqld stop #关闭mysql服务cp -a /var/lib/mysql/* /mnt #将数据库数据拷贝到mnt目录下umount /dev/vgmysql/lv0 #移除挂载信息mount /dev/vgmysql/lv0 /var/lib/mysql/ #将逻辑卷与Mysql数据目录挂载service mysqld start #重启mysqld服务设置开机自动挂载,在/etc/fstab文件中配置。最好不要采用在 /etc/fstab直接指定分区(如/dev/sdb1)的方法,因为设备的顺序编码在关闭或者开启服务器过程中可能发生改变,例如/dev/sdb1可能会变成/dev/sdb2。推荐使用UUID来配置自动挂载数据盘。查询磁盘分区的UUIDblkid /dev/sdb1UUID=一串数字:要挂载的磁盘分区的UUID/var/lib/mysql:挂载目录ext4:分区格式为ext4defaults:挂载时所要设定的参数(只读,读写,启用quota等),输入defaults包括的参数有(rw、dev、exec、auto、nouser、async) 0:使用dump是否要记录,0为不需要,1为需要 2:2是开机时检查的顺序,boot系统文件为1,其他文件系统都为2,如不要检查就为0在给lv0做快照之前,先使用FLUSH TABLES和FLUSH TABLES WITH READ LOCK强行将所有OS的缓冲数据写入磁盘(类似于操作系统的sync命令),同时将数据库置为全局只读。快照完成之后,再使用UNLOCK TABLES解锁。然后只需要将快照进行挂载,复制其中数据,在复制完成之后删除该快照即可! /bin/bashTIMESTAMP=date +%Y%m%d%H%M%SHOSTNAME=“127.0.0.1"USERNAME=“root"PASSWORD=“12345678"MOUNT=/bin/mountUMOUNT=/bin/umountLVCREATE=/usr/sbin/lvcreateLVREMOVE=/usr/sbin/lvremoveMYSQL=/usr/bin/mysqlTAR=/bin/tarSNAP_SIZE=1024mSNAP_MYSQL=/dev/vgmysql/lv0MOUNT_POINT=/mntEXEC_MySQL=“FLUSH TABLES;FLUSH TABLES WITH READ LOCK;FLUSH LOGS;SNAP_MYSQL /dev/vgmysql/lv0UNLOCK TABLES;“echo “$EXEC_MySQL” | $MYSQL-u$USERNAME-p$PASSWORD-h$HOSTNAME$MOUNT $SNAP_MYSQL $MOUNT_POINTcd /root$TAR -zcvf ${TIMESTAMP}.tgz $MOUNT_POINT$UMOUNT $MOUNT_POINT$LVREMOVE -f $SNAP_MYSQL

March 14, 2019 · 1 min · jiezi

MySQL 配置参数 -- logs-slave-updates

logs-slave-updates 参数主要在多主多从的集群架构中开启,否则会导致各从实例无法完整同步集群的全量数据的问题。多主多从集群架构:masterA → slaveA↑ ↓masterB → slaveBlogs-slave-updates:Normally, a slave does not log to its own binary log any updates that are received from a master server. This option tells the slave to log the updates performed by its SQL thread to its own binary log.即,正常情况下,一个slave节点是不会将其从master节点同步的数据更新操作记录至自己的二进制日志bin-log中的。在多主的场景下,各master节点其实又相互作为另一方的slave节点进行着数据的一致性同步操作。例如 masterA 会以slave的角色同步 masterB 上的数据,masterB 也会以slave的角色同步 masterA 上的数据,如果没有开启 logs-slave-updates参数配置,则masterA \ masterB 虽然也能保证数据的一致性和完整性,但二者的 bin-log 中都只记录了作用在自身实例上的数据更新操作。例如:masterA insert row1 bin-logA add row1masterB insert row2 bin-logB add row2masterA replicate row2 from masterB But bin-logA will not log this updatemasterB replicate row1 from masterA But bin-logB will not log this updateslaveA replicate row1 form bin-logAslaveB replicate row2 form bin-logB因为主从复制是使用 bin-log 完成的,masterA masterB 互补同步数据时并没有从对方同步的数据写入自己的bin-log,则会导致自己的从实例只能同步到集群的部分数据。多从一从在多主一从模式下,logs-slave-updates就没那么必须了,各主实例只需维护好自身的 bin-log,从实例则分别读取各主实例的bin-log汇总集群的全量数据,还可以一定层度上提高集群性能。但为了保证容灾恢复,还是要尽可能的保证logs-slave-updates的开启,否则每台主实例都只有自身数据更新的bin-log,都只能恢复集群数据的一部分,虽然也可以只恢复各自的bin-log再全量同步其他主实例的数据,但相对麻烦些。 ...

March 14, 2019 · 1 min · jiezi

linux yum 安装 mysql

下载mysql库先到mysql官网找到需要安装的版本https://dev.mysql.com/downloa…然后再linux上执行 wget 将对应的rpm下载下来shell> wget https://dev.mysql.com/get/mysql80-community-release-el7-2.noarch.rpmyum 安装mysql把下载下来的包添加到yum库shell> rpm -Uvh mysql80-community-release-el7-2.noarch.rpm看一下是否添加成功了shell> yum repolist all | grep mysql可以开始安装了shell> yum -y install mysql-community-server安装成功后,启动服务,并且查看mysql的状态shell> systemctl start mysqld.serviceshell> systemctl status mysqld.service修改root密码好了,到现在,已经成功安装并启动mysql了,接下来要配置一下root账号的密码MySQL服务器初始化(从MySQL 5.7开始):在服务器初始启动时,如果服务器的数据目录为空,则会发生以下情况:服务器初始化。在数据目录中生成SSL证书和密钥文件。安装alidate_password插件并启用。将’root’@’localhost’创建一个超级用户帐户。设置超级用户的密码并将其存储在错误日志文件中。所以我们要去错误日志中找到初始随机的root密码shell> grep ’temporary password’ /var/log/mysqld.log知道密码了,现在用这个密码登录,再修改一个好记的密码吧shell> mysql -u root -pmysql> ALTER USER ‘root’@’localhost’ IDENTIFIED BY ‘你的新密码’;注意MySQL的 validate_password 插件默认安装。这将要求密码包含至少一个大写字母,一个小写字母,一个数字和一个特殊字符,并且密码总长度至少为8个字符。远程登录实际开发中,会使用MySQL Workbench、Navicat类的数据库连接工具,但第一次连接时会失败报错:1130-host … is not allowed to connect to this MySql server这是因为该账号不允许远程登录。可以先看下各个账号的访问权限mysql> use mysqlmysql> select host, user from user;+———–+——————+| host | user |+———–+——————+| localhost | mysql.infoschema || localhost | mysql.session || localhost | mysql.sys || localhost | root |+———–+——————+可以看到,目前所有的账号都只持本地登录。只需要修改host的值为你的远程登录ip就可以了,为了方便可以设置成%。修改成功后,刷新权限就可以远程访问了mysql> update user set host = ‘%’ where user = ‘root’;mysql> flush privileges;参考资料使用MySQL Yum存储库的快速指南 ...

March 14, 2019 · 1 min · jiezi

一些程序员值得看的电子书

https://github.com/guanhui07/… 自取 上传到这里

March 14, 2019 · 1 min · jiezi

重磅预告 | 今晚直播:MyCat的坑如何在分布式中间件DBLE上改善

上周,DBLE团队历时3个月准备的开源MySQL分布式中间件DBLE系列公开课发布了,为使社区同学能够更好的评估课程内容、质量以及对DBLE有更清晰深入的认知,我们联合IT168将在第二节课程发布前开放一期直播,跟大家聊聊DBLE与MyCat错综复杂的故事。直播时间:3月14日(今晚)20:00PM分享嘉宾:DBLE项目负责人 蓝寅分享主题:《MyCat的坑如何在分布式中间件DBLE上改善》课程亮点跟你分享MyCat不为人知的一面线上的复杂查询其实结果不对?被MyCat默默吃掉的那些SQL!MyCat不太支持的那些语法!不严谨代码导致的那些bug!内容简介分享将从DBA与研发两个角度进行说明,对于DBA同学最关心的SQL语言实现以及运维管理应当如何评估;开发者同学十分关注的bug修复质量,代码质量及代码科学管理如何判断,通过拆解两方对中间件的需求来吐槽MyCat到底有多少坑以及DBLE是如何避免的,为大家在MySQL的“读写分离”、“分库分表”等架构选型方面提供指引。课程提纲一.DBA角度SQL语言实现 数据查询: 简单查询 / 聚合函数查询 / 函数嵌套查询 / union 查询 / 子查询 / Join查询 / 跨表Join查询 数据操作:Insert / 全局序列 上下文设置:上下文变量运维管理 用户权限:管理端用户权限二.开发角度1.bug修复质量2.代码半成品残留3.代码灌水4.伪造实现直播课程参与方式关注「爱可生开源社区」,回复关键字 “ 直播 ”即可成功报名啦!今晚20:00,一起相约微课堂,关于DBLE公开课或者DBLE的使用问题反馈一键直达DBLE研发团队。开源分布式中间件DBLE社区官网:https://opensource.actionsky….GitHub主页:https://github.com/actiontech…技术交流群:669663113开源数据传输中间件DTLE社区官网:https://opensource.actionsky….GitHub主页:https://github.com/actiontech…技术交流群:852990221

March 14, 2019 · 1 min · jiezi

CentOS 7安装MySQL8.0

下载rpm包: wget https://dev.mysql.com/get/mys…安装rpm包sudo chmod 755 mysql80-community-release-el7-2.noarch.rpmsudo yum install mysql80-community-release-el7-2.noarch.rpmsudo yum update安装MySQLsudo yum install mysql-community-server启动MySQL,并设置开机自动启动sudo systemctl start mysqldsudo systemctl enable mysqld与安装MySQL5.7不同,MySQL8.0安装过程中没有设置密码操作,MySQL自带root用户,root用户密码在MySQL启动时会写入日志文件中,可以使用一下命令查看:cat /var/log/mysqld.log | grep password使用日志文件中的密码后需要修改root密码才能对数据库进行操作.mysql -u root -p # 然后输入日志文件中的密码ALTER USER ‘root’@’localhost’ IDENTIFIED BY ‘新密码’; # 新密码必须符合MySQL8.0 密码策略,需要有一定的强度,否则会失败设置远程主机可以访问数据库USE mysqlUPDATE user SET host=’%’ WHERE user=‘root’ # 修改root用户可以远程登录GRANT ALL PRIVILEGES ON . to ‘root’@’%’ WITH GRANT OPTION # root用户将拥有 对所有表操作的权限FLUSH PRIVILEGES # 刷新权限增加用户USE mysqlCREATE USER username IDENTIFIED BY ‘password’;UPDATE user SET host=’%’ WHERE user=‘username’ # 用户可以远程登录GRANT EXECUTE,INSERT,SELECT,UPDATE /ALL PRIVILEGES ON 数据库名.数据表(可以用*代表所有) TO ‘username’@’%’;FLUSH PRIVILEGES查看用户权限SHOW GRANTS FOR usernameWITH GRANT OPTION和 WITH admin OPTION使用了with grant option和with admin option的被授权用户user1可以将自己获得的权限授予 其他用户(user2),不同在于,取消user1的权限时,如果user1是通过with grant option授权的, 则user2的权限也会被删除即级联删除权限,with admin option则是非级联删除权限,删除user1 的权限,user2的权限不会删除.参考:https://www.jb51.net/article/…https://www.cnblogs.com/testw...https://blog.csdn.net/dongdon… ...

March 14, 2019 · 1 min · jiezi

Vitess安全审计结果(pdf)

作者:Adrianna Tan我们很高兴地宣布,Vitess最近接受了由CNCF/Linux基金会资助的安全评估。2019年2月,来自Cure53的团队在以下领域进行了测试:系统复杂性云基础设施源代码审计操作系统交互低级协议分析多角度渗透测试此独立安全审核是在本地安装的系统以及基于Kubernetes的群集上执行的。审计员执行:(1)手动代码审计和(2)代码辅助渗透测试。以下是一些亮点。“在Cure53看来,有明确的意图和后续行动提供一个用于扩展MySQL数据库的安全系统。这是通过保持攻击面最小,并选择适合此实现的语言来实现的。审计员设法覆盖了与Vitess软件系统主存储库相关的所有方面的广泛覆盖范围。选择最有可能被攻击的途径并验证其恢复能力。”“这次由CNCF/Linux基金会资助的Cure53评估,结果证明了Vitess数据库缩放器是安全可靠的。通过限制攻击面,适当关注用户提供的输入和安全驱动的最佳实践,以及,在某种程度上,使用Go语言生态系统,可以实现这一非常好的结果。”“虽然这次评估的结果很少,而且可能暗示某种测试限制,但实际上它们证明了Vitess团队能够兑现他们所做出的安全承诺。”审核员给Vitess团队确认了三个改进地方。我们感谢Cure53、CNCF、Linux基金会和所有项目贡献者的帮助。点击下载完整报告的pdf。KubeCon + CloudNativeCon + Open Source Summit大会日期:会议日程通告日期:2019 年 4 月 10 日会议活动举办日期:2019 年 6 月 24 至 26 日KubeCon + CloudNativeCon和Open Source Summit赞助方案KubeCon + CloudNativeCon和Open Source Summit多元化奖学金现正接受申请KubeCon + CloudNativeCon和Open Source Summit即将首次合体落地中国KubeCon + CloudNativeCon和Open Source Summit购票窗口,立即购票!CNCF邀请你加入最终用户社区

March 13, 2019 · 1 min · jiezi

【每日学习记录】使用录像设备记录每天的学习

在这里使用学而思网校的录像设备,记录每天学习的内容:2019-03-07 ~ 2019-03-0803-07内存管理一 by 陈雷03-08内存管理二 by 陈雷2019-03-11 ~ 2019-03-1503-11内存管理三 by 陈雷03-12基本变量 by 陈雷03-13字符串 by 景罗

March 13, 2019 · 1 min · jiezi

initContainer 使用案例

将glusterfs存储同时挂载到initContainer和container的指定目录上,如:/var/data/在initContainer中拉取资源放到/var/data/,也就推到了分布式存储glusterfs上,如:wget -P /var/data/ http://127.0.0.1:8081/repository/k8s/kubectl/kubectl-v1.10.0-linux-amd64.tar.gz这样当业务容器启动后,就可以在指定目录/var/data下看到initContainer拉取到的资源在initContainer中,最好增加一部检测指定资源是否存在,以防重复拉取,如:if [ ! -f “/var/data/kubectl-v1.10.0-linux-amd64.tar.gz” ]; then wget -P /var/data/ http://127.0.0.1:8081/repository/k8s/kubectl/kubectl-v1.10.0-linux-amd64.tar.gz; fiapiVersion: apps/v1kind: Deploymentmetadata: name: mysqlspec: replicas: 1 selector: matchLabels: name: mysql template: metadata: labels: name: mysql spec: initContainers: - name: getresource image: busybox:v0.1.0 command: [‘sh’, ‘-c’, ‘wget -P /var/data/ http://127.0.0.1:8081/repository/k8s/kubectl/kubectl-v1.10.0-linux-amd64.tar.gz ‘] volumeMounts: - name: mysql-pvc mountPath: /var/data containers: - name: mysql image: percona:5.7.22 imagePullPolicy: Always ports: - containerPort: 3306 resources: limits: memory: “500Mi” cpu: “500m” requests: memory: “500Mi” cpu: “250m” env: - name: MYSQL_ROOT_PASSWORD value: “mysql” volumeMounts: - name: mysql-pvc mountPath: /var/data volumes: - name: mysql-pvc persistentVolumeClaim: claimName: mysql—apiVersion: v1kind: PersistentVolumeClaimmetadata: name: mysqlspec: accessModes: - ReadWriteMany resources: requests: storage: “5Gi” volumeName: storageClassName: glusterfs—kind: ServiceapiVersion: v1metadata: name: mysqlspec: type: ClusterIP ports: - name: mysql port: 3306 targetPort: 3306 protocol: TCP selector: name: mysql ...

March 13, 2019 · 1 min · jiezi

地理位置geo处理之mysql函数

目前越来越多的业务都会基于LBS,附近的人,外卖位置,附近商家等等,现就讨论离我最近这一业务场景的解决方案。目前已知解决方案有:mysql 自定义函数计算mysql geo索引mongodb geo索引postgresql PostGis索引redis geoElasticSearch本文测试下mysql 函数运算的性能准备工作创建数据表CREATE TABLE driver ( id int(11) unsigned NOT NULL AUTO_INCREMENT, lng float DEFAULT NULL, lat float DEFAULT NULL, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8;创建测试数据在创建数据之前先了解下基本的地理知识:全球经纬度的取值范围为: 纬度-9090,经度-180180中国的经纬度范围大约为: 纬度3.8653.55,经度73.66135.05北京行政中心的纬度为39.92,经度为116.46越北面的地方纬度数值越大,越东面的地方经度数值越大度分转换: 将度分单位数据转换为度单位数据,公式:度=度+分/60分秒转换: 将度分秒单位数据转换为度单位数据,公式:度 = 度 + 分 / 60 + 秒 / 60 / 60在纬度相等的情况下:经度每隔0.00001度,距离相差约1米在经度相等的情况下:纬度每隔0.00001度,距离相差约1.1米mysql函数计算DELIMITER //CREATE DEFINER=root@localhost FUNCTION getDistance( lng1 float(10,7) , lat1 float(10,7) , lng2 float(10,7) , lat2 float(10,7)) RETURNS double COMMENT ‘计算2坐标点距离’BEGIN declare d double; declare radius int; set radius = 6371000; #假设地球为正球形,直径为6371000米 set d = (2*ATAN2(SQRT(SIN((lat1-lat2)*PI()/180/2) *SIN((lat1-lat2)PI()/180/2)+ COS(lat2PI()/180)COS(lat1PI()/180) *SIN((lng1-lng2)*PI()/180/2) *SIN((lng1-lng2)*PI()/180/2)), SQRT(1-SIN((lat1-lat2)*PI()/180/2) *SIN((lat1-lat2)PI()/180/2) +COS(lat2PI()/180)COS(lat1PI()/180) *SIN((lng1-lng2)*PI()/180/2) *SIN((lng1-lng2)*PI()/180/2))))*radius; return d;END//DELIMITER ;创建数据python脚本# coding=utf-8from orator import DatabaseManager, Modelimport loggingimport randomimport threading""" 中国的经纬度范围 纬度3.8653.55,经度73.66135.05。大概0.00001度差距1米 “”"# 创建 日志 对象logger = logging.getLogger()handler = logging.StreamHandler()formatter = logging.Formatter( ‘%(asctime)s %(name)-12s %(levelname)-8s %(message)s’)handler.setFormatter(formatter)logger.addHandler(handler)logger.setLevel(logging.DEBUG)# Connect to the databaseconfig = { ‘mysql’: { ‘driver’: ‘mysql’, ‘host’: ’localhost’, ‘database’: ‘dbtest’, ‘user’: ‘root’, ‘password’: ‘’, ‘prefix’: ’’ }}db = DatabaseManager(config)Model.set_connection_resolver(db)class Driver(Model): table = ‘driver’ timestamps = False passdef ins_driver(thread_name,nums): logger.info(‘开启线程%s’ % thread_name) for _ in range(nums): lng = ‘%.5f’ % random.uniform(73.66, 135.05) lat = ‘%.5f’ % random.uniform(3.86, 53.55) driver = Driver() driver.lng = lng driver.lat = lat driver.save()thread_nums = 10for i in range(thread_nums): t = threading.Thread(target=ins_driver, args=(i, 400000)) t.start()以上脚本创建10个线程,10个线程插入4万条数据。耗费150.18s执行完,总共插入40万条数据测试测试环境系统:mac os内存:16Gcpu: intel core i5硬盘: 500g 固态硬盘测试下查找距离(134.38753,18.56734)这个坐标点最近的10个司机select *,getDistance(134.38753,18.56734,lng,lat) as dis from driver ORDER BY dis limit 10耗时:18.0sexplain:全表扫描我测试了从1万到10万间隔1万和从10万到90万每间隔10万测试的结果变化结论此方案在数据量达到3万条查询耗时就会超过1秒大约每增加1万条就会增加0.4秒的耗时 ...

March 13, 2019 · 1 min · jiezi

商品、订单、购物车和订单商品快照的关系,初步了解成为架构师的思想

导读最近见同事在做订单和订单材料快照这方面的业务,这其实就像淘宝上的商品,不过,这里是材料了。它一共涉及到 五 张表:材料表,材料()的详细信息订单表,用户购买商品下单时购物车,待付款材料用户表,谁购买了了材料订单材料快照表,这就是订单和材料的快照因而,这里面就涉及到一对一的关系。一对一的关系虽然一对一的关系用的不多,但我们有时也会设计这方面的数据表,不常用不代表不用。我们一般做分表会使用一堆的关系,比如说用户表和用户快照表。用户表存储用户必填的信息,碧如 账号、性别、密码、登录时间、退出时间等等。用户扩展表就是用户的扩展信息,比如爱好、昵称、等等。是用户可填可不填的。如果数据都放在同一张表中,会感觉比较乱的。如代码所示:用户表的部分数据@Entity@Table(name = “core_user”)public class User { /** * 用户名,唯一 / @NotEmpty(message = “用户名不能为空”) @Column(length = 100, unique = true, nullable = false) private String username; /* * 密码 / @Column(nullable = false, length = 66) private String password; /* * 用户扩展表 / @OneToOne(mappedBy = “user”, fetch = FetchType.LAZY, cascade = CascadeType.ALL) private UserExt userExt; 。。。。}用户扩展表的部分数据@Entity@Table(name = “core_user_ext”)public class UserExt implements Serializable { @OneToOne @PrimaryKeyJoinColumn private User user; /* * 自我介绍 / @Lob @Basic(fetch = FetchType.LAZY) @Column(name = “introduce”, columnDefinition = “text”) private String introduce; /* * 性别 / @Enumerated(EnumType.STRING) @Column(name = “gender”, length = 20) private GenderEnum gender; /* * 昵称 / @Column(name = “nick_name”, length = 66) private String nickName; /* * 真实姓名 / @Column(name = “real_name”, length = 100) private String realName; /* * 头像 / @ManyToOne @JoinColumn(name = “head_image”) private Picture headImage; /* * 联系地址 / @Column(name = “address”, length = 200) private String address; /* * 邮政编码 / @Column(name = “postal_code”) private String postalCode; @Column(name = “qq”, length = 66) private String qq; /* * 身高 / @Column(length = 3) private Integer height; /* * 喜欢的运动 / @Column(name = “loved_sports”, length = 255) private String lovedSports; /* * 喜欢的电影 / @Column(name = “loved_movies”, length = 255) private String lovedMovies; /* * 喜欢的音乐 / @Column(name = “loved_musics”, length = 255) private String lovedMusics; /* * 喜欢的食物 / @Column(name = “loved_foods”, length = 255) private String lovedFoods; /* * 喜欢的书 / @Column(name = “loved_books”, length = 255) private String lovedBooks; /* * 喜欢的游戏 / @Column(name = “loved_games”, length = 255) private String lovedGames;}用户扩展信息是用户的辅助信息,它们适合一对一的关系。详解客户材料(商品)、用户材料(商品)、订单、购物车、用户、和订单商品快照用户,是谁购买了这个商品,这时我们需要知道的。材料商材料(材料商的商品),因为,我们这个平台主要是售卖装修材料的,其分为两部分。一部分是材料商在平台上挂出他们的材料,材料商下的经销商购买材料。他们购买材料后,这就涉及另一部分,即经销商挂出他们的材料。这个时候才卖给用户,因而,对于经销商来说,还要有一个市场零售价。经销商材料(经销商的商品):正如上面所说,我是经销商,从总代理那边购买了产品,然后,在我的这部分平台上售卖,此时,用户从我这边购买了,然后将其加入到购物车中。订单,即为我们在购买装修材料时所生成的一条购买记录。购物车,即为我们已经选好了商品,暂时还没有进行付款,只是暂存起来,这也是一条记录。等我们付款的时候,就从购物车中付款了。订单快照,比如我们昨天购买的商品材料,其当时的零售价是25元,但今天就变成了40元,等我们拿到手后,感觉不大合适,于是,申请退还装修材料。此时我们在网页上看到的材料价格是40元,而我们付款的时候是25元。商家是退给我们多少钱呢?25元,还是40元?当然,是25元,而不是40元。这25元存储在哪里呢?就是订单快照表。我们在付款的那一刻,或者,不管是直接购买的付款,还是从购物车中的购买,就会生成一条订单材料快照表。快照表有哪些信息:一个购物车的外键,订单的外键,装修材料的中必须让用户知道的信息等。装修材料从某种意义上来说,其也是一种一对一的关系,商品和快照找那个所存储的主要的材料信息。所有的信息,如以下代码,不重要的或设计公司机密的信息,就不在这里展示了。用户表这里用户可以为分材料商和经销商,通过这个枚举类CustomerTypeEnum确定的。材料商和经销商放在同一张表中,根据他们的类型,来确认是材料商和经销商。@Entity@Table(name = “core_user”)public class User{ /* * 用户名,唯一 / @NotEmpty(message = “用户名不能为空”) @Column(length = 100, unique = true, nullable = false) private String username; /* * 密码 / @Column(nullable = false, length = 66) private String password; /* * 最近登录ip / @Column(name = “last_login_ip”, length = 64) private String lastLoginIP; 。。。。材料商材料因为材料商是一个实体,我们这里只要关联材料商的外键即可,而且还是一对多的关系。因为,材料表中某一个材料商的记录不止一条。其次,还要有材料类型,材料类型表是树结构的,类型下面还有子类型。这里也是一对多的关系。public class Material extends BaseObj { /* * 所属品类 / @ManyToOne @JoinColumn(name = “material_category_id”) private MaterialCategory materialCategory; /* * 名称 / private String name; /* * 供应商 / @ManyToOne @JoinColumn(name = “supplier_id”) private Supplier supplier; /* * 品牌 / @ManyToOne @JoinColumn(name = “material_brand_id”) private MaterialBrand materialBrand; /* * 成本价 / @Column(name = “cost_price”, precision = 12, scale = 2) private BigDecimal costPrice; /* * 零售价 / @Column(name = “retail_price”, precision = 12, scale = 2) private BigDecimal retailPrice; /* * 单位 / @ManyToOne @JoinColumn(name = “unit_code”) private DataDict unit; 。。。}经销商材料经销商从材料商那边购买了材料,经销商的材料即来源于材料这张表。因而,这个经销商材料只要关联材料表即可。同时,还要有确认经销商是哪个经销商,因而,需要一个用户的外键,即经销商材料的所有者。经销商有自己的定价规则,他需要定义一个市场价。public class UserMaterial extends BaseObj { /* * 材料 / @ManyToOne @JoinColumn(name = “material_id”) private Material material; /* * 所有者 / @ManyToOne @JoinColumn(name = “owner_id”) private User owner; /* * 市场价 / @Column(name = “market_price”, precision = 12, scale = 2) private BigDecimal marketPrice; 。。。}订单我们从经销商那边购买了材料,此时,有条购买记录,这就是订单。谁发起的订单,也就是说,谁购买的这个材料,因而,需要用户的外键。public class Order extends BaseObj { /* * 收货地址 / @ManyToOne @JoinColumn(name = “work_site_id”) private WorkSite workSite; /* * 订单编号 / @Column(name = “order_no”) private String orderNo; /* * 订单状态 / @Enumerated(EnumType.STRING) private OrderStatusEnum status; /* * 订单备注 / @Column(name = “remark”, columnDefinition = “longtext”) private String remark; /* * 下单时间 / @Column(name = “generate_time”) private Date generateTime; /* * 提交时间 / @Column(name = “submit_time”) private Date submitTime; /* * 买家 / @ManyToOne @JoinColumn(name = “buyer_id”) private User buyer;购物车我们已经选好了材料,但还没有确认付不付款,于是,先在购物车存着,想起来就付款。加入到购物车的材料时经销商的材料,因而,购物车需要存储经销商的外键。同时,还要确认是谁加入的购物车,因而购物车需要用户的外键。这都是一对多的关系。同时,需要确认,这个购物车是否被使用。public class ShoppingCart extends BaseObj { /* * 用户材料 / @ManyToOne @JoinColumn(name = “user_material_id”) private UserMaterial userMaterial; /* * 数量 / @Column(name = “num”, precision = 12, scale = 2) private Double num = 1.0; /* * 金额 / @Column(name = “amount”, precision = 12, scale = 2) private BigDecimal amount; /* * 所有者 / @ManyToOne @JoinColumn(name = “owner_id”) private User owner; /* * 是否被使用 / @Enumerated(EnumType.STRING) @Column(name = “is_used”) private BooleanEnum isUsed = NO;}订单材料快照表这里,我们只要存储订单快照的id,而不是订单快照的对象?为什么呢?首先,对于用户来说,购买装修材料时,这个购物车是不常用的,因为,他们一旦看中了,就会直接购买的。其次,如果我们存储的是对象,用户每次加载购物车时,都要从数据库中遍历当前id的购物车的数据库的字段值,然后通过hibernate的反射封装到购物车的对象中。所以说呢,无疑是减缓了效率,这样不好。需要订单的外键,订单和订单快照虽然是一对一的关系,一个订单一个订单快照。也就是说,当用户生成订单时,就会有个订单快照。用户查看其购买记录(订单记录)的材料信息时,材料信息不是来源于用户材料表,而是来源于用户订单快照表。还有,用户退还材料时,经销商要查看用户当前购买的材料信息。这就用到了订单材料快照表。public class OrderMaterialSnapshot extends BaseObj { /* * 购物车id / @Column(name = “shopping_cart_id”) private Long shoppingCartId; /* * 订单 / @ManyToOne @JoinColumn(name = “order_id”) private Order order; /* * 状态 / @Enumerated(EnumType.STRING) @Column(name = “status”) private OrderMaterialStatusEnum status; /* * 购买数量 / @Column(name = “num”, precision = 12, scale = 2) private Double num = 1.0; /* * 退货数量 / @Column(name = “refund_num”) private Double refundNum = 0.0; /* * 购买总金额 / @Column(name = “amount”, precision = 12, scale = 2) private BigDecimal amount; /* * 接单时间 / @Column(name = “receive_time”) private Date receiveTime; /* * 发货时间 / @Column(name = “deliver_time”) private Date deliverTime; /* * 收货时间 / @Column(name = “receipt_time”) private Date receiptTime; /* * 所属品类 / @ManyToOne @JoinColumn(name = “material_category_id”) private MaterialCategory materialCategory; /* * 名称 / private String name; /* * 品牌 / @OneToOne @JoinColumn(name = “material_brand_id”) private MaterialBrand materialBrand; /* * 型号 / private String model; /* * 零售价 / @Column(name = “retail_price”, precision = 12, scale = 2) private BigDecimal retailPrice; /* * 会员单价(材料商零售价*会员折扣) / @Column(name = “vip_unit_price”, precision = 12, scale = 2) private BigDecimal vipUnitprice; /* * 市场价 / @Column(name = “market_price”, precision = 12, scale = 2) private BigDecimal marketPrice; /* * 单位 */ @ManyToOne @JoinColumn(name = “unit_code”) private DataDict unit; 。。。}总结要想称为优秀的架构师,首先参考别人的架构,毕竟,他山之石,可以攻玉吗!!!再接再厉,致奋斗的自己!!! ...

March 13, 2019 · 4 min · jiezi

数据库的常用操作

一、技术起源数据库操作,不管是服务端、前端、移动端,都或多或少的会涉及到数据的存储、查询、修改。所以作为一名开发者,数据库操作也是开发必备的一项技能。SQL全称是Structured Query Language,翻译后就是结构化查询语言,是一种数据库查询和设计语言,用于存取数据与及查询、更新和管理关系数据库系统。常见的数据库有MySQL、SQLServer、ORACLE、DB2等等。二、数据库基础数据库操作概览图:数据库的基本操作步骤:1、创建数据库2、连接(打开)数据库3、创建表4、往表中加入数据5、更新数据、查询数据、删除数据6、断开(关闭)数据库1、建表(CREATE TABLE)CREATE TABLE emp ( id int NOT NULL PRIMARY KEY, //添加主键 name varchar(20), gender varchar(2), performance int, salary double)如果创建表后,忘记添加主键或者外键,可以使用ALERT添加。ALERT TABLE emp ADD PRIMARY KEY(id); //添加主键ALERT TABLE orders ADD FOREIGN KEY (e_id) REFERENCE emp(id); //添加外键2、INSERT(插入)向表中加入数据。//向emp 表中插入一条数据,插入字符串时使用’‘INSERT INTO emp VALUES(1, ‘yijie’, ‘male’, 85, 18000.0);向表中插入数据的标准格式是:insert into tableName(column1, column2…) values(‘value1’, ‘value2’…)3、UPDATE(更新)更新表中的数据。update emp set salary=20000 where name=‘yijie’;4、DELETE (删除)delete from emp where id=8;//删除表中的某条数据,where后面的为条件delete * from emp;//删除表中的所有数据,清空表drop table 表名称; //删除某张表注意:在使用delete删除表中数据时,如果该表与其他表有关联关系,如:外键,得先删除关联表中的外键。5、DISTINCT(去重)一张表经过一段时间的操作,避免不了会出现数据重复的情况。重复的数据不仅没有意义,而且占用存储空间。这个时候distinct就悄然登场了。distinct用于根据条件去除表中的重复内容。//查询emp中的name,返回唯一的名字select distinct name from emp;6、Select (查询)查询是数据库操作中最常用的操作,也是最难的。select语句用于从表中查询数据,结果被存储在一个结果表中(称为结果集)。SELECT 语法:SELECT 列名称 FROM 表名称; //查询表中的某列数据SELECT * FROM 表名称; //查询整张表还有更为复杂的条件查询。三、基础函数数据库还为我们提供了一些函数,方便我们进行数据库操作。这些基础函数基本都是列名为函数参数,返回某一列的计算结果。1.AVG()平均值avg()用于返回某列的平均值,NULL不包含在计算中。select AVG(salary) as avg_salary form emp; //查询员工的平均薪水2.COUNT()COUNT函数用于返回匹配指定条件的行数。select COUNT(*) from emp; //返回表的记录数3.MAX()MAX函数返回指定列的最大值,NULL字不包括在计算中。4.MIN()MIN函数返回指定列的最小值,NULL字不包括在计算中。5.SUM()SUM函数返回指定列的总数。6.ROUND()ROUND函数用于把数值字段舍入为指定的小数位数。select ROUND(salary,1) as n_salary from emp; //将salary保留一位小数select ROUND(column_name,decimals) from table_name;参数描述column_name要舍入的字段decimals规定要返回的小数位数7.FORMAT()FORMAT用于对指定字段的显示进行格式化。SELECT FROMAT(column_name,format) FROM table_name;参数描述column_name要格式化的字段format指定的格式四、高级用法还有一些SQL的高级用法,分页、模糊匹配、排序等等。1.分页(LIMIT)分页查询就是返回返回当前页码对应的页面的数据。分页查询的基本公式:(page - 1) * pageSize + 当前页要显示的数据条数select * from emp limit 4; //返回前4条数据2.模糊匹配(LIKE)模糊匹配是配合where条件使用的。//%可以理解为定义通配符select * from emp where name like ‘a%’; //返回以a开头的所有姓名3.IN返回特定列在某个集合中的所有数据。select * from emp where name in (‘AA’, ‘BB’); //返回name为AA、BB的所有数据。4.JOIN联表运算符JOIN,用于将两个或者两个以上的表进行关联,并从这些表中查询数据。常用的几种连接方式:INNER JOIN: 内连接。LETF JOIN:就算右表中没有匹配,也从左表中返回所有的行。RIGHT JOIN:就算左表中没有匹配,也从右表中返回所有的行。FULL JOIN:只要有一个表存在就返回。5.UNIONUNION运算符用于合并两个或多个SELECT语句的结果集。UNION内部的SELECT语句必须具有相同数量的列,列也必须具有相似的数据类型。同时,每条SELECT语句中列的顺序必须相同。6.AUTO_INCREMENT(自增)一般用于修饰主键,使其保持自增。7.ORDER BY (排序)使用order by对查询结果进行排序,默认是升序。ASC:升序(从小到大)DESC:降序(从大到小)select * from emp order by name;8.GROUP BY通常匹配合计函数使用,根据一个或者多个列队结果集进行分组。9.HAVING用于给分组设置条件。10.DEFAULTdefault约束用于向列中插入默认值。写在最后本文是对数据库中经常用到的一些写法与及函数的归纳总结,方便以后用到的时候能够快速查询到。题外话:主要是前段时间去面试的时候,被问到修改一条数据的语句怎么写时,竟然没有回到上来,所以决定对数据库的常用操作做一个总结。 ...

March 13, 2019 · 1 min · jiezi

调研的六个开源DevOps平台

项目名称开发语言活跃度文档说明功能描述Walle 瓦力Python活跃、更新及时文档较完善,有官网DevOps代码部署平台:空间管理、服务器管理、项目管理、环境管理、用户管理、部署管理gaiaGolang活跃、更新及时文档较完善,有官网CI/CD:创建主流开发语言的Pipline,包括Golang、Python、Java、C++、Ruby蓝鲸智云配置平台(BlueKing CMDB)Golang活跃、更新及时文档很完善,有官网,有视频教程云CMDB:拓扑化的主机管理、组织架构管理、模型管理、进程管理、事件注册与推送、通用权限管理、操作审计cdsGolang活跃、更新及时有英文文档和官网CD & DevOps自动化:Self-Service、Horizontal Scalability、High Availability、Pipeline Reutilisability、REST API、CustomizablecloudunitJava不活跃,基本停止更新有部分安装和使用文档DevOps平台:CI/CD with Gitlab or Jenkins2、EKL Stack for monitoring users containers、Prometheus for monitoring platform、Sonar to analyse and reports in the future、Mattermost chatHygieiaJava活跃、更新及时文档较完善,有视频一站式DevOps平台:CI/CD、交付管道、数据可视化、看板

March 12, 2019 · 1 min · jiezi

说一说数据库的并发控制

说一说数据库的并发控制最近在看Elasticsearch时看到了并发控制,由此看到了新的并发控制方式。不得不说Elasticsearch相较于关系型数据库就是两种理论建立的数据存储体系,当然它们在并发控制上也相差甚远,各有千秋。什么是并发冲突说到并发就不得不提一下串行,所谓串行就是数据库操作事物按照特定的顺序从上到下执行,在串行操作某个数据表的某个字段的值时写入和读取都很好理解,且不会发生异常。但是往往线上数据操作会发生各种异常,因为线上数据库对用户来说不可能是串行访问的,往往是并发访问。就会产生如写入并发造成的覆盖现象,读写并发造成的脏读现象等。下面简单介绍一下这些异常形成的原因。A获取字段X的值为3将它减1然后写入数据库X为2,当B在A写入之前同时获取X的值为3将它减1然后写入数据库X为2。可以看到上面X经过两次减1操作,但是它的值只减了1,因为B将A的操作覆盖了。如果X的值为1,A想将X的值减1,如果X等于0则返回,当A将减1操作执行完成前,B同样进行这个操作,B取到的X也等于1,然后执行减1操作,会是X的值变为-1。上面的例子说明了并发操作中存在冲突,需要一种机制来处理这种冲突。于是锁的机制就应运而生来。锁顾名思义就是将需要执行的数据锁起来,从而让他从并发变成串行。如何治理并发冲突悲观锁并发控制悲观锁对数据被修改持悲观态度。即每次获取数据的时候,都会担心数据被修改,所以每次获取数据的时候都会进行加锁,确保在自己使用的过程中数据不会被别人修改,使用完成后进行数据解锁。由于数据进行加锁,期间对该数据进行读写的其他线程都会进行等待。读写锁在数据库中,锁被设计为两种模式,即共享锁(读锁)和互斥锁(写锁)。当一个事物获得共享锁之后,它只能进行读操作;当一个事物获取一行数据当互斥锁时,就可以对该行数据进行读和写操作。多个事物可以同时获取同一行数据的共享锁,但互斥锁同一时间只能被一个事物获取。没有获得锁但事物将处于等待状态。两阶段锁协议(2PL)两阶段锁协议是一种能够保证事物可串行化但协议,它将事物的获取锁和释放锁分成增长和缩减两个不同的阶段。在增长阶段,一个事物可以获得锁但是不能释放锁;而在缩减阶段事物只可以释放锁,不能获取新的锁。Strict 2PL 事物持有的互斥锁必须在提交后再释放Rigorous 2PL 事物持有的所以锁必须在提交后释放乐观锁并发控制乐观锁并不是真正的锁,而是一种并发控制的思想。它可以基于各种协议来实现这种并发控制。不同的事物按照锁协议同一数据项依次执行,因为后面执行的事务想要获取的数据已经被前面的事务加锁,只能等待锁的释放。基于时间戳的协议每个事务都有一个全局唯一且随时间递增的时间戳。每一项数据有两个时间戳分别是读时间戳和写时间戳,分别代表事务的开始和结束。这个协议使无论读操作还是写操作都需要比较读写时间戳,如果小于当前值(最后一次操作都时间戳)就会拒绝,然后回滚,然后数据库给这个事务一个新的时间戳重新执行。基于验证的协议这个协议根据事务的只读或者更新将所有事务的执行分为两到三个阶段。在读阶段,数据库会执行事务中的全部读操作和写操作,并将所以写后的值村润临时变量中,并不会真正更新数据库的内容;然后进入下一阶段,数据库会检测当前改动是否合法,即是否有其他事务在读阶段更新了数据,如果通过测试就直接写入数据库,否则就拒绝执行。多版本并发控制多版本并发控制意味着更新数据时不覆盖原数据,而是在数据库中保留多个版本,根据需要使用某个版本。它本质上跟上面的不冲突,属于两种解决方案,它并不能解决冲突而是将冲突分割开。

March 12, 2019 · 1 min · jiezi

MySql(一)——启动选项和系统变量

MySql(一)——启动选项和系统变量启动MySql服务器程序mysqldmysqld这个可执行文件就代表着MySql服务器程序,运行这个可执行文件就可以直接启动一个服务器进程。mysqld_safemysqld_safe是一个启动脚本,它会间接的调用mysql,而且还顺便启动了另外一个监控服务,这个监控进程在服务器进程挂了的时候,可以帮助重启它。另外,使用mysqld_safe启动服务器程序时,它会将服务器程序的出错信息和其他诊断信息重定向到某个文件中,产生出错误日志,这样可以方便我们找出发生错误的原因。mysql.servermysql.server也是一个启动脚本,它会间接的调用mysql_safe,在调用mysql.server时在后边指定start参数就可以启动服务器程序了:mysql.server startmysqld_multi一台计算机上可以运行多个服务器实例。mysqld_multi可以对每一个服务器进程的启动或停止进行监控。tips:windows下,命令:“完整的可执行文件路径” –install [-manual] [服务名]可以指定程序注册为windows服务。启动MySql客户端程序mysql -h主机名 -u用户名 -p密码参数名含义-h计算机的域名或者IP地址-u用户名-p密码客户端与服务器的连接过程运行着的服务器程序和客户端程序本质上都是计算机上的一个进程,所以客户端进程向服务器进程发送请求并得到回复的过程本质上是一个进程间通信的过程!MySql支持下边三种客户端进程和服务器进程的通信方式。TCP/IPmysqld -P3307,-P可以改变MySql服务器监听哪个端口mysql -p3307,-P可以让客户端去连接服务器的指定端口命名管道和共享内存仅windows用户可用Unix域套接字文件如果我们的服务器进程和客户端进程都运行在同一台操作系统为类Unix的机器上的话,我们可以使用Unix域套接字文件来进行进程间通信如果我们在启动客户端程序的时候指定的主机名为localhost,或者指定了–protocal=socket的启动参数,那服务器程序和客户端程序之间就可以通过Unix域套接字文件来进行通信了。MySql服务器程序默认监听的Unix域套接字文件路径为/tmp/mysql.sock,客户端程序也默认连接到这个Unix域套接字文件。如果我们想改变这个默认路径,可以在启动服务器程序时指定socket参数mysqld –socket=/tmp/a.txtmysql -hlocalhost -uroot –socket=/tmp/a.txt -p服务器处理客户端请求其实不论客户端进程和服务器进程是采用哪种方式进行通信,最后实现的效果都是:客户端进程向服务器进程发送一段文本(MySQL语句),服务器进程处理后再向客户端进程发送一段文本(处理结果)。大致是以下三个部分连接管理:每当有一个客户端进程连接到服务器进程时,服务器进程都会创建一个线程来专门处理与这个客户端的交互,当该客户端退出时会与服务器断开连接,服务器并不会立即把与该客户端交互的线程销毁掉,而是把它缓存起来,在另一个新的客户端再进行连接时,把这个缓存的线程分配给该新客户端。这样就起到了不频繁创建和销毁线程的效果,从而节省开销解析与优化查询缓存:如果两个查询请求在任何字符上的不同(例如:空格、注释、大小写),都会导致缓存不会命中。虽然查询缓存有时可以提升系统性能,但也不得不因维护这块缓存而造成一些开销,比如每次都要去查询缓存中检索,查询请求处理完需要更新查询缓存,维护该查询缓存对应的内存区域。从MySQL 5.7.20开始,不推荐使用查询缓存,并在MySQL 8.0中删除语法解析:判断请求的语法是否正确查询优化:MySql的优化程序会对我们的语句做一些优化存储引擎blackhole、MyIsam、InnoDB…关于存储引擎的一些操作SHOW ENGINES;创建表时指定存储引擎:CREATE TABLE 表名( 建表语句; ) ENGINE = 存储引擎名称;修改表的存储引擎:ALTER TABLE 表名 ENGINE = 存储引擎名称;启动选项和配置文件在命令行上使用选项,格式:–启动选项1[=值1] –启动选项2[=值2] … –启动选项n[=值n]mysqld –skip-networking:客户端不能用-h(TCP/IP)进行网络通信mysqld –default-storage-engine=MyISAM:把默认引擎设置为MyISAM在命令行中设置启动选项只对当次启动生效配置文件中使用选项Windows操作系统的配置文件路径名备注%WINDIR%my.ini, %WINDIR%my.cnfWINDIR通常是C:WINDOWSC:my.ini, C:my.cnf BASEDIRmy.ini, BASEDIRmy.cnfBASEDIR指的是MySQL安装目录的路径defaults-extra-file命令行指定的额外配置文件路径%APPDATA%MySQL.mylogin.cnf登录路径选项(仅限客户端)列表中最后一个名为.mylogin.cnf配置文件有点儿特殊,它不是一个纯文本文件(其他的配置文件都是纯文本文件),而是使用mysql_config_editor实用程序创建的加密文件。文件中只能包含一些用于启动客户端软件时连接服务器的一些选项,包括 host、user、password、port和socket。而且它只能被客户端程序所使用。类Unix操作系统中的配置文件路径名备注/etc/my.cnf /etc/mysql/my.cnf SYSCONFDIR/my.cnfCMake构建MySQL时使用SYSCONFDIR选项指定的目录$MYSQL_HOME/my.cnf特定于服务器的选项(仅限服务器)defaults-extra-file命令行指定的额外配置文件路径~/.my.cnf用户特定选项~/.mylogin.cnf用户特定的登录路径选项(仅限客户端)配置文件的内容[server]option1 #这是option1,该选项不需要选项值option2 = value2 #这是option2,该选项需要选项值mysqldmysqld_safeclientmysqlmysqladmin如果我们在多个配置文件中设置了相同的启动选项,那以最后一个配置文件中的为准启动命令类别能读取的组mysqld启动服务器[mysqld]、[server]mysqld_safe启动服务器[mysqld]、[server]、[mysqld_safe]mysql.server启动服务器[mysqld]、[server]、[mysql.server]mysql启动客户端[mysql]、[client]mysqladmin启动客户端[mysqladmin]、[client]mysqldump启动客户端[mysqldump]、[client]defaults-file的使用如果我们不想让MySql到默认的路径下搜索配置文件(就是上表中列出的那些),可以在命令行指定defaults-file选项,比如这样(以UNIX系统为例):mysqld –defaults-file=/tmp/myconfig.txt。如果文件不存在或无法访问,则会发生错误。命令行和配置文件中启动选项的区别如果同一个启动选项既出现在命令行中,又出现在配置文件中,那么以命令行中的启动选项为准系统变量1.系统变量简介MySql服务器程序运行过程中会用到许多影响程序行为的变量SHOW [GLOBAL|SESSION] VARIABLES [LIKE 匹配的模式];2.设置系统变量通过启动选项设置:上面说的很具体服务器程序运行过程中设置系统变量比较牛逼的一点就是,对于大部分系统变量来说,它们的值可以在服务器程序运行过程中进行动态修改而无需停止并重启服务器设置不同作用范围的系统变量GLOBAL:全局变量,影响服务器的整体操作。SESSION:会话变量,影响某个客户端连接的操作。(注:SESSION有个别名叫LOCAL)服务器会为每个连接的客户端维护一组会话变量设置:SET [GLOBAL|SESSION] 系统变量名 = 值;SET GLOBAL default_storage_engine = MyISAMSET [@@(GLOBAL|SESSION).]var_name = XXX;SET @@GLOBAL.default_storage_engine = MyISAMSET default_storage_engine = MyISAM如果在设置系统变量的语句中省略了作用范围,默认的作用范围就是SESSION如果某个客户端改变了某个系统变量在GLOBAL作用范围的值,并不会影响该系统变量在当前已经连接的客户端作用范围为SESSION的值,只会影响后续连入的客户端在作用范围为SESSION的值。3.启动选项和系统变量的区别- 大部分的系统变量都可以被当作启动选项传入- 有些系统变量是在程序运行过程中自动生成的,是不可以当作启动选项来设置,比如auto_increment_offset、character_set_client- 有些启动选项也不是系统变量,比如defaults-file4.状态变量为了让我们更好的了解服务器程序的运行情况,MySQL服务器程序中维护了好多关于程序运行状态的变量。由于状态变量是用来显示服务器程序运行状况的,所以它们的值只能由服务器程序自己来设置,我们程序员是不能设置的。如:Threads_connected ...

March 12, 2019 · 1 min · jiezi

为什么开发人员必须要了解数据库锁?

1.锁?1.1何为锁锁在现实中的意义为:封闭的器物,以钥匙或暗码开启。在计算机中的锁一般用来管理对共享资源的并发访问,比如我们java同学熟悉的Lock,synchronized等都是我们常见的锁。当然在我们的数据库中也有锁用来控制资源的并发访问,这也是数据库和文件系统的区别之一。1.2为什么要懂数据库锁?通常来说对于一般的开发人员,在使用数据库的时候一般懂点DQL(select),DML(insert,update,delete)就够了。小明是一个刚刚毕业在互联网公司工作的Java开发工程师,平常的工作就是完成PM的需求,当然在完成需求的同时肯定逃脱不了spring,springmvc,mybatis的那一套框架,所以一般来说sql还是自己手写,遇到比较复杂的sql会从网上去百度一下。对于一些比较重要操作,比如交易啊这些,小明会用spring的事务来对数据库的事务进行管理,由于数据量比较小目前还涉及不了分布式事务。前几个月小明过得都还风调雨顺,知道有一天,小明接了一个需求,商家有个配置项,叫优惠配置项,可以配置买一送一,买一送二等等规则,当然这些配置是批量传输给后端的,这样就有个问题每个规则都得去匹配他到底是删除还是添加还是修改,这样后端逻辑就比较麻烦,聪明的小明想到了一个办法,直接删除这个商家的配置,然后全部添加进去。小明马上开发完毕,成功上线。开始上线没什么毛病,但是日志经常会出现一些mysql-insert-deadlock异常。由于小明经验比较浅,对于这类型的问题第一次遇见,于是去问了他们组的老司机-大红,大红一看见这个问题,然后看了他的代码之后,输出了几个命令看了几个日志,马上定位了问题,告诉了小明:这是因为delete的时候会加间隙锁,但是间隙锁之间却可以兼容,但是插入新的数据的时候就会因为插入意向锁会被间隙锁阻塞,导致双方被资源被互占,导致死锁。小明听了之后似懂非懂,由于大红的事情比较多,不方便一直麻烦大红,所以决定自己下来自己想。下班过后,小明回想大红说的话,什么是间隙锁,什么是插入意向锁,看来作为开发者对数据库不应该只会写SQL啊,不然遇到一些疑难杂症完全没法解决啊。想完,于是小明就踏上了学习Mysql锁这条不归之路。2.InnoDB2.1mysql体系架构小明没有着急去了解锁这方面的知识,他首先先了解了下Mysql体系架构:可以发现Mysql由连接池组件、管理服务和工具组件、sql接口组件、查询分析器组件、优化器组件、 缓冲组件、插件式存储引擎、物理文件组成。小明发现在mysql中存储引擎是以插件的方式提供的,在Mysql中有多种存储引擎,每个存储引擎都有自己的特点。随后小明在命令行中打出了:show engines \G;一看原来有这么多种引擎。又打出了下面的命令,查看当前数据库默认的引擎:show variables like ‘%storage_engine%’;小明恍然大悟:原来自己的数据库是使用的InnoDB,依稀记得自己在上学的时候好像听说过有个引擎叫MyIsAM,小明想这两个有啥不同呢?马上查找了一下资料:对比项InnoDBMyIsAM事务支持不支持锁支持MVCC行锁表锁外键支持不支持存储空间存储空间由于需要高速缓存,较大可压缩适用场景有一定量的update和Insert大量的select小明大概了解了一下InnoDB和MyIsAM的区别,由于使用的是InnoDB,小明就没有过多的纠结这一块。2.2事务的隔离性小明在研究锁之前,又回想到之前上学的时候教过的数据库事务隔离性,其实锁在数据库中其功能之一也是用来实现事务隔离性。而事务的隔离性其实是用来解决,脏读,不可重复读,幻读几类问题。2.2.1 脏读一个事务读取到另一个事务未提交的更新数据。什么意思呢?时间点事务A事务B1begin; 2select * from user where id = 1;begin;3 update user set namm = ’test’ where id = 1;4select * from user where id = 1; 5commit;commit;在事务A,B中,事务A在时间点2,4分别对user表中id=1的数据进行了查询了,但是事务B在时间点3进行了修改,导致了事务A在4中的查询出的结果其实是事务B修改后的。破坏了数据库中的隔离性。2.2.2 不可重复读在同一个事务中,多次读取同一数据返回的结果不同,和脏读不同的是这里读取的是已经提交过后的。时间点事务A事务B1begin; 2select * from user where id = 1;begin;3 update user set namm = ’test’ where id = 1;4 commit;5select * from user where id = 1; 6commit; 在事务B中提交的操作在事务A第二次查询之前,但是依然读到了事务B的更新结果,也破坏了事务的隔离性。2.2.3 幻读一个事务读到另一个事务已提交的insert数据。时间点事务A事务B1begin; 2select * from user where id > 1;begin;3 insert user select 2;4 commit;5select * from user where id > 1; 6commit; 在事务A中查询了两次id大于1的,在第一次id大于1查询结果中没有数据,但是由于事务B插入了一条Id=2的数据,导致事务A第二次查询时能查到事务B中插入的数据。事务中的隔离性:隔离级别脏读不可重复读幻读未提交读(RUC)NONONO已提交读(RC)YESNONO可重复读(RR)YESYESNO可串行化YESYESYES小明注意到在收集资料的过程中,有资料写到InnoDB和其他数据库有点不同,InnoDB的可重复读其实就能解决幻读了,小明心想:这InnoDB还挺牛逼的,我得好好看看到底是怎么个原理。2.3 InnoDB锁类型小明首先了解一下Mysql中常见的锁类型有哪些:2.3.1 S or X在InnoDb中实现了两个标准的行级锁,可以简单的看为两个读写锁:S-共享锁:又叫读锁,其他事务可以继续加共享锁,但是不能继续加排他锁。X-排他锁: 又叫写锁,一旦加了写锁之后,其他事务就不能加锁了。兼容性:是指事务A获得一个某行某种锁之后,事务B同样的在这个行上尝试获取某种锁,如果能立即获取,则称锁兼容,反之叫冲突。纵轴是代表已有的锁,横轴是代表尝试获取的锁。.XSX冲突冲突S冲突兼容2.3.2 意向锁意向锁在InnoDB中是表级锁,和他的名字一样他是用来表达一个事务想要获取什么。意向锁分为:意向共享锁:表达一个事务想要获取一张表中某几行的共享锁。意向排他锁:表达一个事务想要获取一张表中某几行的排他锁。这个锁有什么用呢?为什么需要这个锁呢?首先说一下如果没有这个锁,如果要给这个表加上表锁,一般的做法是去遍历每一行看看他是否有行锁,这样的话效率太低,而我们有意向锁,只需要判断是否有意向锁即可,不需要再去一行行的去扫描。在InnoDB中由于支持的是行级的锁,因此InnboDB锁的兼容性可以扩展如下:.IXISXSIX兼容兼容冲突冲突IS兼容兼容冲突兼容X冲突冲突冲突冲突S冲突兼容冲突兼容2.3.3 自增长锁自增长锁是一种特殊的表锁机制,提升并发插入性能。对于这个锁有几个特点:在sql执行完就释放锁,并不是事务执行完。对于Insert…select大数据量插入会影响插入性能,因为会阻塞另外一个事务执行。自增算法可以配置。在MySQL5.1.2版本之后,有了很多优化,可以根据不同的模式来进行调整自增加锁的方式。小明看到了这里打开了自己的MySQL发现是5.7之后,于是便输入了下面的语句,获取到当前锁的模式:mysql> show variables like ‘innodb_autoinc_lock_mode’;+————————–+——-+| Variable_name | Value |+————————–+——-+| innodb_autoinc_lock_mode | 2 |+————————–+——-+1 row in set (0.01 sec)在MySQL中innodb_autoinc_lock_mode有3种配置模式:0、1、2,分别对应”传统模式”, “连续模式”, “交错模式”。传统模式:也就是我们最上面的使用表锁。连续模式:对于插入的时候可以确定行数的使用互斥量,对于不能确定行数的使用表锁的模式。交错模式:所有的都使用互斥量,为什么叫交错模式呢,有可能在批量插入时自增值不是连续的,当然一般来说如果不看重自增值连续一般选择这个模式,性能是最好的。2.4InnoDB锁算法小明已经了解到了在InnoDB中有哪些锁类型,但是如何去使用这些锁,还是得靠锁算法。2.4.1 记录锁(Record-Lock)记录锁是锁住记录的,这里要说明的是这里锁住的是索引记录,而不是我们真正的数据记录。如果锁的是非主键索引,会在自己的索引上面加锁之后然后再去主键上面加锁锁住.如果没有表上没有索引(包括没有主键),则会使用隐藏的主键索引进行加锁。如果要锁的列没有索引,则会进行全表记录加锁。2.4.2 间隙锁间隙锁顾名思义锁间隙,不锁记录。锁间隙的意思就是锁定某一个范围,间隙锁又叫gap锁,其不会阻塞其他的gap锁,但是会阻塞插入间隙锁,这也是用来防止幻读的关键。2.4.3 next-key锁这个锁本质是记录锁加上gap锁。在RR隔离级别下(InnoDB默认),Innodb对于行的扫描锁定都是使用此算法,但是如果查询扫描中有唯一索引会退化成只使用记录锁。为什么呢?因为唯一索引能确定行数,而其他索引不能确定行数,有可能在其他事务中会再次添加这个索引的数据会造成幻读。这里也说明了为什么Mysql可以在RR级别下解决幻读。2.4.4 插入意向锁插入意向锁Mysql官方对其的解释:An insert intention lock is a type of gap lock set by INSERT operations prior to row insertion. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap. Suppose that there are index records with values of 4 and 7. Separate transactions that attempt to insert values of 5 and 6, respectively, each lock the gap between 4 and 7 with insert intention locks prior to obtaining the exclusive lock on the inserted row, but do not block each other because the rows are nonconflicting.可以看出插入意向锁是在插入的时候产生的,在多个事务同时写入不同数据至同一索引间隙的时候,并不需要等待其他事务完成,不会发生锁等待。假设有一个记录索引包含键值4和7,不同的事务分别插入5和6,每个事务都会产生一个加在4-7之间的插入意向锁,获取在插入行上的排它锁,但是不会被互相锁住,因为数据行并不冲突。这里要说明的是如果有间隙锁了,插入意向锁会被阻塞。2.5 MVCCMVCC,多版本并发控制技术。在InnoDB中,在每一行记录的后面增加两个隐藏列,记录创建版本号和删除版本号。通过版本号和行锁,从而提高数据库系统并发性能。在MVCC中,对于读操作可以分为两种读:快照读:读取的历史数据,简单的select语句,不加锁,MVCC实现可重复读,使用的是MVCC机制读取undo中的已经提交的数据。所以它的读取是非阻塞的。当前读:需要加锁的语句,update,insert,delete,select…for update等等都是当前读。在RR隔离级别下的快照读,不是以begin事务开始的时间点作为snapshot建立时间点,而是以第一条select语句的时间点作为snapshot建立的时间点。以后的select都会读取当前时间点的快照值。在RC隔离级别下每次快照读均会创建新的快照。具体的原理是通过每行会有两个隐藏的字段一个是用来记录当前事务,一个是用来记录回滚的指向Undolog。利用undolog就可以读取到之前的快照,不需要单独开辟空间记录。3.加锁分析小明到这里,已经学习很多mysql锁有关的基础知识,所以决定自己创建一个表搞下实验。首先创建了一个简单的用户表:CREATE TABLE user ( id int(11) unsigned NOT NULL AUTO_INCREMENT, name varchar(11) CHARACTER SET utf8mb4 DEFAULT NULL, comment varchar(11) CHARACTER SET utf8 DEFAULT NULL, PRIMARY KEY (id), KEY index_name (name)) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;然后插入了几条实验数据:insert user select 20,333,333;insert user select 25,555,555;insert user select 20,999,999;数据库事务隔离选择了RR3.1 实验1小明开启了两个事务,进行实验1.时间点事务A事务B1begin; 2select * from user where name = ‘555’ for update;begin;3 insert user select 31,‘556’,‘556’;4 ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction小明开启了两个事务并输入了上面的语句,发现事务B居然出现了超时,小明看了一下自己明明是对name = 555这一行进行的加锁,为什么我想插入name=556给我阻塞了。于是小明打开命令行输入:select * from information_schema.INNODB_LOCKS发现在事务A中给555加了Next-key锁,事务B插入的时候会首先进行插入意向锁的插入,于是得出下面结论:可以看见事务B由于间隙锁和插入意向锁的冲突,导致了阻塞。3.2 实验2小明发现上面查询条件用的是普通的非唯一索引,于是小明就试了一下主键索引:时间点事务A事务B1begin; 2select * from user where id = 25 for update;begin;3 insert user select 26,‘666’,‘666’;4 Query OK, 1 row affected (0.00 sec)Records: 1 Duplicates: 0 Warnings: 0居然发现事务B并没有发生阻塞,哎这个是咋回事呢,小明有点疑惑,按照实验1的套路应该会被阻塞啊,因为25-30之间会有间隙锁。于是小明又祭出了命令行,发现只加了X记录锁。原来是因为唯一索引会降级记录锁,这么做的理由是:非唯一索引加next-key锁由于不能确定明确的行数有可能其他事务在你查询的过程中,再次添加这个索引的数据,导致隔离性遭到破坏,也就是幻读。唯一索引由于明确了唯一的数据行,所以不需要添加间隙锁解决幻读。3.3 实验3上面测试了主键索引,非唯一索引,这里还有个字段是没有索引,如果对其加锁会出现什么呢?时间点事务A事务B1begin; 2select * from user where comment = ‘555’ for update;begin;3 insert user select 26,‘666’,‘666’;4 ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction5 insert user select 31,‘3131’,‘3131’;6 ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction7 insert user select 10,‘100’,‘100’;8 ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction小明一看哎哟我去,这个咋回事呢,咋不管是用实验1非间隙锁范围的数据,还是用间隙锁里面的数据都不行,难道是加了表锁吗?的确,如果用没有索引的数据,其会对所有聚簇索引上都加上next-key锁。所以大家平常开发的时候如果对查询条件没有索引的,一定进行一致性读,也就是加锁读,会导致全表加上索引,会导致其他事务全部阻塞,数据库基本会处于不可用状态。4.回到事故4.1 死锁小明做完实验之后总算是了解清楚了加锁的一些基本套路,但是之前线上出现的死锁又是什么东西呢?死锁:是指两个或两个以上的事务在执行过程中,因争夺资源而造成的一种互相等待的现象。说明有等待才会有死锁,解决死锁可以通过去掉等待,比如回滚事务。解决死锁的两个办法:等待超时:当某一个事务等待超时之后回滚该事务,另外一个事务就可以执行了,但是这样做效率较低,会出现等待时间,还有个问题是如果这个事务所占的权重较大,已经更新了很多数据了,但是被回滚了,就会导致资源浪费。等待图(wait-for-graph): 等待图用来描述事务之间的等待关系,当这个图如果出现回路如下:就出现回滚,通常来说InnoDB会选择回滚权重较小的事务,也就是undo较小的事务。4.2 线上问题小明到这里,基本需要的基本功都有了,于是在自己的本地表中开始复现这个问题:时间点事务A事务B1begin;begin;2delete from user where name = ‘777’;delete from user where name = ‘666’;3insert user select 27,‘777’,‘777’;insert user select 26,‘666’,‘666’;4ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transactionQuery OK, 1 row affected (14.32 sec) Records: 1 Duplicates: 0 Warnings: 0可以看见事务A出现被回滚了,而事务B成功执行。具体每个时间点发生了什么呢?时间点2:事务A删除name = ‘777’的数据,需要对777这个索引加上next-Key锁,但是其不存在,所以只对555-999之间加间隙锁,同理事务B也对555-999之间加间隙锁。间隙锁之间是兼容的。时间点3:事务A,执行Insert操作,首先插入意向锁,但是555-999之间有间隙锁,由于插入意向锁和间隙锁冲突,事务A阻塞,等待事务B释放间隙锁。事务B同理,等待事务A释放间隙锁。于是出现了A->B,B->A回路等待。时间点4:事务管理器选择回滚事务A,事务B插入操作执行成功。4.3 修复BUG这个问题总算是被小明找到了,就是因为间隙锁,现在需要解决这个问题,这个问题的原因是出现了间隙锁,那就来去掉他吧:方案一:隔离级别降级为RC,在RC级别下不会加入间隙锁,所以就不会出现毛病了,但是在RC级别下会出现幻读,可提交读都破坏隔离性的毛病,所以这个方案不行。方案二:隔离级别升级为可序列化,小明经过测试后发现不会出现这个问题,但是在可序列化级别下,性能会较低,会出现较多的锁等待,同样的也不考虑。方案三:修改代码逻辑,不要直接删,改成每个数据由业务逻辑去判断哪些是更新,哪些是删除,那些是添加,这个工作量稍大,小明写这个直接删除的逻辑就是为了不做这些复杂的事的,所以这个方案先不考虑。方案四:较少的修改代码逻辑,在删除之前,可以通过快照查询(不加锁),如果查询没有结果,则直接插入,如果有通过主键进行删除,在之前第三节实验2中,通过唯一索引会降级为记录锁,所以不存在间隙锁。经过考虑小明选择了第四种,马上进行了修复,然后上线观察验证,发现现在已经不会出现这个Bug了,这下小明总算能睡个安稳觉了。4.4 如何防止死锁小明通过基础的学习和平常的经验总结了如下几点:以固定的顺序访问表和行。交叉访问更容易造成事务等待回路。尽量避免大事务,占有的资源锁越多,越容易出现死锁。建议拆成小事务。降低隔离级别。如果业务允许(上面4.3也分析了,某些业务并不能允许),将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为gap锁造成的死锁。为表添加合理的索引。防止没有索引出现表锁,出现的死锁的概率会突增。最后由于篇幅有限很多东西并不能介绍全如果感兴趣的同学可以阅读《Mysql技术内幕-InnoDB引擎》第6章 以及 何大师的MySQL 加锁处理分析。作者本人水平有限,如果有什么错误,还请指正。最后这篇文章被我收录于JGrowing,一个全面,优秀,由社区一起共建的Java学习路线,如果您想参与开源项目的维护,可以一起共建,github地址为:https://github.com/javagrowin… 麻烦给个小星星哟。如果你觉得这篇文章对你有文章,可以关注我的技术公众号,你的关注和转发是对我最大的支持,O(∩_∩)O ...

March 12, 2019 · 3 min · jiezi

Mac 安装MYSQL

安装登陆MYSQL官网 ,注意下载5.7版本,不登陆直接下载文件。记住临时密码下载完成后,双击打开一路确定,但是当弹出一个MYSQL Installer提示框的时候一定打开备忘录复制粘贴记下弹出框的密码2019-03-12T01:42:11.344785Z 1 [Note] A temporary password is generated for root@localhost: ZulSd!;yn2,GIf you lose this password, please consult the section How to Reset the Root Password in the MySQL reference manual.打开MySQL服务1、进入系统偏好设置2、点击MySQL3、开启MySQL服务配置路径vim /.bash_profile加入PATH=$PATH:/usr/local/mysql/bin并保存(vim 中先按 Esc键,在输入 :wq )实际操作后如下:export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/mysql/binexport NVM_DIR="$HOME/.nvm"[ -s “$NVM_DIR/nvm.sh” ] && . “$NVM_DIR/nvm.sh” # This loads nvm[ -s “$NVM_DIR/bash_completion” ] && . “$NVM_DIR/bash_completion” # This loads nvm bash_completion 登陆命令行输入:mysql -u root -p//这是安装时候保存的信息 输入密码(ZulSd!;yn2,G)2019-03-12T01:42:11.344785Z 1 [Note] A temporary password is generated for root@localhost: ZulSd!;yn2,GIf you lose this password, please consult the section How to Reset the Root Password in the MySQL reference manual.登陆,输入之前保存的密码,进入后,再操作修改密码mysql> SET PASSWORD FOR ‘root’@’localhost’ = PASSWORD(‘123456’);备注:也可以在命令行安装,在命令行中输入brew install mysqlbrew 包管理工具会自行安装 MySQL. ...

March 12, 2019 · 1 min · jiezi

【MySQL】常用拼接语句

前言:在MySQL中 CONCAT ()函数用于将多个字符串连接成一个字符串,利用此函数我们可以将原来一步无法得到的sql拼接出来,在工作中也许会方便很多,下面主要介绍下几个常用的场景。注:适用于5.7版本 低版本可能稍许不同。1.拼接查询所有用户SELECT DISTINCT CONCAT( ‘User: '’, USER, ‘'@'’, HOST, ‘';’ ) AS QUERYFROM mysql.USER;# 当拼接字符串中出现’时 需使用\转义符2.拼接DROP tableSELECT CONCAT( ‘DROP table ‘, TABLE_NAME, ‘;’ )FROM information_schema. TABLESWHERE TABLE_SCHEMA = ’test’;3.拼接kill连接SELECT concat(‘KILL ‘, id, ‘;’)FROM information_schema. PROCESSLISTWHERE STATE LIKE ‘Creating sort index’;4.拼接创建数据库语句SELECT CONCAT( ‘create database ‘, ‘', SCHEMA_NAME, '’, ’ DEFAULT CHARACTER SET ‘, DEFAULT_CHARACTER_SET_NAME, ‘;’ ) AS CreateDatabaseQueryFROM information_schema.SCHEMATAWHERE SCHEMA_NAME NOT IN ( ‘information_schema’, ‘performance_schema’, ‘mysql’, ‘sys’ );5.拼接创建用户的语句SELECT CONCAT( ‘create user '’, user, ‘'@'’, Host, ‘'’ ’ IDENTIFIED BY PASSWORD '’, authentication_string, ‘';’ ) AS CreateUserQueryFROM mysql.userWHERE User NOT IN ( ‘root’, ‘mysql.session’, ‘mysql.sys’ );#有密码字符串哦 在其他实例执行 可直接创建出与本实例相同密码的用户6.导出权限脚本 这个shell脚本也用到了拼接#!/bin/bash #Function export user privileges pwd=yourpass expgrants() { mysql -B -u’root’ -p${pwd} -N $@ -e “SELECT CONCAT( ‘SHOW GRANTS FOR ‘’’, user, ‘’’@’’’, host, ‘’’;’ ) AS query FROM mysql.user” | \ mysql -u’root’ -p${pwd} $@ | \ sed ’s/(GRANT .)/\1;/;s/^(Grants for .)/– \1 /;/–/{x;p;x;}’ } expgrants > /tmp/grants.sqlecho “flush privileges;” >> /tmp/grants.sql7.查找表碎片SELECT t.TABLE_SCHEMA, t.TABLE_NAME, t.TABLE_ROWS, concat(round(t.DATA_LENGTH / 1024 / 1024, 2), ‘M’) AS size, t.INDEX_LENGTH, concat(round(t.DATA_FREE / 1024 / 1024, 2), ‘M’) AS datafreeFROM information_schema.tables tWHERE t.TABLE_SCHEMA = ’test’ order by DATA_LENGTH desc;8.查找无主键表 这个没用到拼接 也分享出来吧#查找某一个库无主键表SELECTtable_schema,table_nameFROM information_schema.TABLESWHERE table_schema = ’test’AND TABLE_NAME NOT IN ( SELECT table_name FROM information_schema.table_constraints t JOIN information_schema.key_column_usage k USING ( constraint_name, table_schema, table_name ) WHERE t.constraint_type = ‘PRIMARY KEY’ AND t.table_schema = ’test’);#查找除系统库外 无主键表SELECT t1.table_schema, t1.table_nameFROM information_schema. TABLES t1LEFT OUTER JOIN information_schema.TABLE_CONSTRAINTS t2 ON t1.table_schema = t2.TABLE_SCHEMAAND t1.table_name = t2.TABLE_NAMEAND t2.CONSTRAINT_NAME IN (‘PRIMARY’)WHERE t2.table_name IS NULLAND t1.TABLE_SCHEMA NOT IN ( ‘information_schema’, ‘performance_schema’, ‘mysql’, ‘sys’) ;欢迎大家收藏,其他同学有没有类似实用的语句呢 也可以分享出来哦! ...

March 11, 2019 · 2 min · jiezi

MySQL 数据库设计总结

本文由云+社区发表作者:漆洪凯规则1:一般情况可以选择MyISAM存储引擎,如果需要事务支持必须使用InnoDB存储引擎。注意:MyISAM存储引擎 B-tree索引有一个很大的限制:参与一个索引的所有字段的长度之和不能超过1000字节。另外MyISAM数据和索引是分开,而InnoDB的数据存储是按聚簇(cluster)索引有序排列的,主键是默认的聚簇(cluster)索引,因此MyISAM虽然在一般情况下,查询性能比InnoDB高,但InnoDB的以主键为条件的查询性能是非常高的。规则2:命名规则。数据库和表名应尽可能和所服务的业务模块名一致服务与同一个子模块的一类表应尽量以子模块名(或部分单词)为前缀或后缀表名应尽量包含与所存放数据对应的单词字段名称也应尽量保持和实际数据相对应联合索引名称应尽量包含所有索引键字段名或缩写,且各字段名在索引名中的顺序应与索引键在索引中的索引顺序一致,并尽量包含一个类似idx的前缀或后缀,以表明期对象类型是索引。约束等其他对象也应该尽可能包含所属表或其他对象的名称,以表明各自的关系规则3:数据库字段类型定义经常需要计算和排序等消耗CPU的字段,应该尽量选择更为迅速的字段,如用TIMESTAMP(4个字节,最小值1970-01-01 00:00:00)代替Datetime(8个字节,最小值1001-01-01 00:00:00),通过整型替代浮点型和字符型变长字段使用varchar,不要使用char对于二进制多媒体数据,流水队列数据(如日志),超大文本数据不要放在数据库字段中规则4:业务逻辑执行过程必须读到的表中必须要有初始的值。避免业务读出为负或无穷大的值导致程序失败规则5:并不需要一定遵守范式理论,适度的冗余,让Query尽量减少Join规则6:访问频率较低的大字段拆分出数据表。有些大字段占用空间多,访问频率较其他字段明显要少很多,这种情况进行拆分,频繁的查询中就不需要读取大字段,造成IO资源的浪费。规则7:大表可以考虑水平拆分。大表影响查询效率,根据业务特性有很多拆分方式,像根据时间递增的数据,可以根据时间来分。以id划分的数据,可根据id%数据库个数的方式来拆分。一.数据库索引规则8:业务需要的相关索引是根据实际的设计所构造sql语句的where条件来确定的,业务不需要的不要建索引,不允许在联合索引(或主键)中存在多于的字段。特别是该字段根本不会在条件语句中出现。规则9:唯一确定一条记录的一个字段或多个字段要建立主键或者唯一索引,不能唯一确定一条记录,为了提高查询效率建普通索引规则10:业务使用的表,有些记录数很少,甚至只有一条记录,为了约束的需要,也要建立索引或者设置主键。规则11:对于取值不能重复,经常作为查询条件的字段,应该建唯一索引(主键默认唯一索引),并且将查询条件中该字段的条件置于第一个位置。没有必要再建立与该字段有关的联合索引。规则12:对于经常查询的字段,其值不唯一,也应该考虑建立普通索引,查询语句中该字段条件置于第一个位置,对联合索引处理的方法同样。规则13:业务通过不唯一索引访问数据时,需要考虑通过该索引值返回的记录稠密度,原则上可能的稠密度最大不能高于0.2,如果稠密度太大,则不合适建立索引了。当通过这个索引查找得到的数据量占到表内所有数据的20%以上时,则需要考虑建立该索引的代价,同时由于索引扫描产生的都是随机I/O,生其效率比全表顺序扫描的顺序I/O低很多。数据库系统优化query的时候有可能不会用到这个索引。规则14:需要联合索引(或联合主键)的数据库要注意索引的顺序。SQL语句中的匹配条件也要跟索引的顺序保持一致。注意:索引的顺势不正确也可能导致严重的后果。规则15:表中的多个字段查询作为查询条件,不含有其他索引,并且字段联合值不重复,可以在这多个字段上建唯一的联合索引,假设索引字段为 (a1,a2,…an),则查询条件(a1 op val1,a2 op val2,…am op valm)m<=n,可以用到索引,查询条件中字段的位置与索引中的字段位置是一致的。规则16:联合索引的建立原则(以下均假设在数据库表的字段a,b,c上建立联合索引(a,b,c))联合索引中的字段应尽量满足过滤数据从多到少的顺序,也就是说差异最大的字段应该房子第一个字段建立索引尽量与SQL语句的条件顺序一致,使SQL语句尽量以整个索引为条件,尽量避免以索引的一部分(特别是首个条件与索引的首个字段不一致时)作为查询的条件Where a=1,where a>=12 and a<15,where a=1 and b<5 ,where a=1 and b=7 and c>=40为条件可以用到此联合索引;而这些语句where b=10,where c=221,where b>=12 and c=2则无法用到这个联合索引。当需要查询的数据库字段全部在索引中体现时,数据库可以直接查询索引得到查询信息无须对整个表进行扫描(这就是所谓的key-only),能大大的提高查询效率。 当a,ab,abc与其他表字段关联查询时可以用到索引当a,ab,abc顺序而不是b,c,bc,ac为顺序执行Order by或者group不要时可以用到索引以下情况时,进行表扫描然后排序可能比使用联合索引更加有效 a.表已经按照索引组织好了 b.被查询的数据站所有数据的很多比例。规则17:重要业务访问数据表时。但不能通过索引访问数据时,应该确保顺序访问的记录数目是有限的,原则上不得多于10.二.Query语句与应用系统优化规则18:合理构造Query语句Insert语句中,根据测试,批量一次插入1000条时效率最高,多于1000条时,要拆分,多次进行同样的插入,应该合并批量进行。注意query语句的长度要小于mysqld的参数 max_allowed_packet查询条件中各种逻辑操作符性能顺序是and,or,in,因此在查询条件中应该尽量避免使用在大集合中使用in永远用小结果集驱动大记录集,因为在mysql中,只有Nested Join一种Join方式,就是说mysql的join是通过嵌套循环来实现的。通过小结果集驱动大记录集这个原则来减少嵌套循环的循环次数,以减少IO总量及CPU运算次数尽量优化Nested Join内层循环。只取需要的columns,尽量不要使用select *仅仅使用最有效的过滤字段,where 字句中的过滤条件少为好尽量避免复杂的Join和子查询 Mysql在并发这块做得并不是太好,当并发量太高的时候,整体性能会急剧下降,这主要与Mysql内部资源的争用锁定控制有关,MyIsam用表锁,InnoDB好一些用行锁。规则19:应用系统的优化合理使用cache,对于变化较少的部分活跃数据通过应用层的cache缓存到内存中,对性能的提升是成数量级的。对重复执行相同的query进行合并,减少IO次数。事务相关性最小原则此文已由腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号

March 11, 2019 · 1 min · jiezi

RDS性能降低 - 复盘 - Honeycomb

RDS性能降低 - 复盘 - Honeycomb原文:https://www.honeycomb.io/blog…译:祝坤荣概要注:除非特别说明,所有时间都是UTC。5月3号周四, 从00:39:08 UTC(周三 17:39 PDT)我们经历了一次Honeycomb服务的大约24分钟的彻底停机。大部分服务恢复时间是2018-05-03 01:02:49,所有面向客户的服务恢复是在01:07:00。影响在停机期间,客户不能登录或查看Honeycomb服务的图表。停机期间发送给Honeycomb API的事件被大量拒绝;大约86%的API不能服务,而且大约81%的应该已经提交的事件在停机期间没有被记录。(百分比的差距是由于一个单独的API可以处理不同的事件并且停机没有平均影响到所有数据集)由于Honeycomb API没有报告成功,一些仍存储在客户服务上的事件在Honeycomb API恢复正常后又被重新提交。停机期间大约有15%的事件成功保存我们对这次停机影响的每一个客户都十分抱歉。我们对于数据的管理十分认真,并通过对系统的多项改进措施来确保未来这类的停机事件不会造成数据丢失,并确保我们在类似的失败中可以更快的恢复。发生了什么?事后看,故障链只有4点连通:我们产品使用的RDS MySQL数据库实例经历了一次突然的和大规模的性能减低; P95(query_time)从11毫秒变成>1000毫秒,同时写操作吞吐在20秒内从 780/秒降到 5/秒。RDS没有识别到故障,所以 Mutil-AZ feature没有激活故障转移。由于增加的query_time导致的延迟, Go的MySQL客户端连接池被等待慢查询结果的连接填满了并且作为补偿又打开了更多的连接。MySQL服务器的max_connections设置达到了上限,导致定时任务和新启动的后台进程不能连接到数据库并导致“Error 1040:Too many connections”的日志信息。恢复部分很快:RDS数据库的延迟和吞吐突然出现改善;在20秒内P95(query_time)写从>600ms 掉到 <200ms, 同时总吞吐从<500OPS 变成>2500OPS。排队的事件快速完成,数据库在70秒内的OPS大于3000, 并在回到正常状态时变成: 300-500OPS,<10ms 写。我们如何得到答案的故事是一个如何使用Honeycomb来debug生产系统的有趣的例子。根因分析在之后第二天早上我们的复盘会议上,两个理论摆在桌上:大量的连接数导致停机,并引起了数据库运行缓慢,或数据库由于一些原因运行缓慢,导致大量的连接数。我们担心一些bug隐藏在我们的应用里(或我们使用的其中一个Go库)导致我们的应用在不能连接数据库时关机,这样在同样情况再发生时又会导致一样的停机故障。每个人都同意这很像是下层数据库的问题(存储,CPU或连接)是根因,但我们也同意如果我们以抱怨网络的方式忽略一个潜在应用级的bug会更有危险。作为开发者的责任:这可能不是数据库,而可能是你的代码问题。为了降低风险,我们决定在受控环境来重现Error 1040场景并观察系统表现。在我们的实验集群上重现连接池溢出清楚的表明了连接满确实会影响应用并导致定时任务失败,它不会导致失控的CPU或延迟升高。我们现在有两个数据集:生产的停机和实验用的。由于我们使用Honeycomb来观察Honeycomb,所以在这个例子对比A和B很容易实验生产停机左边,实验集群从12:30到13:23(除了一些失败的定时任务很难看出证据)运行,而在右边,生产的停机很清楚地显示着。实验有个空结果:我们没有发现 Error 1040导致了停机。看起来像是系统的一些底层问题导致的。有了这个信息,我们需要在生产数据上挖掘的更深入了。由于Honeycomb数据集是高保真的(我们不做任何聚合或预先的计算),可以将数据调整到每秒级别并调整数据来抽取模式。这里是从rdslogs里记录的性能数据。有15秒没有活动,然后有一批query_time值达到了15秒的完成动作,看起来很明显。在结束时的性能异常也有一个有意思的热力图模式:概括下,数据展示了高于23分钟的低吞吐,高延迟行为,并持续了少于30秒切换区域,之前是正常的高吞吐,低延迟,尖峰应用驱动的行为,接着是大量的追赶事件,最后切换到正常的高吞吐模式。由于这不是一个全面的根因分析,但对于我们明确问题在数据库系统而不是我们的应用代码已经够了;我们的应用看起来运行正常。我们之后再SQL的normalized_query字符串上验证了我们的代码在恢复过程中工作符合预期。得到这些信息后我们重新调查了我们的RDS配置并确认Multi-AZ是打开的。实例监视面板没有显示任何健康事件。AWS文档指出Multi-AZ不会考虑性能作为健康检查的内容经验受管理的数据库很好,除非他们没托管。我们会优先考虑在“game day”运行我们的实验RDS做故障转移来重新验证在硬件故障时我们对于我们系统运行的理解。虽然我们不认为RDS会有很多硬件故障,但当我们需要处理RDS AZ故障转移时我们需要一个准备好的手册来执行。我们在改进我们的管道来保证在基础设施不能连接到数据库时接受的用户事件我们可以缓存它们而不是失败。硬件问题发生,这就是生活;丢失数据不需要发生。时间线 (2018-05-03 UTC)00:39 – outage starts00:40 – first alert00:42 – engineers start investigation00:50 – escalation, multiple investigators looking at several potential avenues00:55 – status.honeycomb.io updated to keep our customers informed00:58 – first engineering interventions to attempt to heal system, minimal impact01:03 – outage resolution starts01:04:23 – resolution completes, system stabilized01:15 – engineers conclude that outage is resolved, update status.honeycomb.io本文来自微信公众号「麦芽面包,id「darkjune_think」转载请注明。交流Email: zhukunrong@yeah.net ...

March 11, 2019 · 1 min · jiezi

关系型数据库中的事务管理详解:并发控制与事务日志

本文节选自:关系型数据库理论 https://url.wx-coder.cn/DJNQn ,涉及引用/整理的文章列举在了 Database-List。关系型数据库中的事务管理详解:并发控制与事务日志数据库系统的萌芽出现于 60 年代。当时计算机开始广泛地应用于数据管理,对数据的共享提出了越来越高的要求。传统的文件系统已经不能满足人们的需要。能够统一管理和共享数据的数据库管理系统(DBMS)应运而生。1961 年通用电气公司(General ElectricCo.)的 Charles Bachman 成功地开发出世界上第一个网状 DBMS 也是第一个数据库管理系统—— 集成数据存储(Integrated DataStore IDS),奠定了网状数据库的基础。1970 年,IBM 的研究员 E.F.Codd 博士在刊物 Communication of the ACM 上发表了一篇名为“A Relational Modelof Data for Large Shared Data Banks”的论文,提出了关系模型的概念,奠定了关系模型的理论基础。1974 年,IBM 的 Ray Boyce 和 DonChamberlin 将 Codd 关系数据库的 12 条准则的数学定义以简单的关键字语法表现出来,里程碑式地提出了 SQL(Structured Query Language)语言。在很长的时间内,关系数据库(如 MySQL 和 Oracle)对于开发任何类型的应用程序都是首选,巨石型架构也是应用程序开发的标准架构。本文即是对关系型数据库中的事务管理相关内容进行讨论。事务基础ACID事务提供一种全做,或不做(All or Nothing)的机制,即将一个活动涉及的所有操作纳入到一个不可分割的执行单元,组成事务的所有操作只有在所有操作均能正常执行的情况下方能提交,只要其中任一操作执行失败,都将导致整个事务的回滚。数据库事务具有 ACID 属性,即原子性(Atomic)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),在分布式事务 https://url.wx-coder.cn/7p8Xx 中我们也会讨论分布式系统中应该如何实现事务机制。ACID 包含了描述事务操作的整体性的原子性,描述事务操作下数据的正确性的一致性,描述事务并发操作下数据的正确性的隔离性,描述事务对数据修改的可靠性的持久性。针对数据库的一系列操作提供了一种从失败状态恢复到正常状态的方法,使数据库在异常状态下也能够保持数据的一致性,且面对并发访问时,数据库能够提供一种隔离方法,避免彼此间的操作互相干扰。原子性(Atomicity):整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。例如:银行转账,从 A 账户转 100 元至 B 账户,分为两个步骤:从 A 账户取 100 元;存入 100 元至 B 账户。这两步要么一起完成,要么一起不完成。一致性(Consistency):在事务开始之前和事务结束以后,数据库数据的一致性约束没有被破坏;即当事务 A 与 B 同时运行,无论 A,B 两个事务的结束顺序如何,数据库都会达到统一的状态。隔离性(Isolation):数据库允许多个并发事务同时对数据进行读写和修改的能力,如果一个事务要访问的数据正在被另外一个事务修改,只要另外一个事务未提交,它所访问的数据就不受未提交事务的影响。隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。 例如:现有有个交易是从 A 账户转 100 元至 B 账户,在这个交易事务还未完成的情况下,如果此时 B 查询自己的账户,是看不到新增加的 100 元的。持久性(Durability):当某个事务一旦提交,无论数据库崩溃还是其他未知情况,该事务的结果都能够被持久化保存下来。隔离级别SQL 标准定义了 4 类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。隔离级别脏读(Dirty Read )不可重复读(NonRepeatable Read )幻读(Phantom Read )未提交读(Read Uncommitted)可能可能可能提交读(Read Committed )不可能可能可能可重复读(Repeatable Read )不可能不可能可能可串行化(Serializable )不可能不可能不可能Read Uncommitted | 未提交读在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。Read Committed 提交读这是大多数数据库系统的默认隔离级别比如 Sql Server, Oracle 等,但不是 MySQL 默认的。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的 Commit,所以同一查询可能返回不同结果。Repeatable Read | 重复读当隔离级别设置为 Repeatable Read 时,可以避免不可重复读。不可重复读是指事务 T1 读取数据后,事务 T2 执行更新操作,使 T1 无法再现前一次读取结果。具体地讲,不可重复读包括三种情况:事务 T1 读取某一数据后,事务 T2 对其做了修改,当事务 T1 再次读该数据时,得到与前一次不同的值。例如,T1 读取 B=100 进行运算,T2 读取同一数据 B,对其进行修改后将 B=200 写回数据库。T1 为了对读取值校对重读 B,B 已为 200,与第一次读取值不一致。事务 T1 按一定条件从数据库中读取了某些数据记录后,事务 T2 删除了其中部分记录,当 T1 再次按相同条件读取数据时,发现某些记录神密地消失了。事务 T1 按一定条件从数据库中读取某些数据记录后,事务 T2 插入了一些记录,当 T1 再次按相同条件读取数据时,发现多了一些记录,也就是幻读。这是 MySQL 的默认事务隔离级别,它确保在一个事务内的相同查询条件的多次查询会看到同样的数据行,都是事务开始时的数据快照。虽然 Repeatable Read 避免了不可重复读,但还有可能出现幻读。简单说,就是当某个事务在读取某个范围内的记录时,另外的一个事务又在该范围内插入新的记录。在之前的事务在读取该范围的记录时,就会产生幻行,InnoDB 通过间隙锁(next-key locking)策略防止幻读的出现。Serializable | 序列化Serializable 是最高的事务隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。该隔离级别代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读。并发控制并发控制旨在针对数据库中对事务并行的场景,保证 ACID 中的一致性(Consistency)与隔离性(Isolation)。假如所有的事务都仅进行数据读取,那么事务之间并不会有冲突;而一旦某个事务读取了正在被其他事务修改的数据或者两个事务修改了相同的数据,那么数据库就必须来保证事务之间的隔离,来避免某个事务因为未见最新的数据而造成的误操作。解决并发控制问题最理想的方式就是能够每当某个事务被创建或者停止的时候,监控所有事务的所有操作,判断是否存在冲突的事务,然后对冲突事务中的操作进行重排序以尽可能少地减少冲突,而后以特定的顺序运行这些操作。绝大部分数据库会采用锁(Locks)或者数据版本控制(Data Versioning)的方式来处理并发控制问题。数据库技术中主流的三种并发控制技术分别是: Multi-version Concurrency Control (MVCC), Strict Two-Phase Locking (S2PL), 以及 Optimistic Concurrency Control (OCC),每种技术也都有很多的变种。在 MVCC 中,每次写操作都会在旧的版本之上创建新的版本,并且会保留旧的版本。当某个事务需要读取数据时,数据库系统会从所有的版本中选取出符合该事务隔离级别要求的版本。MVCC 的最大优势在于读并不会阻塞写,写也不会阻塞读;而像 S2PL 这样的系统,写事务会事先获取到排他锁,从而会阻塞读事务。PostgreSQL 以及 Oracle 等 RDBMS 实际使用了所谓的 Snapshot Isolation(SI)这个 MVCC 技术的变种。Oracle 引入了额外的 Rollback Segments,当写入新的数据时,老版本的数据会被写入到 Rollback Segment 中,随后再被覆写到实际的数据块。PostgreSQL 则是使用了相对简单的实现方式,新的数据对象会被直接插入到关联的 Table Page 中;而在读取表数据的时候,PostgreSQL 会通过可见性检测规则(Visibility Check Rules)来选择合适的版本。锁管理器(Lock Manager)基于锁的方式基础理念为:如果某个事务需要数据,则对数据加锁,操作完毕后释放锁;如果过程中其他事务需要锁,则需要等到该事务释放数据锁,这种锁也就是所谓的排他锁(Exclusive Lock)。不过使用排他锁会带来极大的性能损耗,其会导致其他那些仅需要读取数据的事务也陷入等待。另一种加锁的方式称为共享锁(Shared Lock),当两个事务都声明读取数据 A 时,它们会分别给 A 添加共享锁;对于此事需要修改数据 A 的事务而言,它必须等待所有的共享锁释放完毕之后才能针对数据 A 添加排他锁。同样地,对于已经被设置了排他锁的数据,仅有读取请求的事务同样需要等到该排他锁被释放后才能添加共享锁。从锁定的数据范围锁粒度(Lock Granularity)来看分为:表锁:管理锁的开销最小,同时允许的并发量也最小的锁机制。MyIsam 存储引擎使用的锁机制。当要写入数据时,把整个表都锁上,此时其他读、写动作一律等待。在 MySql 中,除了 MyIsam 存储引擎使用这种锁策略外,MySql 本身也使用表锁来执行某些特定动作,比如 ALTER TABLE.行锁:可以支持最大并发的锁策略。InnoDB 和 Falcon 两种存储引擎都采用这种策略。锁管理器(Lock Manager)即负责分配与释放锁,大部分数据库是以哈希表的方式来存放持有锁以及等待锁的事务。在 MySQL 实战 https://url.wx-coder.cn/Tu5dq 中我们也讨论了如何触发锁机制,譬如查询加锁,select * from testlock where id=1 for update;,即查询时不允许更改,该语句在自动提交为 off 或事务中生效,相当于更改操作,模拟加锁;而更像类操作 update testlock name=name; 则是会自动加锁。同样的,参考并发编程导论 https://url.wx-coder.cn/Yagu8 中的讨论,只要存在锁的地方就会存在死锁(Deadlock)的可能性:在发生死锁的时候,锁管理器会根据一定的规则来选取应该终止或者被回滚的事务:根据是否能最小化需要被回滚的数据;根据事务发生的先后顺序;根据事务执行所需要的时间,以尽可能避免饥饿状态的出现;根据需要回滚的关联事务的数目;避免死锁,确保纯隔离的最简单方法是在事务开始时获取锁并在事务结束时释放锁。这意味着事务必须在启动之前等待其所有锁,并且在事务结束时释放事务持有的锁,这种方式会浪费很多时间来等待所有锁。实际的数据库,譬如 DB2 与 SQL Server 中往往采取两阶段锁协议(Two-Phase Locking Protocol),即将事务过程切分为两个阶段:Growing Phase: 该阶段仅可以获取锁,而不可以释放锁。Shrinking Phase: 该阶段仅可以释放锁,而不可以获取新的锁。该策略能够减少其他事务等待锁的时间,并且避免某个事务在中途修改了并不是它初次申请的数据。MVCC在并发编程导论 https://url.wx-coder.cn/Yagu8 中我们讨论了两种不同类型的锁:乐观锁(Optimistic Lock)与悲观锁(Pessimistic Lock),前文介绍的各种锁即是悲观锁,而 MVCC(Multiple Version Concurrency Control) 这样的基于数据版本的锁则是乐观锁,它能够保证读写操作之间不会相互阻塞:每个事务都可以在同一时间修改相同的数据;每个事务会保有其需要的数据副本;如果两个事务修改了相同的数据,那么仅有单个更改操作会被接收,另一个操作会被回滚或者重新执行。乐观锁,大多是基于数据版本(Version)记录机制实现。数据版本即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 version 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。而 PostgreSQL 中则是依赖于 txid 以及 Commit Log 结合而成的可见性检测机制来实现 MVCC,详情可以参考 PostgreSQL 架构机制 https://url.wx-coder.cn/SgRDQ 中关于并发控制相关的介绍。日志管理器(Log Manager)数据库事务由具体的 DBMS 系统来保障操作的原子性,同一个事务当中,如果有某个操作执行失败,则事务当中的所有操作都需要进行回滚,回到事务执行前的状态。导致事务失败的原因有很多,可能是因为修改不符合表的约束规则,也有可能是网络异常,甚至是存储介质故障等,而一旦事务失败,则需要对所有已作出的修改操作进行还原,使数据库的状态恢复到事务执行前的状态,以保障数据的一致性,使修改操作要么全部成功、要么全部失败,避免存在中间状态。访问磁盘中的数据往往速度较慢,换言之,内存中数据的访问速度还是远快于 SSD 中的数据访问速度。基于这个考量,基本上所有数据库引擎都尽可能地避免访问磁盘数据。并且无论数据库表还是数据库索引都被划分为了固定大小的数据页(譬如 8 KB)。当我们需要读取表或者索引中的数据时,关系型数据库会将磁盘中的数据页映射入存储缓冲区。当我们需要修改数据时,关系型数据库首先会修改内存页中的数据,然后利用 fsync 这样的同步工具将改变同步回磁盘中。不过一旦数据库突发崩溃,那么缓冲区中的数据也就丢失,最终打破了事务的持久性。另一个极端情况而言,我们也可以随时将数据写入到磁盘中,但是在崩溃的时候,很可能只写入了一半的数据,而打破了事务的原子性(Atomicity)。为了解决这个问题,我们可以采取以下两种方案:影子拷贝(Shadow Copies/Pages):每个事务会创建数据库的部分拷贝,然后针对这些拷贝进行操作。在发生异常的时候,这些拷贝会被移除;正常的情况下,数据库则会立刻将这个拷贝写入到磁盘然后移除老的数据块。事务日志(Transaction Log):所谓的事务日志即是独立的存储空间,在将数据写入真正的数据表之外,数据库都会将事务操作顺序写入到某个日志文件中。在实际情况下,Shadow Copies/Pages 会受到极大的磁盘限制,因此绝大部分数据库还是选择了以事务日志的方式。事务日志(Transaction Log)为了实现数据库状态的恢复,DBMS 系统通常需要维护事务日志以追踪事务中所有影响数据库数据的操作,以便执行失败时进行事务的回滚。以 MySQL 的 InnoDB 存储引擎为例,InnoDB 存储引擎通过预写事务日志的方式,来保障事务的原子性、一致性以及持久性。它包含 Redo 日志和 Undo 日志,Redo 日志在系统需要的时候,对事务操作进行重做,如当系统宕机重启后,能够对内存中还没有持久化到磁盘的数据进行恢复,而 Undo 日志,则能够在事务执行失败的时候,利用这些 Undo 信息,将数据还原到事务执行前的状态。事务日志可以提高事务执行的效率,存储引擎只需要将修改行为持久到事务日志当中,便可以只对该数据在内存中的拷贝进行修改,而不需要每次修改都将数据回写到磁盘。这样做的好处是,日志写入是一小块区域的顺序 I/O,而数据库数据的磁盘回写则是随机 I/O,磁头需要不停地移动来寻找需要更新数据的位置,无疑效率更低,通过事务日志的持久化,既保障了数据存储的可靠性,又提高了数据写入的效率。当某个事务需要去更改数据表中某一行时,未提交的改变会被写入到内存数据中,而之前的数据会被追加写入到 Undo Log 文件中。Oracle 或者 MySQL 中使用了所谓 Undo Log 数据结构,而 SQL Server 中则是使用 Transaction Log 完成此项工作。PostgreSQL 并没有 Undo Log,不过其内建支持所谓多版本的表数据,即同一行的数据可能同时存在多个版本。总而言之,任何关系型数据库都采用的类似的数据结构都是为了允许回滚以及数据的原子性。某个事务提交之后,内存中的改变就需要同步到磁盘中。不过并不是所有的事务提交都会立刻触发同步,过高频次的同步反而会对应用性能造成损伤。这里关系型数据库就是依靠 Redo Log 来达成这一点,它是一个仅允许追加写入的基于磁盘的数据结构,它会记录所有尚未执行同步的事务操作。相较于一次性写入固定数目的数据页到磁盘中,顺序地写入到 Redo Log 会比随机访问快上很多。因此,关于事务的 ACID 特性的保证与应用性能之间也就达成了较好的平衡。该数据结构在 Oracle 与 MySQL 中就是叫 Redo Log,而 SQL Server 中则是由 Transaction Log 执行,在 PostgreSQL 中则是使用 Write-Ahead Log(WAL)。下面我们继续回到上面的那个问题,应该在何时将内存中的数据写入到磁盘中。关系型数据库系统往往使用检查点来同步内存的脏数据页与磁盘中的对应部分。为了避免 IO 阻塞,同步过程往往需要等待较长的时间才能完成。因此,关系型数据库需要保证即使在所有内存脏页同步到磁盘之前引擎就崩溃的时候不会发生数据丢失。同样地,在每次数据库重启的时候,数据库引擎会基于 Redo Log 重构那些最后一次成功的检查点以来所有的内存数据页。WAL(Write-Ahead Logging)WAL 协议主要包含了以下三条规则:每个数据库中的修改操作都会产生一条记录,该记录必须在数据被写入到数据库之前被写入到日志文件中;所有的操作日志都必须严格按序记录,即如果 A 记录发生在 B 之前,那么 A 也必须在 B 之前被写入到日志中;在事务被提交之后,必须在日志写入成功之后才能回复事务处理成功。同样可以参考 PostgreSQL 架构机制 https://url.wx-coder.cn/SgRDQ 中有关于 WAL 的实例讨论。延伸阅读欢迎关注某熊的技术之路公众号或某熊的技术之路指北,让我们一起前行。 ...

March 11, 2019 · 2 min · jiezi

相对的一对多和多对一,一对一的分表概念

导读最近公司在做这样的一个业务,由我来设计数据库,其中有有一个需求,根据原型图设计数据库吗,这也是我第一次独立设计数据库,因涉及公司的机密,只能展示部分原型图:1、如图是项目的原型图,每个项目都是一条记录,因而,这可以设计成独立的项目表2、当点击红框中的“人员”,就会出现一个弹框,弹框如图所示。这是项目人员,自然是根据项目来的。不同项目可能有不懂的人员。因而,这可以设计成一张项目人员表。表中的字段肯定有“人员类型”,比如业务员,业务部经理等。外键自然是项目的主键。一个项目可能会有多条记录,比如说业务员一条记录,业务部经理一条记录等。所以,对于项目而言,这是一对多的关系,是什么意思呢?一个项目在项目人员表有多条记录;但对于项目人员来说,这是多对一的关系,多条项目人员的记录对着一个项目。如图所示:生成数据表结构是有Java代码生成的,因而,代码如下:/** * Created By zby on 15:00 2018/12/25 * 项目人员 /@AllArgsConstructor@NoArgsConstructor@Data@Entity@Table(name = “zq_project_person”)public class ProjectPerson extends BaseObj { /* * 人员类型 / @Enumerated(EnumType.STRING) @Column(name = “person_type”) private PersonTypeEnum personType; /* * 人员的ids,存储人员的编号 / @Column(name = “ids”) private String ids; /* * 选择时间 / @Column(name = “op_time”) private Date opTime; /* * 项目 / @ManyToOne @JoinColumn(name = “project_id”) private Project project;}人员类型是枚举,代码如下:/* * Created By zby on 9:43 2018/12/27 /public enum PersonTypeEnum implements TitleEnum { PERSON_TYPE_SALESMAN(“业务员”), PERSON_TYPE_SALESMAN_MANAGER(“业务部经理”), PERSON_TYPE_DESIGNER(“设计师”), PERSON_TYPE_DESIGNER_MANAGER(“设计部经理”), PERSON_TYPE_PROJECT_SUPERVISION(“工程监理”), PERSON_TYPE_ENGINEERING_MANAGER(“工程部经理”); 。。。一对多和多对一一和多的概念通过以上的分析,我们知道一对多和多对一的关系。这个“一”和“多”到底是什么是 “一”,什么又是“多”呢?在实际的项目中,我们多问几个为什么,成长也会特别的快。不要怕问,也许,人家可能没时间回答你,或许,人家害怕把你教会了。这样,也要问,不问就永远不知道。“一”针对“一个点”来说,就像是spring中的aop(Aspect Oriented Programming)编程一样。spring框本身就是以算法驱动为开发,但我们在使用它时,一般是以业务驱动为开发的。既然是业务处理,自然涉及到业务的诸多流程,比如,专门将JVM中的瞬时态的对象转化为数据库的持久态的字段值、或将数据库的持久态的字段值转化为顺势态的Java对象的dao(data access object)层;专门处理数据库事务相关的事务层(service层);专门处理接受前端数据和返回前端数据的控制层(controller层)。我们单单只考虑其中的一个业务流程,即数据库的事务层(service层)。这就是一个点,也就是aop需要考虑的一个点。aop的配置文件如下所示:<?xml version=“1.0” encoding=“UTF-8”?><beans xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xmlns:aop=“http://www.springframework.org/schema/aop" xmlns:tx=“http://www.springframework.org/schema/tx" xmlns=“http://www.springframework.org/schema/beans" xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd" default-lazy-init=“true”> <!– =================================================================== –> <!– AOP: Configuration and Aspects @TODO:事务缩小范围到 固定的 名字 –> <!– 采用 ant风格的编写方式, 一个 * 表示至少有0个字母,两个 ** 表示至少有0个目录 –> <!– =================================================================== –> <aop:config> <aop:advisor id=“managerTxOne” advice-ref=“txAdvice” pointcut=“execution( ..service.Service.(..))” order=“0”/> </aop:config> <tx:annotation-driven/> <!– 开启AOP监听 只对当前配置文件有效 –> <aop:aspectj-autoproxy expose-proxy=“true”/> <tx:advice id=“txAdvice”> <tx:attributes> <!–获得单个对象–> <tx:method name=“get” read-only=“true”/> <!–列表对象–> <tx:method name=“list*” read-only=“true”/> <!–搜索分页对象–> <tx:method name=“search*” read-only=“true”/> <tx:method name=”*”/> </tx:attributes> </tx:advice> </beans>我们注意上面的<aop:config> 。。。</aop:config>这段代码,其实就是配置的设置这个点。这个是处理所有以service结尾的。因而,回头说说我们数据的库的 “一”,这是针对一条记录来说的,比如上文说到的项目表中有很多项目,我们单单举出来编号为167的项目,那么,这就是 “一”。“多”我们针对的是当前数据表中涉及到外键字段的记录的条数。比如在项目人员表中,外键名为project_id的项目编号等于167的有很多条记录。那么,这就是“多”。一对多和多对一的理论示例所以,一和多之间,并非绝对的关系,只是相对来说。就像我们初中学过的运动间的相对关系。什么是静止的,什么是运动的?我们坐在车厢里,相对于窗外的行道树,我们是运动的。相对于车子,我们就是静止的。所以,也有句话,叫做日行不动八万里。万物就是这样,没有绝对的关系。因而,在实际的项目中,我们遇到了太多的一对多(多对一)的关系,比如:项目和项目人员就是一个典型的一对多和多对一的关系。因为上文提到了,就不再赘述。项目和项目阶段也是一对多和多对一的关系。一个项目从执行到竣工,肯定有很多的阶段,每个阶段的执行时间、阶段名称、涉及到的人等。因而,这也是一对多的关系。所以,在项目阶段表中相同项目编号的记录至少零条。评论表和用户、文章也是一对多和多对一的关系。比如我在思否写了篇文章,当其他用户登录了,假设评论我的文章,一个用户可以评论多次一篇文章,一个用户也可以评论我的多篇文章。所以,在评论表中,一个用户的记录至少零条,一个用户的文章的记录数至少零条。同一张表中也可以存在一对多的关系。如文章类型这张表,文章类型肯定有父类型。在这张表中,父类型至少有零条记录数。如栏目,栏目肯定有父类型,父栏目下面有子栏目,父栏目的记录至少有零条。根据4所说的,一个文章类型下面有多篇文章,这也是典型的一对多;一个栏目下面有多个内容,这也是典型的一对多。。。。在有的话,欢迎评论。以项目人员为示例业务需求我们只有点击选择人员,才将数据保存到数据库中,但是,我们还是要将人员类型按照顺序展示出来。因而,我们需要考虑的是,判断数据库中是否存在某种人员类型,比如业务员类型,业务部经理类型。存在,就把数据表的对象取出来,然后填充到集合容器中。不存在,我们就要不存在人员类型填充到集合容器中。算法思想:在做项目之前,我们需要考虑算法,否则,做出来的东西虽然没错,但不是业务所需要的。我们从数据库中取出当前项目下的项目人员的记录,即 List<ProjectPerson> projectList = projectPersonDao.listProjectPersons(projectId);我们获取枚举对象的数组,然后遍历枚举集合,即 for (PersonTypeEnum obj : PersonTypeEnum.class.getEnumConstants());设置一个开关,true 表示数据存在该枚举对象,false 表示数据库不存在该枚举对象,即 boolean objInProjectPerson = false;再遍历projectList ,获取项目人员的对象,拿到人员类型的对象,与枚举对象进行比较,如果相等,就objInProjectPerson置为true,然后挑出内循环。判断objInProjectPerson状态,如果未false,就将人员类型的对象设置为当前枚举对象。最后,通过 projectPersonList.sort(Comparator<? super E> c)方法进行排序。因为方法形参的是Comparator接口,因而,我们需要一方法内部类的方式实现排序。方法实现通过上文的算法设计,我们编写实现方法: @Override public Result<List<ProjectPerson>> listProjectPersons(Long projectId) {// 【1】步骤一 List<ProjectPerson> projectList = projectPersonDao.listProjectPersons(projectId); List<ProjectPerson> projectPersonList = new ArrayList<>(); Class<PersonTypeEnum> clz = PersonTypeEnum.class; // 【2】步骤二 for (PersonTypeEnum obj : clz.getEnumConstants()) { // 【3】步骤三 boolean objInProjectPerson = false; for (ProjectPerson projectPerson : projectList) { // 【4】步骤四 if (obj.equals(projectPerson.getPersonType())) { projectPerson.setSort(obj.ordinal()); objInProjectPerson = true; projectPersonList.add(projectPerson); break; } } // 【5】步骤五 if (!objInProjectPerson) { ProjectPerson projectPerson = new ProjectPerson(); projectPerson.setPersonType(obj); projectPerson.setSort(obj.ordinal()); projectPersonList.add(projectPerson); } } // 【6】步骤6 projectPersonList.sort(new Comparator<ProjectPerson>() { @Override public int compare(ProjectPerson firstProjectPerson, ProjectPerson secondProjectPerson) { return firstProjectPerson.getSort() - secondProjectPerson.getSort(); } }); return ResultUtil.buildSuccess(projectPersonList); }一对一的概念我们有时也会涉及到一对一的数据表设计,这里是不大会用到的。这一般时用来分表所做的,比如说用户表和用户快照表。用户表存储用户必填的信息,碧如 账号、性别、密码、登录时间、退出时间等等。用户扩展表就是用户的扩展信息,比如爱好、昵称、等等。是用户可填可不填的。如果数据都放在同一张表中,会感觉比较乱的。总结我们在开发过程中和,要分明白何时以业务驱动为开发对象,何时以算法驱动为开发对象。这样,做一个有条理的人,你就会获得更多的知识。 ...

March 10, 2019 · 2 min · jiezi

亦大亦小如你--MySQL

写在前面MySQL 是个神奇的关系型数据库,真心感觉牛逼,因为做的项目比较杂,之前也碰到过 Oracle 数据库,给我的印象是 Oracle 很臃肿繁琐,配置多,如果是小项目用它的话感觉就像是杀鸡用牛刀,大材小用。但是也不是说Mysql不能用于大项目,MySQL 开元免费,是现在关系型数据库的主流产品,网上相应的文档和问题解决方案也会很多,意思就是比如菜鸟如我碰到了Mysql出的问题,网上基本上很全。系统环境 Debain 7Mysql 5.61.MySQL 简单操作命令//1.登录mysql,括号中的为可选项,$(包括$)后面为实际数据,-D是指定数据库登录mysql (-h$host) (-P$port) -u$user -p$pwd (-D$dbname) //地址 端口 账号 密码 数据库名//2.删除、创建数据库drop database dbname; //删除数据库create database dbname charset utf8 //创建数据库//3.删除、创建数据库表drop table tablename;//删除表create table tablename(id int, name varchar(80)); //创建表//4.表操作show triggers\g / show triggers; //查看触发器show variables like ‘character_set_database’; //查看库编码desc tablename; //查看表结构select current_date(); //查看表创建时间//5.导入sql文件use dbname;source /dbname.sql; //执行 sql 文件//6.当前的连接情况select current_user(); //查看当前登录账号show processlist; //查看当前进程show full processlist;//查看当前全部进程select user,host,Super_priv from mysql.user; //查看所有可连接用户、地址和权限信息(Super_priv 用户有super权限才可以导入数据)grant all privileges on . to root@’%’ identified by ‘root’ with grant option;flush privileges; //给root用户远程登录的所有权限2.自动导入sql文件2.1 shell操作#创建 createDb.sh,内容如下:#!/bin/bash#通过 shell 自动初始化数据库和表结构host=$1 #地址port=$2 #端口user=$3 #账号pwd=$4 #密码dbname=$5 #数据库名path=$6 #sql 文件路径mysql -h$host -P$port -u$user -p$pwd <<EOFdrop database if exists $dbname;create database $dbname charset utf8;use $dbname;source $pathCOMMIT;EOF#查看 shell 的执行过程命令sh -x ./shell //查看 shell 执行过程2.2 expect操作#!/usr/bin/expect -fset timeout 10set host [lindex $argv 0]set port [lindex $argv 1]set user [lindex $argv 2]set pwd [lindex $argv 3]set dbname [lindex $argv 4]set path [lindex $argv 5]set cset [lindex $argv 6] #字符编码spawn mysql -h$host -P$port -u$user -pexpect “Enter password: “send “$pwd\r"expect “mysql> “send “drop database if exists $dbname;create database $dbname charset $cset;\r"expect “mysql> “send “use $dbname;\r"expect “mysql> “send “source $path;\r"expect “mysql> “send “exit\r"interact回头研究再更新操作,有哪里写的不对的也请不吝赐教 ...

March 9, 2019 · 1 min · jiezi

基于Xtrabackup及可传输表空间实现多源数据恢复

本文目录一、使用背景1.可传输表空间基本流程2.使用前提条件/限制二、技术要点三、实施步骤1.实验环境2.源端操作 1) 造测试数据并模拟小压力 2) 备份单库数据 3) 备份表结构 4) 批量生成可传输表空间命令3.目标端操作 1) 备份数据恢复准备 2) 恢复表结构至目标端 3) 舍弃目标端对应表空间文件 4) 拷贝表数据及配置文件到目标端sbtest库数据目录 5) 执行导入表空间文件操作4.建立复制(多源复制)四、参考链接Keywords:MySQL xtrabackup Transportable Tablespaces MySQLdump multi-source replication一、使用背景 MySQL在5.7以后引入了多源复制(Multi-Source)功能,可用于将多个单独的数据库汇聚到一个数据库实例下,方便用户进行数据分析、汇总或推送至其他数据库平台。早期针对汇聚场景初始化各源端数据到汇聚库,为了提升效率通常会使用使用开源并行逻辑导入导出工具myloader/mydumper进行数据导入/导出,当源端多实例多库数据量较大(100G以上)情况下,使用myloader/mydumper花费的时间则可能无法满足时间要求,需要考虑是否有更高效的方式进行数据初始化同步操作,以及当复制通道异常时更便捷快速修复。ps:多源复制使用物理备份(xtrabackup)做数据初始化时,常规方式只有第一个通道可做覆盖还原,后续通道需逻辑还原或用其他方式还原,如该方案所描述的方式。MySQL在5.6以后支持了可传输表空间,以及Xtrabackup针对该功能在备份时提供了一个对应参数export,该参数支持对InnoDB存储引擎表的备份数据export转换,且与常规备份操作一样可生成备份时间点的binlog信息(GTID信息),结合这两特性可以更高效和快速的方式实现多库数据汇聚的初始导入操作。▽场景架构图如下▽1.可传输表空间基本流程先简单梳理一下可传输表空间基本流程:在源端将InnoDB表进行ibd数据文件导出处理(export tablespace))在目标端创建与源端相同表结构的表将目标端的表数据文件舍弃(discard tablespace)用源端的相对应ibd文件覆盖到目标端在目标端执行表空间文件的导入/置换(import tablespace)单纯使用可传输表空间功能无法记录对应表的事务点,也就是如果需要进行汇聚复制同步,无法知道从binlog哪个文件的哪个position点进行数据同步,这也就是需要引入xtrabackup及export功能的原因。备份元数据信息本身会记录复制同步点信息。2.使用前提条件/限制MySQL须为5.6以上版本表存储引擎须为InnoDB存储引擎(支持可传输表空间)导入完成后建议执行ANALYZE TABLE更新统计信息二、技术要点sysbench 源端造测试数据并模拟压力mysqldump 备份源端表结构concat()拼接批量可传输表空间SQLxtrabackup 备份源端单库(部分库)数据Transportable Tablespace可传输表空间功能Multi-source Replication 多源复制mysql-error.log 错误信息日志校验ANALYZE TABLE更新统计信息三、实施步骤 1.实验环境 2.源端操作 1) 造测试数据并模拟小压力使用sysbench创建4张各100W记录的测试表,并使用2个并发持续模拟业务压力:## 造数据shell> /opt/sysbench-0.9/sysbench/sysbench –test=/opt/sysbench-0.9/sysbench/tests/db/oltp.lua –oltp-table-size=1000000 –oltp-tables-count=4 –mysql-user=sysbench –mysql-password=sysbench –mysql-host=10.186.60.16 –mysql-port=3333 prepare## 模拟小压力shell> /opt/sysbench-0.9/sysbench/sysbench –test=/opt/sysbench-0.9/sysbench/tests/db/oltp.lua –oltp-table-size=1000000 –oltp-tables-count=4 –mysql-user=sysbench –mysql-password=sysbench –mysql-host=10.186.60.16 –mysql-port=3333 –num-threads=2 –max-requests=0 –max-time=0 –report-interval=1 run2) 备份单库数据使用xtrabackup备份工具对源端sbtest库进行单库备份,保存至/data/mysql/backup/目录下:shell> innobackupex –databases=sbtest /data/mysql/backup/3) 备份表结构为可传输表空间做准备,将源端表结构备份并后续在目标端导入:shell> cd /data/mysql/backupshell> mysqldump –no-data –set-gtid-purged=off sbtest>sbtest_schema.sql4) 批量生成可传输表空间命令discard使用concat函数拼接出批量DISCARD TABLESPACE的SQL:shell> cd /data/mysql/backupshell> mysql -ssre “select concat(‘ALTER TABLE ‘,TABLE_SCHEMA,’.’,TABLE_NAME,’ DISCARD TABLESPACE;’) from information_schema.tables where TABLE_SCHEMA=‘sbtest’;” >discard_tbs.sql## 输出文件如下所示shell> cat discard_tbs.sqlALTER TABLE sbtest.sbtest1 DISCARD TABLESPACE;ALTER TABLE sbtest.sbtest2 DISCARD TABLESPACE;ALTER TABLE sbtest.sbtest3 DISCARD TABLESPACE;ALTER TABLE sbtest.sbtest4 DISCARD TABLESPACE;- import使用concat函数拼接出批量IMPORT TABLESPACE的SQL:shell> cd /data/mysql/backupshell> mysql -ssre “select concat(‘ALTER TABLE ‘,TABLE_SCHEMA,’.’,TABLE_NAME,’ IMPORT TABLESPACE;’) from information_schema.tables where TABLE_SCHEMA=‘sbtest’;">import_tbs.sql## 输出文件如下所示shell> cat import_tbs.sqlALTER TABLE sbtest.sbtest1 IMPORT TABLESPACE;ALTER TABLE sbtest.sbtest2 IMPORT TABLESPACE;ALTER TABLE sbtest.sbtest3 IMPORT TABLESPACE;ALTER TABLE sbtest.sbtest4 IMPORT TABLESPACE;拷贝源端/data/mysql/backup目录下生成的所有相关文件到目标端/data/mysql/backup下3.目标端操作1) 备份数据恢复准备对备份数据进行apply-log日志应用及将数据进行export转换生成配置文件:shell> innobackupex –apply-log –export /data/mysql/backup/2019-02-22_15-26-20/## export执行完后sbtest库下备份文件如下所示## exp结尾的文件为Percona针对Percona XtraDB做export的配置文件## cfg结尾的文件为Percona针对MySQL可传输表空间export的配置文件shell> ll /data/mysql/backup/2019-02-22_15-26-20/sbtest/总用量 999556-rw-r—– 1 root root 67 2月 22 15:40 db.opt-rw-r–r– 1 root root 569 2月 22 15:53 sbtest1.cfg-rw-r—– 1 root root 16384 2月 22 15:53 sbtest1.exp-rw-r—– 1 root root 8632 2月 22 15:40 sbtest1.frm-rw-r—– 1 root root 255852544 2月 22 15:53 sbtest1.ibd-rw-r–r– 1 root root 569 2月 22 15:53 sbtest2.cfg-rw-r—– 1 root root 16384 2月 22 15:53 sbtest2.exp-rw-r—– 1 root root 8632 2月 22 15:40 sbtest2.frm-rw-r—– 1 root root 255852544 2月 22 15:53 sbtest2.ibd-rw-r–r– 1 root root 569 2月 22 15:53 sbtest3.cfg-rw-r—– 1 root root 16384 2月 22 15:53 sbtest3.exp-rw-r—– 1 root root 8632 2月 22 15:40 sbtest3.frm-rw-r—– 1 root root 255852544 2月 22 15:53 sbtest3.ibd-rw-r–r– 1 root root 569 2月 22 15:53 sbtest4.cfg-rw-r—– 1 root root 16384 2月 22 15:53 sbtest4.exp-rw-r—– 1 root root 8632 2月 22 15:40 sbtest4.frm-rw-r—– 1 root root 255852544 2月 22 15:53 sbtest4.ibd2) 恢复表结构至目标端将源端表结构在目标端数据库创建:shell> cd /data/mysql/backup## 手工创建sbtest库shell> mysql -e “create database sbtest;”## 导入源端对应的表结构shell> mysql sbtest< sbtest_schema.sql## 验证shell> mysql -e “show tables from sbtest;“3) 舍弃目标端对应表空间文件shell> cd /data/mysql/backupshell> mysql <discard_tbs.sql## 执行后效果如下所示,ibd文件已被舍弃shell> ll /data/mysql/data/sbtest/-rw-r—– 1 mysql mysql 67 2月 22 15:58 db.opt-rw-r—– 1 mysql mysql 8632 2月 22 15:58 sbtest1.frm-rw-r—– 1 mysql mysql 8632 2月 22 15:58 sbtest2.frm-rw-r—– 1 mysql mysql 8632 2月 22 15:58 sbtest3.frm-rw-r—– 1 mysql mysql 8632 2月 22 15:58 sbtest4.frm4) 拷贝表数据及配置文件到目标端sbtest库数据目录## 拷贝ibd文件shell> cp /data/mysql/backup/2019-02-22_15-26-20/sbtest/.ibd /data/mysql/data/sbtest/## 拷贝cfg文件shell> cp /data/mysql/backup/2019-02-22_15-26-20/sbtest/.cfg /data/mysql/data/sbtest/## 修改文件权限为mysql用户shell> chown -R mysql:mysql /data/mysql/data/sbtest5) 执行导入表空间文件操作shell> cd /data/mysql/backup## 出现Warning为可传输表空间正常输出,可忽略,详情可参考下图所示note说明shell> mysql <import_tbs.sqlWarning (Code 1814): InnoDB: Tablespace has been discarded for table ‘sbtest1’Warning (Code 1814): InnoDB: Tablespace has been discarded for table ‘sbtest2’Warning (Code 1814): InnoDB: Tablespace has been discarded for table ‘sbtest3’Warning (Code 1814): InnoDB: Tablespace has been discarded for table ‘sbtest4’## 可通过MySQL错误日志查看import期间是否存在导入异常shell> less /data/mysql/data/mysql-error.log4.建立复制(多源复制)mysql> CHANGE MASTER TO MASTER_HOST=‘10.186.60.16’, MASTER_USER=‘repl’, MASTER_PORT=3333, MASTER_PASSWORD=‘repl’,MASTER_LOG_FILE=‘mysql-bin.000004’,MASTER_LOG_POS=149327998 FOR CHANNEL ‘10-186-60-16’;mysql> CHANGE REPLICATION FILTER REPLICATE_WILD_DO_TABLE=(‘sbtest.%’);mysql> START SLAVE FOR CHANNEL ‘10-186-60-16’;mysql> SHOW SLAVE STATUS FOR CHANNEL ‘10-186-60-16’\G;四、参考链接Percona xtrabackup export功能介绍https://www.percona.com/doc/p…MySQL 可传输表空间功能说明https://dev.mysql.com/doc/ref…场景示例思路来源博客http://www.cnblogs.com/xuanzh…开源分布式中间件DBLE 社区官网:https://opensource.actionsky…. GitHub主页:https://github.com/actiontech… 技术交流群:669663113开源数据传输中间件DTLE 社区官网:https://opensource.actionsky…. GitHub主页:https://github.com/actiontech… 技术交流群:852990221 ...

March 9, 2019 · 3 min · jiezi

一句sum千行泪,笛卡尔积多坑人,mysql执行的先后顺序

我们每一个人都想要优化SQL语句,以便能够提升性能,但是,如果不了解其机制,可能就会事倍功半。我以一个简单的例子 ,来讲解SQL的部分机制。今天在公司工作时,面临这样一个需求:根据条件查询项目的预算金额。查询要求:项目的id项目人员的类型数据库表设计数据库有这样的两张表,一张是项目表project,下图是简单裁剪的图:一张是项目人员表,这张表记录的是某个项目涉及哪些类型的人员,人员类型(枚举)如下表所示:key值value值PERSON_TYPE_SALESMAN业务员PERSON_TYPE_SALESMAN_MANAGER业务部经理PERSON_TYPE_DESIGNER设计师PERSON_TYPE_DESIGNER_MANAGER设计部经理PERSON_TYPE_PROJECT_SUPERVISION工程监理PERSON_TYPE_ENGINEERING_MANAGER工程部经理因而,数据表项目人员(project_person)的的设计为:查询条件条件1:我们首先查询项目编号为167的项目SELECT SUM(budgetary_amount) FROM zq_project WHERE is_deleted = 0 AND id=167输出结果为 10条件2:关联项目人员表,查找编号为167的项目SELECT SUM(zp.budgetary_amount)FROM zq_project zpLEFT JOIN zq_project_person zpp ON(zpp.is_deleted = 0 AND zpp.project_id = zp.id)WHERE zp.is_deleted = 0 AND zp.id=167输出结果为 60为什么会这样呢为什么会出现上诉情况,当我们在做一对多的sum求和时,就出现了笛卡尔积的现象。我们查找出项目人员表中的项目编号为167的有多少条记录SELECT * from zq_project_person zpp WHERE zpp.is_deleted = 0 and zpp.project_id = 167输出结果如图所示:由上图可知,一共有六条记录,也就是说,项目表中编号为167的这条记录对应着项目人员表中的6条记录,sum之后要计算6次,才变成60,比如下面的代码:SELECT zp.id AS projectId, zp.budgetary_amount, zpp.id AS personIdFROM zq_project zpLEFT JOIN zq_project_person zpp ON(zpp.is_deleted = 0 AND zpp.project_id = zp.id)WHERE zp.is_deleted = 0 AND zp.id=167;输出结果如图所示:这就涉及到mysql的执行先后的顺序造成笛卡尔积的紊乱在讲解mysql执行的先后顺序之前,我们了解一下left join的 on 和 where的区别。left join 的on和where的区别on中的是副表的条件,where会将left join转化为inner join格式的数据,这是过滤数据用的。假设有这两张表,一张是商品表(goods表),一张是商品分类表(goods_category),商品表的外键是商品分类表的主键。我们来做left join的测试查找语句为:SELECT *FROM cce_goods cgLEFT JOIN cce_goods_category cgc ON(cgc.is_deleted = 0 AND cgc.id = cg.goods_category_id)WHERE cg.is_deleted = 0查找结果如图所示:你会发现,编号为1的商品分类的字段属性is_deleted的值明明是 1 ,而on之后的is_deleted 的值为 0 ,这应该是筛选不出来了,但还是能筛选出来呢?这里就涉及到on的条件了。首先,left join是并集,那么又是谁的并集?是主表和副表的并集。这时,主表和副表就有两种情况了,一种是主表的外键引用副表的主键,另一种就是主表的主键是副表的外键,那么,这就得分情况了。针对第一种情况我们以商品和商品表为例子,显然,商品表是主键,引用副表商品分类表的外键。主表和副表进行笛卡尔积(主表的外键和副表的主键进行匹配)得到一张临时表,临时表中存储主表和副表的字段属性。这时,以主表为主,副表为辅,即便副表没有数据,其也还会展示副表的字段。所以,编号为1的商品分类副表条件不满足,也就是没有满足的数据,因而,就把商品分类的字段属性为空。换个角度来看,如果我们把WHERE cg.is_deleted = 0这个条件去掉,你会发现会有很多数据出来。筛选条件where在left join之后,它的优先级低于left join。假如,我们把cgc.is_deleted = 0 改成为 cgc.is_deleted = 1,你会发现神奇的一幕,如图所示:你会发现,这是商品分类的字段属性是有值的,因为,副表的条件满足了,能拿到副表中的字段属性值。如果我们把left join 改成inner join ,而cgc.is_deleted = 0 不变,这又不一样了,如代码所示:SELECT *FROM cce_goods cgINNER JOIN cce_goods_category cgc ON(cgc.is_deleted = 0 AND cgc.id = cg.goods_category_id)WHERE cg.is_deleted = 0这样,上面的两条数据也没了,因为,inner join 是主表和副表的交集,主表和副表的条件是平行条件,具有同样的权重,也就是说同时满足主副表的条件,才能出现数据。再假如,我们cgc.is_deleted = 0放到外面,如代码所示:SELECT *FROM cce_goods cgINNER JOIN cce_goods_category cgc ON(cgc.id = cg.goods_category_id)WHERE cg.is_deleted = 0 AND cgc.is_deleted = 0这样,也就把left join 隐性成了 inner join了,主表和副表的条件也是平行条件,具有同样的权重。针对第二种情况1、 以项目和项目人员来看,项目是主表,项目人员是副表,目前有三条没被删除的记录,如图所示:2、 我们来执行以下的查询语句,如代码所示:SELECT zp.id AS projectId, zp.budgetary_amount, zpp.id AS personIdFROM zq_project zpLEFT JOIN zq_project_person zpp ON(zpp.is_deleted = 0 AND zpp.project_id = zp.id)WHERE zp.is_deleted = 0 AND zp.id=167;目前只有三条记录,其他的五条记录没有展示,这是为什么呢?这个只能意会,无法言传。就比如java中的对象,类Project对象是类ProjectPerson的成员属性,我们能在ProjectPerson对象李填充Project对象,但无法在Project对象中填充ProjectPerson的对象是一样的道理。上面也提到了mysql执行的先后顺序了,在下面,详细介绍mysql执行的先后顺序。mysql执行的先后顺序mysql在执行的过程会有一定的先后顺序的,它是按照什么顺序来的呢?任何一种开发语言,不管是面向结构的c语言,还是面向对象的JAVA语言,或者,结构化查询语言sql,其都有一个入口,C语言是main,java是public static void main(String[] args){…},SQL语言比如mysql,其入口是From,然后根据各个优先级。依次往下进行。fromjoinonwheregroup by(开始使用select中的别名,后面的语句中都可以使用)avg,sum…. 复合函数havingselectdistinctorder by以项目表为主表,以项目人员表和项目进程表为副表,查找出项目名和项目的预算金额SELECT DISTINCT zp.id AS projectId, SUM(zp.budgetary_amount) AS totalBugAmo, zp.name AS projectNameFROM zq_project zpLEFT JOIN zq_project_person zper ON ( zper.is_deleted = 0 AND zper.project_id = zp.id)LEFT JOIN zq_project_process zpro ON ( zpro.is_deleted = 0 AND zpro.project_id = zp.id)WHERE zp.is_deleted = 0GROUP BY zp.idHAVING totalBugAmo <= 12000ORDER BY totalBugAmo DESC执行结果如图所示:执行顺序如图所示第一步骤, 以from为入口进入查询语句中,确定主表是zq_project,然后从主表中取数据源LEFT JOIN zq_project_person zper ON (。。。)此时生成一张虚拟表vt1,根据虚拟表vt1中的on之后的筛选条件匹配数据,生成虚拟表vt2LEFT JOIN zq_project_process zpro ON(。。。)在vt2的基础上生成vt3和vt4,where筛选器,过滤掉已被逻辑删除的项目,生成虚拟表vt5,在group by这里出现了分水岭,之后就可以使用select中的别名了。这个为什么要分组呢?比如,项目人员表中相同项目编号的人员不止一个,这个要以项目id来对其进行分组统计,但此时的分组统计,是有问题的,因为,项目的预算金额是在项目表中的,而相同的项目编号的人员不止一个,那么,就出现了人员项目重复统计的现象。下面再细分析。生成虚拟表vt6所以,分组之后再sum等这些复合函数,于是,就出现了同一个项目的项目预算相加。这就出现了数据的累加错误。生成虚拟表vt7having是对虚拟表vt7进行数据过滤的,也就是说,它服务的对象是复合函数。生成虚拟表vt8select是将vt8的根据我们写出的条件筛选出来数据,比如我们只想要项目的id、项目的预算金额、项目的名字等,生成虚拟表vt9使用distinct 对虚拟表vt9进行去重,生成虚拟表vt1010.最后再排序,生成我们最后想要的表。为什么说sum会出现笛卡尔积的统计错误在讲解这个问题前,我们先看这张图:我们的查语句是:SELECT zp.id AS projectId, zp.budgetary_amount AS bugAmo, zp.name AS projectNameFROM zq_project zpLEFT JOIN zq_project_person zper ON ( zper.is_deleted = 0 AND zper.project_id = zp.id)LEFT JOIN zq_project_process zpro ON ( zpro.is_deleted = 0 AND zpro.project_id = zp.id)WHERE zp.is_deleted = 0 AND zp.id=167查询结果的截图为:你会发现,数据多了,为什么会多?以项目编号为167的为研究点,此时,当left join项目人员表时,根据排列组合而来,12=2,多生成一张有两条记录的虚拟表再left join 项目进程表时,根据排列组合而来,23=6,就会出现,这时就会出现6条数据的虚拟表,这时,我们再sum的话,就会计算6次,从而得出项目编号为167的预算金额是60,而不是10。上面就出现了分组之后的项目编号为167的预算金额为90的了,一对多的关系如果sum,是会出现笛卡尔积的错误的。因为,我们需要使用disdict去重,于是,我们重写代码后为:SELECT vt1.projectId, SUM(vt1.bugAmo), vt1.projectNameFROM ( SELECT DISTINCT zp.id AS projectId, zp.budgetary_amount AS bugAmo, zp.name AS projectName FROM zq_project zp LEFT JOIN zq_project_person zper ON ( zper.is_deleted = 0 AND zper.project_id = zp.id ) LEFT JOIN zq_project_process zpro ON ( zpro.is_deleted = 0 AND zpro.project_id = zp.id ) WHERE zp.is_deleted = 0 AND zp.id = 167 ) AS vt1此时,将其去重后的数据作为虚拟表,放置在from里面,我们拿到的数据就是正确的,如图所示:![去重后的数据(/img/bVbpyxH)如果,我们想要查找全部项目的统计金额,也可也可以重写代码,于是乎得到:SELECT SUM(vt1.bugAmo) AS toalBugAmoFROM ( SELECT DISTINCT zp.id AS projectId, zp.budgetary_amount AS bugAmo, zp.name AS projectName FROM zq_project zp LEFT JOIN zq_project_person zper ON ( zper.is_deleted = 0 AND zper.project_id = zp.id ) LEFT JOIN zq_project_process zpro ON ( zpro.is_deleted = 0 AND zpro.project_id = zp.id ) WHERE zp.is_deleted = 0 ) AS vt1GROUP BY vt1.projectIdHAVING toalBugAmo <= 12000ORDER BY toalBugAmo DESC这个执行结果为:结尾任何一门语言,只要掌握住了,它的机制是怎么运行的,你也就学会了如何优化,提升该语言的性能等。只要你真正掌握住了一门变成语言,你掌握其他的变成语言,学起来就非常地快。 ...

March 9, 2019 · 2 min · jiezi

数据库基础操作

数据库基础操作1.命令结束符号;gGG有一点特殊,它并不以表格的形式返回查询数据,而是以垂直的形式展现查询数据,这包含两个方面的意思:如果查询结果中包含多个行的数据,各个行的数据之间会用一堆一星号*隔开。每行中的每一个列的数据将按照列名: 列值的形式给出。2.使用c清除本次操作如果你想放弃本次编写的命令,可以使用c来清除,比如这样:mysql> SELECT NOW()\c展示数据库mysql> SHOW DATABASES;创建数据库mysql> CREATE DATABASE 数据库名;切换当前数据库mysql> USE 数据库名称;查看当前使用的数据库mysql> select database();删除数据库mysql> DROP DATABASE 数据库名;创建表mysql> CREATE TABLE first_table ( -> first_column INT COMMENT ‘列的注释’, -> second_column VARCHAR(100) -> ) COMMENT ‘表的注释’;展示当前数据库中的表mysql> SHOW TABLES;简单的表操作语句SELECT * FROM 表名;INSERT INTO 表名(列1, 列2, …) VALUES(列1的值,列2的值, …);INSERT INTO 表名(列1,列2, …) VAULES(列1的值,列2的值, …), (列1的值,列2的值, …), (列1的值,列2的值, …), …;UPDATE 表名 SET 列1 = 列1的值,… WHERE 条件…;删除表DROP TABLE IF EXISTS 表名;约束性条件(列的属性)mysql> CREATE TABLE first_table ( -> first_column INT, -> second_column VARCHAR(100) DEFAULT ‘abc’ -> );非空约束mysql> CREATE TABLE first_table ( -> first_column INT NOT NULL, -> second_column VARCHAR(100) DEFAULT ‘abc’ -> );主键&唯一性约束CREATE TABLE student_info ( number INT PRIMARY KEY, name VARCHAR(5), sex ENUM(‘男’, ‘女’), id_number CHAR(18) UNIQUE, department VARCHAR(30), major VARCHAR(30), enrollment_time DATE);CREATE TABLE student_info ( number INT, name VARCHAR(5), sex ENUM(‘男’, ‘女’), id_number CHAR(18), department VARCHAR(30), major VARCHAR(30), enrollment_time DATE PRIMARY KEY (number) UNIQUE KEY (id_number));PRIMARY KEY (列名1, 列名2, …) | UNIQUE [约束名称] (列名1, 列名2, …) | UNIQUE KEY [约束名称] (列名1, 列名2, …)主键和唯一性约束都能保证某个列或者列组合的唯一性,但是:一张表中只能定义一个主键,却可以定义多个唯一性约束!主键列不允许存放NULL值,而普通的唯一性约束列可以存放NULL值!索引InnoDB和MyISAM会自动为主键或者声明为UNIQUE的列去自动建立B+树索引在创建表的时候指定需要建立索引的单个列或者建立联合索引的多个列:CREATE TALBE 表名 ( 各种列的信息 ··· , [KEY|INDEX] 索引名 (需要被索引的单个列或多个列))其中的KEY和INDEX是同义词,任意选用一个就可以修改表结构的时候添加索引ALTER TABLE 表名 ADD [INDEX|KEY] 索引名 (需要被索引的单个列或多个列);在修改表结构的时候删除索引ALTER TABLE 表名 DROP [INDEX|KEY] 索引名;外键CONSTRAINT [外键名称] FOREIGN KEY(列1, 列2, …) REFERENCES 父表名(父列1, 父列2, …);自增mysql> CREATE TABLE first_table ( -> id int UNSIGNED AUTO_INCREMENT PRIMARY KEY, -> first_column INT, -> second_column VARCHAR(100) DEFAULT ‘abc’ -> );在使用递增属性的时候需要注意这几点:一个表中最多有一个递增列。一般只为整数类型的列定义递增属性,浮点数类型基本不用递增属性。具有AUTO_INCREMENT属性的列必须建立索引。主键和具有唯一性约束的列会自动建立索引,至于什么是索引,我们后边会详细唠叨。一般递增列都是作为主键的属性,来自动生成唯一标识一个记录的主键值。因为具有AUTO_INCREMENT属性的列是从1开始递增的,所以最好用UNSIGNED来修饰这个列,可以提升正数的表示范围。全值匹配假设我们有联合索引idx_name_birthday_phone_number,索引列的顺序为:name、birthday、phone_number 如果我们的搜索条件中的列和索引列一致的话,这种情况就称为全值匹配,比方说下边这个查找语句:SELECT * FROM person_info WHERE name = ‘Ashburn’ AND birthday = ‘1990-09-27’ AND phone_number = ‘15123983239’;如果搜索条件中的列的顺序和索引列的顺序不一致就不太好了,比方说先对birthday列的值进行匹配的话,由于B+树中的数据页和记录是先按name列的值进行排序的,不能直接使用二分法快速定位记录,所以只能扫描所有的记录页。匹配最左边的列搜索条件中的列可以不用包含全部联合索引中的列,只需要包含左边的就行,也可以包含多个,但是列的顺序必须和索引列的定义顺序一致=、in自动优化顺序等于(=)和in 可以乱序。比如,a = 1 AND b = 2 AND c = 3 建立(a,b,c)索引可以任意顺序,MySQL的查询优化器会帮你优化成索引可以识别的模式。匹配列前缀匹配列前缀如:name like ‘As%’,这样的搜索条件是会用到索引的,==如果要匹配的是后缀或者中间的某个子串,而类似:name like ‘%As%’,这是不会用到索引的==以%开头的like查询不能够利用B-tree索引,执行计划中key的值为NULL表示没有使用索引。匹配范围值所有记录都是按照索引列的值从小到大的顺序排好序的,所以查找索引列的值在某个范围内的记录是使用了索引的,==不过进行范围查找的时候需要注意,如果对多个列同时进行范围查找的话,只有对索引最左边的那个列进行范围查找的时候才能用到B+树索引==精确匹配某一列并范围匹配另一列对于同一个联合索引来说,虽然对多个列都进行范围查找时只能用到最左边那个索引列,但是如果左边的列是精确查找,则右边的列可以进行范围查找对于同一个联合索引来说,虽然对多个列都进行范围查找时只能用到最左边那个索引列,但是如果左边的列是精确查找,则右边的列可以进行范围查找用于排序ORDER BY的子句后边的列的顺序须按照索引列的顺序给出,这是能够使用索引的用于分组与用于排序的情况类似单个多列组合索引和多个单列索引的检索查询效果不同,因为在执行SQL时,MySQL只能使用一个索引,会从多个单列索引中选择一个限制最为严格的索引由于MySQL查询只使用一个索引,因此如果WHERE子句中已经使用了索引的话,那么ORDER BY中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下,不要使用排序操作;尽量不要包含多个列的排序,如果需要,最好给这些列也创建组合索引索引不会包含有NULL值的列只要列中包含有NULL值,都将不会被包含在索引中,组合索引中只要有一列有NULL值,那么这一列对于此条组合索引就是无效的。所以我们在数据库设计时,不要让索引字段的默认值为NULL数据类型出现隐式转换的时候也不会使用索引比如,FROM_UNIXTIME(create_time) = ‘2016-06-06’ 就不能使用索引,原因很简单,B+树中存储的都是数据表中的字段值,但是进行检索时,需要把所有元素都应用函数才能比较,显然这样的代价太大。所以语句要写成 : create_time = UNIX_TIMESTAMP(‘2016-06-06’)何时使用聚簇索引或非聚簇索引使用动作描述使用聚簇索引使用非聚簇索引列经常被分组排序√√返回某范围内的数据√×一个或极少不同的值××小数目不同的值√×大数目不同的值×√频繁更新的列×√外键列√√主键列√√频繁修改索引列×√ ...

March 9, 2019 · 2 min · jiezi

DBLE核心研发主讲:MySQL分布式中间件公开课开课啦

DBLE年龄:2岁半爱好:开源技能:数据水平拆分、兼容MySQL、高可用性、SQL支持、复杂查询优化、分布式事务支持特长:兼容性、复杂查询、分布式事务的深入改进优化为了使社区同学能够更清晰的了解开源分布式中间件DBLE的功能特性,DBLE核心研发团队历时3个月推出DBLE系列公开课。DBLE公开课简介课程目录DBLE系列课程分为4章,共12节3月15日起,每周五中午11:30前更新课程咨询:DBLE技术交流群(669663113)每天8:30-20:30即时疑问解答适用人群 正在使用DBLE或MyCat的用户当前中间件无法满足业务需求的用户运维大体量数据库,需要考虑分库分表的DBA为数据库架构选型发愁的研发人员讲师介绍▽Attention▽为了让大家更好的了解课程内容以及评估课程质量,本期预热已发布第一期课程,大家可以先感受一下一股强烈的分布式旋风扑面而来是多么清爽。第一章 DBLE的基本使用1.1 DBLE概述课程观看传送门:方式1:打开「爱可生开源社区」官网,点击技术博客,选择DBLE系列公开课即可观看方式2:点击“阅读原文”直达播放现场,一秒开始听课~如何获取课程DBLE系列公开课自3月15日起将按照每周一节课程发布在「爱可生开源社区官网」,点击官网(http://opensource.actionsky.com)博客专栏,即可查看“DBLE系列公开课”。课程免费,公开,我们的最终目标是能有更多的社区同学了解认可并使用DBLE。除此之外,我们为心急的同学提供了新的打开方式:敲黑板:VIP提前解锁新课程朋友圈分享本文并截图给小编(WeChat:mg116611)即可提前2周获取课程,DBLE系列公开课更新结束前分享均有效。

March 9, 2019 · 1 min · jiezi

深度分析 | MyCat与DBLE的对比性能调优

作者简介蓝寅,开源分布式中间件DBLE项目负责人;持续专注于数据库方面的技术, 始终在一线从事开发;对数据复制,读写分离,分库分表的有深入的理解与实践。问题起因:用benchmarksql_for_mysql对原生MyCat-1.6.1和DBLE-2.17.07版做性能测试对比,发现DBLE性能只到原生版MyCat的70%左右。问题分析过程:分析过程主要有以下内容:包括现象,收集数据,分析猜测原因,验证猜测的方式来进行。开源分布式中间件DBLE: 社区官网,获取DBLE快速入门指南及最新资讯: https://opensource.actionsky.com GitHub主页,查看官方文档: https://github.com/actiontech… 社区技术交流群,迅速获取官方支持: QQ群:6696631131.分析瓶颈1.1 先对两者进行一个CPU占用的堆栈分析通过对CPU火焰图的比较,发现DBLE用在纯排序上的CPU占用在15%以上,而MyCat在排序上没有看到明显的CPU占用。( 复盘时的思考:这里有明显的可疑之处,应当及早观察两者是否公平)1.2 首先猜测可能的原因a.由于MyCat对以下这条用例实现有bug:具体方式是直接原句下发SQL到节点,收到各个节点的结果后直接做加法;而DBLE则是改写为select distinct s_i_id 收集全部结果集,然后在中间件做去重和统计的工作。所以两者在这个case上的对比是不公平的。b.排序本身算法选择的问题1.3 对猜测原因的验证a.去除有bug的case,并未看到性能有提升,而且考虑这条用例在所有用例出现的概率只有4%,涉及到的数据也不多,所以应该不是性能问题的主因。b.去除有排序的case,看到两者性能接近,确定是排序的问题。2.猜测原因2.1 猜测一:源码实现原因2.1.1 猜测描述梳理DBLE源码排序逻辑的实现细节,是多路归并的排序,理论上是最优选择。实际上具体的实现上有可优化的空间,如下图, N个数的K路排序的初始化值理论最优复杂度是O(N),而这里变成了O(NlogK2) 。2.1.2 验证猜测为了快速将排序的因素排除,将cmp函数直接返回1再次做测试。结果提升了10%的性能,所以虽然cmp是有性能问题,但导致性能如此大还有其他原因。(复盘:新版本已优化此处10%的性能差异)2.2 猜测二:回到排序SQL查看B-SQL源码,有3个排序SQL,其中有2个排序SQL的排序列不在select 项中,这本来应该引发MyCat的bug的,但我们在返回集和抓包中都没有发现。再仔细阅读源码,原来B-SQL通过hard code的方式使得压力永远跑不到这两个代码路径上,这样我们又排除了2个干扰因素,问题集中到剩下的那个排序上了。将排序除去,64数据量,64并发,DBLE的性能是MyCat的96%。证明确实和排序有关。3.分析多并发压力排序性能的原因3.1 猜测排序算法在特殊场景下的适用性3.1.1 猜测描述由于MyCat排序采用的是timsort, 时间复杂度的可能最优是O(n)。而DBLE的多路归并排序在B-SQL这个场景下时间复杂度最差情况是O(n*(k-1)).猜测timSort排序在B-SQL多并发场景下可能会优于多路归并。3.1.2 验证猜测用B-SQL压测并统计函数调用次数。结论:在B-SQL场景下:两者平均每个排序调用的cmp函数的次数并没有发生明显的异化每个排序cmp次数虽然没有大的差异,但总的调用次数却相差很大,DBLE大约是MyCat的5倍4. 分析DBLE排序时cmp函数次数调用多的原因问题集中在了为什么DBLE会有更多次的比较函数调用。4.1 验证压力下发的SQL是否与cmp函数调用相符是否下发的SQL就不公平4.1.1收集数据用抓包的方式分别抓取B-SQL发给MyCat和DBLE的包,结果发现 DBLE的所有SQL中排序这条SQL的发生次数是MyCat的10倍左右。再次用yourkit查看调用次数和CPU分布验证,发现调用次数确实符合抓包的结论,CPU分布也是DBLE分了大量的时间用于排序,而MyCat对排序的分配几乎可以忽略。这也与最一开始的火焰图结论一样。用wireshark分析DBLE抓包结果,发现某些连接执行一段时间之后大量的重复出现排序+delete的query请求直到压力结束,举例如下图。4.1.2 分析原因分析B-SQL源码这里发现只有delete的数据为0才会引发死循环。4.1.3 验证测试在引发死循环的原因找到之前,先修改代码验证测试。无论result是否是0都设置newOrderRemoved=true使得B-SQL跳出死循环。验证测试,DBLE性能终于符合预期,变为MyCat的105%。至此,B-SQL有排序引发DBLE性能下降的原因找到了,某种场景下B-SQL对DBLE执行delete,影响行数为0,导致此时会有死循环,发送了大量排序请求,严重降低了DBLE性能,并且并发压力越大越容易出现,但也有一定几率不会触发。5.分析哪种场景下delete行数为05.1隔离级别测试因为对隔离级别并不熟悉,花了很长时间才想到原因,在MySQL上做了一个实验:也就是说,在并发情况下确实有可能有死循环出现。5.2 分析为什么只有在DBLE上有这个问题而在MyCat上没有这个问题原因是DBLE和MyCat的默认隔离级别都是REPEATED_READ,但MyCat的实现有bug,除非客户端显式使用set语句,MyCat后端连接使用的隔离级别都是下属结点上的默认隔离级别;而DBLE会在获取后端连接后同步上下文,使得session级别的隔离级别和DBLE配置相同。而后端的四个结点中除了1台是REPEATED_READ,其他三个结点都是READ_COMMITTED。这样同样的并发条件,DBLE100%会触发,而MyCat只有25%的概率触发。5.3 验证测试将DBLE上的配置添加<property name=“txIsolation”>2</property>(默认是3)与默认做对比:性能比是1:0.75.符合期望,性能原因全部找到。6. 吐槽最后吐槽一下B-SQL,找了官方的B-SQL4.1版和5.0版,4.1版并未对此情况做任何改进,仍有可能陷入死循环影响测试。而5.0的对应代码处有这么一段注释,不知道PGSQL是否这里真的会触发异常,但MySQL并不会触发异常,仍有可能陷入死循环。7.性能原因回顾1.cmp函数时候初始化值的问题,影响部分性能,非主要性能瓶颈,新版本已改进。2.同时触发了MyCat和B-SQL的两个bug,导致测试的性能数据负负得正;Mycat bug:配置的隔离级别不生效问题B-SQL bug:RR隔离级别下,delete死循环问题需要将MySQL结点都改为READ_COMMITED,再将配置改为<property name=“txIsolation”>2</property>,避开上述的bug。8.收获1.测试环境的搭建无论是配置参数还是各个节点的状态都要同步,保证公平。2.性能分析工具的使用。3.性能测试可能一次的结果具有偶然性,需要多次验证。4.当有矛盾的结论时候,可能就快接近问题的真相了,需要持续关注。开源分布式中间件DBLE社区官网: https://opensource.actionsky….GitHub主页: https://github.com/actiontech…技术交流群:669663113开源数据传输中间件DTLE社区官网: https://opensource.actionsky….GitHub主页: https://github.com/actiontech…技术交流群:852990221

March 9, 2019 · 1 min · jiezi

CentOS 7 yum 安装 MySQL

导语已经安装完成 Nginx 和 PHP,接下来就是安装 MySQL。这次不用编译安装,使用 yum 安装试试。添加 yum 源先下载源安装包, 输入 wget http://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm 接下来安装,输入 yum localinstall mysql57-community-release-el7-11.noarch.rpm 。执行之后来看下是否安装成功 yum 安装并启动服务好了,接下来就是 yum -y install mysql-community-server输入 systemctl start mysqld 来启动服务最后输入 systemctl enable mysqld 添加开机自动启动。至此,MySQL 已经安装成功。修改默认密码使用 grep ’temporary password’ /var/log/mysqld.log 查看默认密码。然后 mysql -u root -p 输入查找到的默认密码登录 MySQL输入 ALTER USER ‘root’@’localhost’ IDENTIFIED BY ‘youPassword’; 修改密码。注意,MySQL 5.7 要求密码必须包含大小写字母,数字和特殊字符。开启远程连接在 MySQL 中输入 GRANT ALL PRIVILEGES ON . TO ‘username’@’%’ IDENTIFIED BY ‘password’ WITH GRANT OPTION; 。添加完成后在开启端口 参考资料:CentOS 7 下 Yum 安装 MySQL 5.7、CentOS7 yum安装 MySql、Centos 7 安装 MySQL。 ...

March 9, 2019 · 1 min · jiezi

一次数据库表命名导致的bug

因为order是数据库的一个内置关键字所以我们在写查询语句的时候要注意加上表名称(反引号 ``:就是键盘数字1左边的那个键)例如: select count(1) from order 执行不通过,一直提示sql有问题select count(1) from order 正确写法 或者 select count(1) from 数据库.order浪费了一晚上。不应该

March 9, 2019 · 1 min · jiezi

Python猫荐书系列:文也深度学习,理也深度学习

最近出了两件大新闻,相信大家可能有所耳闻。我来当个播报员,给大家转述一下:1、中国队在第 11 界罗马尼亚数学大师赛(RMM)中无缘金牌。该项赛事是三大国际赛事之一,被誉为中学奥数的最高难度。其中一道题,令中国队全军覆没。 2、一个出自清华姚班,毕业于斯坦福的女博士,她的毕业论文成了学术圈的“爆款”。这篇论文研究的主题是——如何让机器学会理解人类语言? 每天的新闻多如牛毛,唯独这两件引起了我的注意。它们跟本期的荐书栏目也是强关联,下面就给大家说道说道。上图标出了中国队成绩最好的三名队员。前两人在其它题目全部满分的情况下,第三题竟然是 0 分!什么样的题目能让我们的顶尖高手都束手无策呢?算了,题目我就不放出来了(我看不懂,不自找其辱。总之你们知道它很难就得了)。但是,那道题是图论的问题,关于图论,我们可以说说它跟计算机科学的关系。图论是数学的一个分支,它研究的最著名问题有柯尼斯堡七桥问题 与 四色地图问题 ,相信大家都曾见过,而在计算机领域,它也带来了诸多的研究成果:最小生成树问题、旅行商问题(NP困难)、拓扑排序算法、广度优先算法、深度优先算法,等等。奥数就这样跟程序员的职业联系了起来。然而,更值得一提的是第二个新闻:它研究的是人工智能领域最前沿的话题,想构建一个在深度神经网络之上的阅读理解模型 。简单地说是,教会计算机来阅读文本的能力。这项研究与大家熟知的数字个人助理不同(如 Alexa、Siri、Google Assistant、Cortana),它的难度超越了简单会话与信息匹配的一般性问题,想克服的是文本级阅读理解,与开放性问答等高度抽象层面的难关。它的研究成果将给数字个人助理带来质的提升,而对于人类语言文本的阅读理解能力,也必然带来更广阔的应用前途。这一切,都归功于深度学习。深度学习是我很感兴趣的领域。我们有幸生在这个时代,见证了 AlphaGo 打败人类的顶尖棋手,正在见证各种 AI 技术的出现,无人驾驶、医疗诊断、AI 翻译、金融科技、深度法律……我们的未来将被人工智能深远地影响。本期Python 猫荐书栏目(系列之六),就以此为话题,推荐给大家两本书:它们都叫《深度学习》,但是内容很不一样。第一本从应用数学,到深度学习的各种模型、算法与科研问题,走的是极其专业的路线。而另一本讲的是深度学习的 60 年发展史,以及对智能时代的一些前瞻性预测,走的是通俗科普的路线。如果要强行划分的话,前一本属理科,主要给相关领域的学生与程序员阅读,而后一本则属文科,面向所有对人工智能的历史与未来感兴趣的人群。事实上,第一本书被很多人誉为深度学习的圣经,知名度极高,有一个昵称叫作“花书”。简单梳理一下它的内容:第一部分是深度学习的基础,包含线性代数与概率论等数学知识,以及梯度优化、拟合、偏差、最大似然估计与监督学习等基础概念;第二部分是深度学习的关键部分,涉及深度前馈网络、正则化、模型优化的方法、卷积网络、序列建模、与实践应用内容;第三部分是深度学习研究,例如线性因子模型、自编码器、表示学习、结构化概率模型、蒙特卡罗方法、直面配分函数、近似推断、深度生成模型,等等。要知道,本专栏是兴趣大于能力,没办法深入剖析这本书的精华,再讲出些令行家也折服的话,但是,这本书值得推荐之处也很显著:它是一种正统的、学院派的、知识全面的、一丝不苟的、偏重理论的书籍,没错,正像是大学里相关专业的指定参考书。这就意味着,如果想进入深度学习领域,这本书将是你最好的老师。(而且不用考试,手动滑稽)至于第二本《深度学习》,书的副标题是“智能时代的核心驱动力量 ”。其实这只是翻译的结果,原书的英文名是《The Deep Learning Revolution》。20 世纪 70 年代到 90 年代是深度学习(神经网络)的寒冬,本书作者既是深度学习的先驱与奠基者,也是打破此寒冬,令深度学习东山再起的大功臣。他名叫特伦斯·谢诺夫斯基 (Terrence Sejnowski)。特伦斯是谁呢?世界十大AI科学家之一,美国四大国家学院(国家科学院、国家医学院、国家工程院、国家艺术与科学学院)在世仅3位的“四院院士 ”之一,全球AI专业会议NIPS基金会主席。深度学习的核心技术玻尔兹曼机 ,正是由特伦斯与杰弗里·辛顿共同建立的。那书的内容是什么呢?这本书在前言中称:这是一本关于深度学习的过去、现在和未来的指南。 在如此宏观的视角下,它主要讲到了一些重要概念的发展、科研群体研究的内容和传承,以及深度学习对当今社会的影响。也就是说,它不再关心微观的原理、底层的细节、繁复的逻辑。与第一本书的调性截然不同。这本书以第一人称视角讲述,带入了很多个人的动态:读书经历、研究课题、演讲与会议、人际关系、趣闻、甚至还有八卦(例如差点跟女朋友分手的一次会议。PS:他们在一起了,现在也没分开)。因此,第二本书的阅读门槛不高,还饶有趣味。往期荐书回顾: 第一期:《编写高质量代码改善 Python 程序的 91 个建议》第二期:《Python最佳实践指南》第三期:《黑客与画家》第四期:《Python源码剖析》第五期:《Python高性能编程》————-荐书完————-世事无巧不成书。似乎每期荐书都会发生一些巧合,因此我得额外交代几句:1、我早知第一本书的大名,也翻看过数学部分的一些内容,但是兴趣就止步于此。有打算纳其入荐书系列,但没想到会这么快。至于第二本书,恰好是在上期荐书发布后,中信出版社的营销人员找我约稿,当时这本书还没上市。我并非深度学习领域的专家,只能写写旁观者的言语,既然无法深入,干脆就将它们凑在一起了。2、荐书栏目不是专业书评,无法讲透全书的技术精粹,但我仍大着胆写了(之所以拖了这么久才动笔,就是因为过于担心)。一方面逼使自己阅读和查资料,快速归纳与写作;另一方面也确实是希望通过自己的文笔,能够使一部分读者获知到原先不知的信息,产生阅读的兴趣。3、就在前几天(2 月 28 日),一位知名的 Python 博主@Vamei 因抑郁症自杀了。我在看资料的时候,发现他也写了第二本《深度学习》的书评。他发布的时间是 1 月 31 日,而在这个时间,新书还未上市。这意味着他可能跟我一样,都收到了出版社的预读本,我们就是那么巧合地在同样的时间里阅读着同一本还未上市的新书。我想,这本书大概就是在给我传递一个讯息。我有很多次想过放弃邀约(无稿费,赠书一本)、放弃写这一篇荐书,直到前几天才真正开始动笔。这个神秘的讯息就这么巧地传过来了。荐书,见人。写完这篇荐书,我要写写他了。4、Vamei 的豆瓣主页写道:Vamei 是赤道附近一个台风的名字。按照气象规律,台风不常出现在赤道。所以,Vamei是一个离群的风,无所顾忌地生长,不着边际地游荡。公众号【Python猫】, 专注Python技术、数据科学和深度学习,力图创造一个有趣又有用的学习分享平台。本号连载优质的系列文章,有喵星哲学猫系列、Python进阶系列、好书推荐系列、优质英文推荐与翻译等等,欢迎关注哦。PS:后台回复“爱学习”,免费获得一份学习大礼包。

March 8, 2019 · 1 min · jiezi

【MySQL】主从异步复制配置

简介:Mysql的 主从同步 是一个异步的复制过程,从一个 Master复制到另一个 Slave上。在 Master 与 Slave 之间的实现整个复制过程主要由三个线程来完成,其中两个线程(Sql线程和IO线程)在 Slave 端,另外一个线程(IO线程)在 Master 端。 要实现 MySQL 的 主从同步 ,首先必须打开 Master 端的BinaryLog(mysql-bin)功能,否则无法实现。因为整个复制过程实际上就是Slave从Master端获取该日志然后再在自己身上完全顺序的执行日志中所记录的各种操作。打开 MySQL 的 Binary Log 可以通过在启动 MySQL Server 的过程中使用 “—log-bin” 参数选项,或者在 my.cnf 配置文件中的 mysqld 参数组([mysqld]标识后的参数部分)增加 “log-bin” 参数项。原理:(1)master将改变记录到二进制日志(binary log)中(这些记录叫做二进制日志事件,binary log events);(2) slave将master的binary log events拷贝到它的中继日志(relay log);(3) slave重做中继日志中的事件,将改变反映它自己的数据。下图描述了复制的过程:具体配置过程:1.主库配置:用vi /etc/my.cnf打开文件,对文件进行修改,在[mysqld]下面进行添加修改:server-id = 1 # 这是数据库ID,此ID是唯一的,主库默认为1,其他从库以此ID进行递增,ID值不能重复,否则会同步出错;log-bin = mysql-bin # 二进制日志文件,此项为必填项,否则不能同步数据;binlog_format=row # bilog设置为row模式 防止复制出错2.从库配置:用vi /etc/my.cnf打开文件,对文件进行修改,在[mysqld]下面进行添加修改:server_id = 2log-bin=mysql-binrelay_log=mysql-relay-bin# 不指定以下参数则全库同步#replicate-do-table=test.test_tb 同步某张表#binlog-do-db = testcreate 需要同步的数据库,如果需要同步多个数据库;则继续添加此项。#binlog-ignore-db = mysql 不需要同步的数据库;3.配置完需要重启主从库4.主库创建同步账号:create user ‘replica’@’%’ identified by ‘123456’;grant replication slave,replication client,reload,super on . to ‘replica’@’%’ identified by ‘123456’;5.进入从库开启同步同步开启前需要保持主从要同步的数据库数据一致。# 从库启动slave:# (MASTER_LOG_FILE与MASTER_LOG_POS在主库运行SHOW MASTER STATUS;取得)CHANGE MASTER TO MASTER_HOST=‘192.168.1.60’, MASTER_USER=‘replica’, MASTER_PASSWORD=‘123456’, MASTER_LOG_FILE=‘mysql-bin.000001’, MASTER_LOG_POS=875; start slave;show slave status \G; –查看slave状态 确保Slave_IO_Running: Yes Slave_SQL_Running: Yes ...

March 8, 2019 · 1 min · jiezi

【MySQL】查询语法简介

本篇文章主要简介下MySQL中where,group by ,order by ,limit,join,union ,union all,子表等查询语法。测试数据准备create table emp ( empno numeric(4) not null, ename varchar(10), job varchar(9), mgr numeric(4), hiredate datetime, sal numeric(7, 2), comm numeric(7, 2), deptno numeric(2));create table dept ( deptno numeric(2), dname varchar(14), loc varchar(13));create table salgrade ( grade numeric, losal numeric, hisal numeric);insert into dept values (10, ‘ACCOUNTING’, ‘NEW YORK’);insert into dept values (20, ‘RESEARCH’, ‘DALLAS’);insert into dept values (30, ‘SALES’, ‘CHICAGO’);insert into dept values (40, ‘OPERATIONS’, ‘BOSTON’);insert into salgrade values (1, 700, 1200);insert into salgrade values (2, 1201, 1400);insert into salgrade values (3, 1401, 2000);insert into salgrade values (4, 2001, 3000);insert into salgrade values (5, 3001, 9999);insert into emp values (7369, ‘SMITH’, ‘CLERK’, 7902, ‘1980-12-17’, 800, null, 20);insert into emp values (7499, ‘ALLEN’, ‘SALESMAN’, 7698, ‘1981-02-20’, 1600, 300, 30);insert into emp values (7521, ‘WARD’, ‘SALESMAN’, 7698, ‘1981-02-22’, 1250, 500, 30);insert into emp values (7566, ‘JONES’, ‘MANAGER’, 7839, ‘1981-04-02’, 2975, null, 20);insert into emp values (7654, ‘MARTIN’, ‘SALESMAN’, 7698, ‘1981-09-28’, 1250, 1400, 30);insert into emp values (7698, ‘BLAKE’, ‘MANAGER’, 7839, ‘1981-05-01’, 2850, null, 30);insert into emp values (7782, ‘CLARK’, ‘MANAGER’, 7839, ‘1981-06-09’, 2450, null, 10);insert into emp values (7788, ‘SCOTT’, ‘ANALYST’, 7566, ‘1982-12-09’, 3000, null, 20);insert into emp values (7839, ‘KING’, ‘PRESIDENT’, null, ‘1981-11-17’, 5000, null, 10);insert into emp values (7844, ‘TURNER’, ‘SALESMAN’, 7698, ‘1981-09-08’, 1500, 0, 30);insert into emp values (7876, ‘ADAMS’, ‘CLERK’, 7788, ‘1983-01-12’, 1100, null, 20);insert into emp values (7900, ‘JAMES’, ‘CLERK’, 7698, ‘1981-12-03’, 950, null, 30);insert into emp values (7902, ‘FORD’, ‘ANALYST’, 7566, ‘1981-12-03’, 3000, null, 20);insert into emp values (7934, ‘MILLER’, ‘CLERK’, 7782, ‘1982-01-23’, 1300, null, 10);1.模糊查询mysql> select * from emp where ename like ‘%S%’; +——-+——-+———+——+———————+———+——+——–+| empno | ename | job | mgr | hiredate | sal | comm | deptno |+——-+——-+———+——+———————+———+——+——–+| 7369 | SMITH | CLERK | 7902 | 1980-12-17 00:00:00 | 800.00 | NULL | 20 || 7566 | JONES | MANAGER | 7839 | 1981-04-02 00:00:00 | 2975.00 | NULL | 20 || 7788 | SCOTT | ANALYST | 7566 | 1982-12-09 00:00:00 | 3000.00 | NULL | 20 || 7876 | ADAMS | CLERK | 7788 | 1983-01-12 00:00:00 | 1100.00 | NULL | 20 || 7900 | JAMES | CLERK | 7698 | 1981-12-03 00:00:00 | 950.00 | NULL | 30 |+——-+——-+———+——+———————+———+——+——–+5 rows in set (0.00 sec)mysql> select * from emp where ename like ‘S%’; +——-+——-+———+——+———————+———+——+——–+| empno | ename | job | mgr | hiredate | sal | comm | deptno |+——-+——-+———+——+———————+———+——+——–+| 7369 | SMITH | CLERK | 7902 | 1980-12-17 00:00:00 | 800.00 | NULL | 20 || 7788 | SCOTT | ANALYST | 7566 | 1982-12-09 00:00:00 | 3000.00 | NULL | 20 |+——-+——-+———+——+———————+———+——+——–+2 rows in set (0.01 sec)mysql> select * from emp where ename like ‘%S’;+——-+——-+———+——+———————+———+——+——–+| empno | ename | job | mgr | hiredate | sal | comm | deptno |+——-+——-+———+——+———————+———+——+——–+| 7566 | JONES | MANAGER | 7839 | 1981-04-02 00:00:00 | 2975.00 | NULL | 20 || 7876 | ADAMS | CLERK | 7788 | 1983-01-12 00:00:00 | 1100.00 | NULL | 20 || 7900 | JAMES | CLERK | 7698 | 1981-12-03 00:00:00 | 950.00 | NULL | 30 |+——-+——-+———+——+———————+———+——+——–+3 rows in set (0.00 sec)mysql> select * from emp where ename like ‘_O%’;+——-+——-+———+——+———————+———+——+——–+| empno | ename | job | mgr | hiredate | sal | comm | deptno |+——-+——-+———+——+———————+———+——+——–+| 7566 | JONES | MANAGER | 7839 | 1981-04-02 00:00:00 | 2975.00 | NULL | 20 || 7902 | FORD | ANALYST | 7566 | 1981-12-03 00:00:00 | 3000.00 | NULL | 20 |+——-+——-+———+——+———————+———+——+——–+2 rows in set (0.00 sec)总结:%表示任意0个或多个字符,可匹配任意类型和长度的字符;_表示任意单个字符,匹配单个任意字符。2.排序mysql> select * from emp order by sal;+——-+——–+———–+——+———————+———+———+——–+| empno | ename | job | mgr | hiredate | sal | comm | deptno |+——-+——–+———–+——+———————+———+———+——–+| 7369 | SMITH | CLERK | 7902 | 1980-12-17 00:00:00 | 800.00 | NULL | 20 || 7900 | JAMES | CLERK | 7698 | 1981-12-03 00:00:00 | 950.00 | NULL | 30 || 7876 | ADAMS | CLERK | 7788 | 1983-01-12 00:00:00 | 1100.00 | NULL | 20 || 7521 | WARD | SALESMAN | 7698 | 1981-02-22 00:00:00 | 1250.00 | 500.00 | 30 || 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 00:00:00 | 1250.00 | 1400.00 | 30 || 7934 | MILLER | CLERK | 7782 | 1982-01-23 00:00:00 | 1300.00 | NULL | 10 || 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 00:00:00 | 1500.00 | 0.00 | 30 || 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 00:00:00 | 1600.00 | 300.00 | 30 || 7782 | CLARK | MANAGER | 7839 | 1981-06-09 00:00:00 | 2450.00 | NULL | 10 || 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 00:00:00 | 2850.00 | NULL | 30 || 7566 | JONES | MANAGER | 7839 | 1981-04-02 00:00:00 | 2975.00 | NULL | 20 || 7788 | SCOTT | ANALYST | 7566 | 1982-12-09 00:00:00 | 3000.00 | NULL | 20 || 7902 | FORD | ANALYST | 7566 | 1981-12-03 00:00:00 | 3000.00 | NULL | 20 || 7839 | KING | PRESIDENT | NULL | 1981-11-17 00:00:00 | 5000.00 | NULL | 10 |+——-+——–+———–+——+———————+———+———+——–+14 rows in set (0.00 sec)mysql> select * from emp order by sal asc;+——-+——–+———–+——+———————+———+———+——–+| empno | ename | job | mgr | hiredate | sal | comm | deptno |+——-+——–+———–+——+———————+———+———+——–+| 7369 | SMITH | CLERK | 7902 | 1980-12-17 00:00:00 | 800.00 | NULL | 20 || 7900 | JAMES | CLERK | 7698 | 1981-12-03 00:00:00 | 950.00 | NULL | 30 || 7876 | ADAMS | CLERK | 7788 | 1983-01-12 00:00:00 | 1100.00 | NULL | 20 || 7521 | WARD | SALESMAN | 7698 | 1981-02-22 00:00:00 | 1250.00 | 500.00 | 30 || 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 00:00:00 | 1250.00 | 1400.00 | 30 || 7934 | MILLER | CLERK | 7782 | 1982-01-23 00:00:00 | 1300.00 | NULL | 10 || 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 00:00:00 | 1500.00 | 0.00 | 30 || 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 00:00:00 | 1600.00 | 300.00 | 30 || 7782 | CLARK | MANAGER | 7839 | 1981-06-09 00:00:00 | 2450.00 | NULL | 10 || 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 00:00:00 | 2850.00 | NULL | 30 || 7566 | JONES | MANAGER | 7839 | 1981-04-02 00:00:00 | 2975.00 | NULL | 20 || 7788 | SCOTT | ANALYST | 7566 | 1982-12-09 00:00:00 | 3000.00 | NULL | 20 || 7902 | FORD | ANALYST | 7566 | 1981-12-03 00:00:00 | 3000.00 | NULL | 20 || 7839 | KING | PRESIDENT | NULL | 1981-11-17 00:00:00 | 5000.00 | NULL | 10 |+——-+——–+———–+——+———————+———+———+——–+14 rows in set (0.00 sec)mysql> select * from emp order by sal desc;+——-+——–+———–+——+———————+———+———+——–+| empno | ename | job | mgr | hiredate | sal | comm | deptno |+——-+——–+———–+——+———————+———+———+——–+| 7839 | KING | PRESIDENT | NULL | 1981-11-17 00:00:00 | 5000.00 | NULL | 10 || 7788 | SCOTT | ANALYST | 7566 | 1982-12-09 00:00:00 | 3000.00 | NULL | 20 || 7902 | FORD | ANALYST | 7566 | 1981-12-03 00:00:00 | 3000.00 | NULL | 20 || 7566 | JONES | MANAGER | 7839 | 1981-04-02 00:00:00 | 2975.00 | NULL | 20 || 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 00:00:00 | 2850.00 | NULL | 30 || 7782 | CLARK | MANAGER | 7839 | 1981-06-09 00:00:00 | 2450.00 | NULL | 10 || 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 00:00:00 | 1600.00 | 300.00 | 30 || 7844 | TURNER | SALESMAN | 7698 | 1981-09-08 00:00:00 | 1500.00 | 0.00 | 30 || 7934 | MILLER | CLERK | 7782 | 1982-01-23 00:00:00 | 1300.00 | NULL | 10 || 7521 | WARD | SALESMAN | 7698 | 1981-02-22 00:00:00 | 1250.00 | 500.00 | 30 || 7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 00:00:00 | 1250.00 | 1400.00 | 30 || 7876 | ADAMS | CLERK | 7788 | 1983-01-12 00:00:00 | 1100.00 | NULL | 20 || 7900 | JAMES | CLERK | 7698 | 1981-12-03 00:00:00 | 950.00 | NULL | 30 || 7369 | SMITH | CLERK | 7902 | 1980-12-17 00:00:00 | 800.00 | NULL | 20 |+——-+——–+———–+——+———————+———+———+——–+14 rows in set (0.00 sec)总结:order by排序默认按asc升序来排列也可指定desc降序排列3.限制多少行mysql> select * from emp limit 3;+——-+——-+———-+——+———————+———+——–+——–+| empno | ename | job | mgr | hiredate | sal | comm | deptno |+——-+——-+———-+——+———————+———+——–+——–+| 7369 | SMITH | CLERK | 7902 | 1980-12-17 00:00:00 | 800.00 | NULL | 20 || 7499 | ALLEN | SALESMAN | 7698 | 1981-02-20 00:00:00 | 1600.00 | 300.00 | 30 || 7521 | WARD | SALESMAN | 7698 | 1981-02-22 00:00:00 | 1250.00 | 500.00 | 30 |+——-+——-+———-+——+———————+———+——–+——–+3 rows in set (0.00 sec)mysql> select * from emp order by sal desc limit 3;+——-+——-+———–+——+———————+———+——+——–+| empno | ename | job | mgr | hiredate | sal | comm | deptno |+——-+——-+———–+——+———————+———+——+——–+| 7839 | KING | PRESIDENT | NULL | 1981-11-17 00:00:00 | 5000.00 | NULL | 10 || 7902 | FORD | ANALYST | 7566 | 1981-12-03 00:00:00 | 3000.00 | NULL | 20 || 7788 | SCOTT | ANALYST | 7566 | 1982-12-09 00:00:00 | 3000.00 | NULL | 20 |+——-+——-+———–+——+———————+———+——+——–+3 rows in set (0.00 sec)总结:limit限定显示前多少行,可与order by联合使用4.聚合函数count() sum()函数用法:#1.各个部门的薪水和mysql> select deptno,sum(sal) from emp group by deptno;+——–+———-+| deptno | sum(sal) |+——–+———-+| 10 | 8750.00 || 20 | 10875.00 || 30 | 9400.00 |+——–+———-+3 rows in set (0.01 sec)#2.group by字段必须出现在select字段后面 各个部门的各个岗位的薪水和mysql> select deptno,job, sum(sal) from emp group by deptno ,job;+——–+———–+———-+| deptno | job | sum(sal) |+——–+———–+———-+| 10 | CLERK | 1300.00 || 10 | MANAGER | 2450.00 || 10 | PRESIDENT | 5000.00 || 20 | ANALYST | 6000.00 || 20 | CLERK | 1900.00 || 20 | MANAGER | 2975.00 || 30 | CLERK | 950.00 || 30 | MANAGER | 2850.00 || 30 | SALESMAN | 5600.00 |+——–+———–+———-+9 rows in set (0.01 sec)#3.having 薪水和>5000的各个部门的各个岗位mysql> select deptno,job, sum(sal) -> from emp -> group by deptno ,job -> having sum(sal)>5000; +——–+———-+———-+| deptno | job | sum(sal) |+——–+———-+———-+| 20 | ANALYST | 6000.00 || 30 | SALESMAN | 5600.00 |+——–+———-+———-+2 rows in set (0.00 sec)#4.常用组合where order limit select deptno,job, sum(sal) as sum_salfrom emp where job=‘SALESMAN’group by deptno ,jobhaving sum(sal)>5000 order by sum(sal) desc limit 1;下面介绍下join及union的用法数据准备:create table testa(aid int,aname varchar(40));create table testb(bid int,bname varchar(40),age int);insert into testa values(1,‘xiaoming’);insert into testa values(2,‘LY’);insert into testa values(3,‘KUN’);insert into testa values(4,‘ZIDONG’);insert into testa values(5,‘HB’);insert into testb values(1,‘xiaoming’,10);insert into testb values(2,‘LY’,100);insert into testb values(3,‘KUN’,200);insert into testb values(4,‘ZIDONG’,110);insert into testb values(6,’niu’,120);insert into testb values(7,‘meng’,130);insert into testb values(8,‘mi’,170);5.left joinmysql> select -> a.aid,a.aname, -> b.bid,b.bname,b.age -> from testa as a -> left join testb as b on a.aid=b.bid; +——+———-+——+———-+——+| aid | aname | bid | bname | age |+——+———-+——+———-+——+| 1 | xiaoming | 1 | xiaoming | 10 || 2 | LY | 2 | LY | 100 || 3 | KUN | 3 | KUN | 200 || 4 | ZIDONG | 4 | ZIDONG | 110 || 5 | HB | NULL | NULL | NULL |+——+———-+——+———-+——+5 rows in set (0.00 sec)总结:a left join b a表全,用b表去匹配a表LEFT JOIN 关键字会从左表 (a) 那里返回所有的行,即使在右表 (b) 中没有匹配的行,匹配不到的列用NULL代替6.right joinmysql> select -> a.aid,a.aname, -> b.bid,b.bname,b.age -> from testa as a -> right join testb as b on a.aid=b.bid;+——+———-+——+———-+——+| aid | aname | bid | bname | age |+——+———-+——+———-+——+| 1 | xiaoming | 1 | xiaoming | 10 || 2 | LY | 2 | LY | 100 || 3 | KUN | 3 | KUN | 200 || 4 | ZIDONG | 4 | ZIDONG | 110 || NULL | NULL | 6 | niu | 120 || NULL | NULL | 7 | meng | 130 || NULL | NULL | 8 | mi | 170 |+——+———-+——+———-+——+7 rows in set (0.00 sec)总结:a right join b b表全,用a表去匹配b表RIGHT JOIN 关键字会右表 (b) 那里返回所有的行,即使在左表 (a) 中没有匹配的行,匹配不到的列用NULL代替7.inner joinmysql> select -> a.aid,a.aname, -> b.bid,b.bname,b.age -> from testa as a -> inner join testb as b on a.aid=b.bid; +——+———-+——+———-+——+| aid | aname | bid | bname | age |+——+———-+——+———-+——+| 1 | xiaoming | 1 | xiaoming | 10 || 2 | LY | 2 | LY | 100 || 3 | KUN | 3 | KUN | 200 || 4 | ZIDONG | 4 | ZIDONG | 110 |+——+———-+——+———-+——+4 rows in set (0.00 sec)mysql> select -> a.aid,a.aname, -> b.bid,b.bname,b.age -> from testa as a -> join testb as b on a.aid=b.bid; +——+———-+——+———-+——+| aid | aname | bid | bname | age |+——+———-+——+———-+——+| 1 | xiaoming | 1 | xiaoming | 10 || 2 | LY | 2 | LY | 100 || 3 | KUN | 3 | KUN | 200 || 4 | ZIDONG | 4 | ZIDONG | 110 |+——+———-+——+———-+——+4 rows in set (0.00 sec)总结:inner join 与join 效果一样在表中存在至少一个匹配时,INNER JOIN 关键字返回行8.union与union allmysql> select aid,aname from testa -> union -> select bid,bname from testb;+——+———-+| aid | aname |+——+———-+| 1 | xiaoming || 2 | LY || 3 | KUN || 4 | ZIDONG || 5 | HB || 6 | niu || 7 | meng || 8 | mi |+——+———-+8 rows in set (0.01 sec)mysql> select aid,aname from testa -> union all -> select bid,bname from testb;+——+———-+| aid | aname |+——+———-+| 1 | xiaoming || 2 | LY || 3 | KUN || 4 | ZIDONG || 5 | HB || 1 | xiaoming || 2 | LY || 3 | KUN || 4 | ZIDONG || 6 | niu || 7 | meng || 8 | mi |+——+———-+12 rows in set (0.00 sec)总结:union 会去重 union all不去重 ...

March 8, 2019 · 11 min · jiezi

【MySQL】lower_case_table_names参数详解

简介: lower_case_table_names 是mysql设置大小写是否敏感的一个参数。1.参数说明:lower_case_table_names=0 表名存储为给定的大小和比较是区分大小写的 lower_case_table_names = 1 表名存储在磁盘是小写的,但是比较的时候是不区分大小写 lower_case_table_names=2 表名存储为给定的大小写但是比较的时候是小写的unix,linux下lower_case_table_names默认值为 0 .Windows下默认值是 1 .Mac OS X下默认值是 22.查看方法:# 进入mysql命令行 执行以下任一语句查看:show variables like ’lower_case_table_names’;select @@lower_case_table_names;3.更改方法:更改数据库参数文件my.cnf在mysqld下 添加或修改 lower_case_table_names = 1之后重启数据库4.现实情况修改 注意事项:因目前MySQL安装在Linux系统上较多 初始化时采取了默认的lower_case_table_names值 即区分大小写,后续可能会造成同一实例大小写库表都存在的情况,调用时还要注意大小写。这时 更改步骤如下:1.核实实例中是否存在大写的库及表2.将大写的库名及表名改为小写更改库名可参考:https://www.cnblogs.com/gomys…更改表名:rename table TEST_TB to test_tb;3.设置lower_case_table_names = 14.重启数据库

March 8, 2019 · 1 min · jiezi

【MySQL】标准化安装教程

导读:本文主要介绍 CentOS 系统二进制安装 MySQL 5.7.23 版本的安装步骤,其他版本安装过程相似。1.前置准备卸载旧版MySQL查看rpm包rpm -qa|grep mysql 若有可用rpm -e卸载查找mysql残留包,有则删除,没有则忽略find / -name mysql安装相关依赖yum -y install make gcc-c++ cmake bison-devel ncurses-devel numactl libaio创建用户和用户组groupadd mysqluseradd -s /sbin/nologin -g mysql -M mysql2.下载二进制安装包并解压cd /usr/local/# wget下载或者本地下载后上传wget https://downloads.mysql.com/archives/get/file/mysql-5.7.23-linux-glibc2.12-x86_64.tar.gz# 解压安装包tar -zxvf mysql-5.7.23-linux-glibc2.12-x86_64.tar.gz# 解压后为了方便后面操作可把解压后文件名修改为mysqlmv mysql-5.7.23-linux-glibc2.12-x86_64 mysql# 更改文件夹所属chown -R mysql.mysql /usr/local/mysql/3.创建mysql相关目录mkdir -p /data/mysql/{data,logs,tmp}# 更改文件夹所属chown -R mysql.mysql /data/mysql/4.创建mysql配置文件my.cnfvi /etc/my.cnf# 简单模板如下:[client]port = 3306socket = /data/mysql/tmp/mysql.sock[mysqld]user = mysqlbasedir = /usr/local/mysql datadir = /data/mysql/data port = 3306 socket = /data/mysql/tmp/mysql.sockpid-file = /data/mysql/tmp/mysqld.pidtmpdir = /data/mysql/tmp skip_name_resolve = 1symbolic-links=0max_connections = 2000group_concat_max_len = 1024000sql_mode = NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTIONlower_case_table_names = 1log_timestamps=SYSTEMcharacter-set-server = utf8interactive_timeout = 1800 wait_timeout = 1800max_allowed_packet = 32Mbinlog_cache_size = 4Msort_buffer_size = 2Mread_buffer_size = 4Mjoin_buffer_size = 4Mtmp_table_size = 96Mmax_heap_table_size = 96Mmax_length_for_sort_data = 8096#logsserver-id = 1003306log-error = /data/mysql/logs/error.logslow_query_log = 1slow_query_log_file = /data/mysql/logs/slow.loglong_query_time = 3log-bin = /data/mysql/logs/binlogbinlog_format = rowexpire_logs_days = 15log_bin_trust_function_creators = 1relay-log = /data/mysql/logs/relay-binrelay-log-recovery = 1 relay_log_purge = 1 #innodb innodb_file_per_table = 1innodb_log_buffer_size = 16Minnodb_log_file_size = 256Minnodb_log_files_in_group = 2innodb_io_capacity = 2000innodb_io_capacity_max = 4000innodb_flush_neighbors = 0innodb_flush_method = O_DIRECTinnodb_autoinc_lock_mode = 2innodb_read_io_threads = 8innodb_write_io_threads = 8innodb_buffer_pool_size = 2G5.配置mysql.servercd /usr/local/mysql/support-filescp mysql.server /etc/init.d/mysqlvi /etc/init.d/mysql# 修改目录位置basedir=/usr/local/mysqldatadir=/data/mysql/data# 注册开机启动服务chkconfig –add mysqlchkconfig –list6.添加环境变量echo “PATH=$PATH:/usr/local/mysql/bin " >> /etc/profile source /etc/profile7.初始化mysql/usr/local/mysql/bin/mysqld –initialize –user=mysql –basedir=/usr/local/mysql –datadir=/data/mysql/data# 临时密码保存在errlog中 # 获取临时密码more /data/mysql/logs/error.log |grep password8.启动mysql服务 并修改密码# 启动mysql服务service mysql start# 使用初始密码登录mysql服务mysql -uroot -palter user ‘root’@’localhost’ identified by ‘root’;flush privileges; ...

March 8, 2019 · 1 min · jiezi

MySQL运维实战 之 PHP访问MySQL你使用对了吗

大家都知道,slow query系统做的好不好,直接决定了解决slow query的效率问题一个数据库管理平台,拥有一个好的slow query系统,基本上就拥有了解锁性能问题的钥匙但是今天主要分享的并不是平台,而是在平台中看到的奇葩指数五颗星的slow issue好了,关子卖完了,直接进入正题一、症状一堆如下慢查询# User@Host: cra[cra] @ [xx] Id: 3352884621# Query_time: 0.183673 Lock_time: 0.000000 Rows_sent: 0 Rows_examined: 0use xx_db;SET timestamp=1549900927;# administrator command: Prepare;# Time: 2019-02-12T00:02:07.516803+08:00# User@Host: cra[cra] @ [xx] Id: 3351119968# Query_time: 0.294081 Lock_time: 0.000000 Rows_sent: 0 Rows_examined: 0SET timestamp=1549900927;# administrator command: Prepare;从我们的监控图上可以看到,每天不定时间段的slow query 总数在攀升,但是却看不到任何query 语句这是我接触到的slow query优化案例中从来没有过的情况,比较好奇,也比较兴奋,至此决心要好好看看这个问题二、排查要解决这个问题,首先想到的是,如何复现这个问题,如何模拟复现这个症状MySQL客户端 模拟prepare* 模拟root:xx> prepare stmt1 from ‘select * from xx_operation_log where id = ?’;Query OK, 0 rows affected (0.00 sec)Statement prepared* 结果# Time: 2019-02-14T14:14:50.937462+08:00# User@Host: root[root] @ localhost [] Id: 369# Query_time: 0.000105 Lock_time: 0.000000 Rows_sent: 0 Rows_examined: 0SET timestamp=1550124890;prepare stmt1 from ‘select * from xx_operation_log where id = ?’;结论是: MySQL client 模拟出来的prepare 并不是我们期待的,并没有得到我们想要的 administrator command: Prepareperl 模拟prepare#!/usr/bin/perluse DBI;my $dsn = “dbi:mysql:database=${db_name};hostname=${db_host};port=${db_port}”;#数据源#获取数据库句柄my $dbh = DBI->connect(“DBI:mysql:database=xx;host=xx”, “xx”, “xx”, {‘RaiseError’ => 1});my $sql = qq{select * from xx_operation_log where id in (?)};my $sth = $dbh->prepare($sql);$sth->bind_param (1, ‘100’);sleep 3;$sth->execute();结论是:跟MySQL客户端一样,同样是看不到administrator command: Preparephp 模拟prepare1. 官方网址:https://dev.mysql.com/doc/apis-php/en/apis-php-mysqli-stmt.prepare.html<?php$link = mysqli_connect(“xx”, “dba”, “xx”, “xx_db”);/* check connection /if (mysqli_connect_errno()) { printf(“Connect failed: %s\n”, mysqli_connect_error()); exit();}$city = ‘1’;/ create a prepared statement /$stmt = mysqli_stmt_init($link);if (mysqli_stmt_prepare($stmt, ‘select * from xx_operation_log where id in (1,2,3)’){ / bind parameters for markers / / mysqli_stmt_bind_param($stmt, “s”, $city); /* execute query / mysqli_stmt_execute($stmt); / bind result variables / mysqli_stmt_bind_result($stmt, $district); / fetch value / mysqli_stmt_fetch($stmt); printf("%s is in district %s\n", $city, $district); / close statement / mysqli_stmt_close($stmt);}/ close connection */mysqli_close($link);?>php模拟得到的slow 结果[root@xx 20190211]# cat xx-slow.log | grep ‘administrator command: Prepare’ -B4 | grep ‘User@Host’ | grep ‘xx_rx’ | wc -l7891[root@xx 20190211]# cat xx-slow.log | grep ‘administrator command: Prepare’ -B4 | grep ‘User@Host’ | wc -l7908结论: 通过php代码,我们成功模拟出了想要的结果那顺藤摸瓜,抓取下这段时间有相同session id的整个sql执行过程MySQL开启slow=0的抓包模式可以定位到同一个session id(3415357118) 的 prepare + execute + close stmt# User@Host: xx_rx[xx_rx] @ [xx.xxx.xxx.132] Id: 3415357118# Query_time: 0.401453 Lock_time: 0.000000 Rows_sent: 0 Rows_examined: 0use xx_db;SET timestamp=1550017125;# administrator command: Prepare;# Time: 2019-02-13T08:18:45.624671+08:00–# User@Host: xx_rx[xx_rx] @ [xx.xxx.xxx.132] Id: 3415357118# Query_time: 0.001650 Lock_time: 0.000102 Rows_sent: 0 Rows_examined: 1use xx_db;SET timestamp=1550017125;update xx set updated_at = ‘2019-02-13 08:18:45’, has_sales_office_phone = 1, has_presale_permit = 1 where id = 28886;# Time: 2019-02-13T08:18:45.626138+08:00–# User@Host: xx_rx[xx_rx] @ [xx.xxx.xxx.132] Id: 3415357118# Query_time: 0.000029 Lock_time: 0.000000 Rows_sent: 0 Rows_examined: 1use xx_db;SET timestamp=1550017125;# administrator command: Close stmt;# Time: 2019-02-13T08:18:45.626430+08:00结论:我们发现,prepare时间的确很长,但是sql语句却执行的很快,这就很尴尬了本来是想通过抓包,看看是否能够验证我们的猜想: prepare的语句非常大,或者条件非常复杂,从而导致prepare在服务器端很慢结果发现query语句也都非常简单那么既然如此,我们就找了业务方,将对应业务的prepare方法一起看看结果发现,业务使用的是php-pdo的方式,所以我们就又有了如下发现php-pdo 两种prepare模式http://php.net/manual/zh/pdo.prepare.php1. 本地prepare $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES,true); 不会发送给MySQL Server2. 服务器端prepare $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES,false); 发送给MySQL Server验证两种prepare模式服务端prepare模式( ATTR_EMULATE_PREPARES = false)<?php$dbms=‘mysql’; //数据库类型$host=‘xxx’; //数据库主机名$dbName=‘test’; //使用的数据库$user=‘xx’; //数据库连接用户名$pass=‘123456’; //对应的密码$dsn="$dbms:host=$host;dbname=$dbName";try { $pdo = new PDO($dsn, $user, $pass); //初始化一个PDO对象 $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES,false); echo “—– prepare begin —–\n”; $stmt = $pdo->prepare(“select * from test.chanpin where id = ?”); echo “—– prepare after —–\n”; $stmt->execute([333333]); echo “—– execute after —–\n”; $rs = $stmt->fetchAll();} catch (PDOException $e) { die (“Error!: " . $e->getMessage() . “<br/>”);}strace -s200 -f php mysql1.php 跟踪大家可以看到这个模式下,prepare的时候,是将query+占位符 发送给服务端的本地prepare模式 (ATTR_EMULATE_PREPARES = true )<?php$dbms=‘mysql’; //数据库类型$host=‘xx’; //数据库主机名$dbName=‘test’; //使用的数据库$user=‘xx’; //数据库连接用户名$pass=‘123456’; //对应的密码$dsn="$dbms:host=$host;dbname=$dbName”;try { $pdo = new PDO($dsn, $user, $pass); //初始化一个PDO对象 $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES,true); echo “—– prepare begin —–\n”; $stmt = $pdo->prepare(“select * from test.chanpin where id = ?”); echo “—– prepare after —–\n”; $stmt->execute([333333]); echo “—– execute after —–\n”; $rs = $stmt->fetchAll();} catch (PDOException $e) { die (“Error!: " . $e->getMessage() . “<br/>”);}strace -s200 -f php mysql1.php 跟踪大家可以看到这个模式下,prepare的时候,是不会将query发送给服务端的,只有execute的时候才会发送跟业务方确认后,他们使用的是后者,也就是修改了默认值,他们原本是想提升数据库的性能,因为预处理后只需要传参数就好了但是对于我们的业务场景并不适合,我们的场景是频繁打开关闭连接,也就是预处理基本就用不到另外文档上面也明确指出prepared statements 性能会不好调整和验证如何验证业务方是否将prepare修改为local了呢?dba:(none)> show global status like ‘Com_stmt_prepare’;+——————+———–+| Variable_name | Value |+——————+———–+| Com_stmt_prepare | 716836596 |+——————+———–+1 row in set (0.00 sec)通过观察,发现这个值没有变化,说明调整已经生效总结prepare的优点1. 防止SQL注入2. 特定场景下提升性能 什么是特定场景: 就是先去服务端用占位符占位,后面可以直接发送请求来填空(参数值) 这样理论上来说, 你填空的次数非常多,性能才能发挥出来prepare的缺点1. 在服务器端的prepare毕竟有消耗,当并发量大,频繁prepare的时候,就会有性能问题2. 服务端的prepare模式还会带来的另外一个问题就是,排错和slow 优化有困难,因为大部分情况下是看不到真实query的3. 尽量设置php-pdo为 $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES,true) ,在本地prepare,不要给服务器造成额外压力建议1. 默认情况下,应该使用php-pdo的默认配置,采用本地prepare的方式,这样可以做到防SQL注入的效果,性能差不到哪里去2. 除非真的是有上述说的特定场景,可以考虑配置成服务器prepare模式,前提是要做好测试本文作者:兰春阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

March 8, 2019 · 3 min · jiezi

GROUP BY你都不会!ROLLUP,CUBE,GROUPPING详解

Group ByGroup By 谁不会啊?这不是最简单的吗?越是简单的东西,我们越会忽略掉他,因为我们不愿意再去深入了解它。1 小时 SQL 极速入门(一)1 小时 SQL 极速入门(二)1 小时 SQL 极速入门(三)——Oracle 分析函数SQL 高级查询——(层次化查询,递归)今天就带大家了解一下Group By 的新用法吧。ROLL UPROLL UP 搭配 GROUP BY 使用,可以为每一个分组返回一个小计行,为所有分组返回一个总计行。直接看例子,我们有以下数据表,包含工厂列,班组列,数量列三列。当向 ROLLUP 传入一列时,会得到一个总计行。SELECT factory, SUM(quantity)FROM productionGROUP BY ROLLUP(factory)ORDER BY factory结果:当向 ROLLUP 传递两列时,将会按照这两列进行分组,同时按照第一列的分组结果返回小计行。我们同时传入工厂和部门看一下。SELECT factory,department, SUM(quantity)FROM productionGROUP BY ROLLUP(factory, department)ORDER BY factory结果:可以看到对每一个工厂都有一个小计行,最后对所有的有一个总计行。也可以这样理解 如果 ROLLUP(A,B)则先对 A,B进行 GROUP BY,之后对 A 进行 GROUP BY,最后对全表 GROUP BY。如果 ROLLUP(A,B,C)则先对 A,B,C进行 GROUP BY ,然后对 A,B进行GROUP BY,再对 A 进行GROUP BY,最后对全表进行 GROUP BY.CUBECUBE 和 ROLLUP 对参数的处理是不同的,我们可以这样理解。如果 CUBE(A,B)则先对 A,B 进行 GROUP BY,之后对 A 进行 GROUP BY,然后对 B 进行 GROUP BY,最后对全表进行 GROUP BY.如果 CUBE(A,B,C)则先对 A,B,C 进行 GROUP BY,之后对 A,B ,之后对A,C ,之后对 B,C 之后对 A,之后对 B,之后对 C,最后对全表GROUP BY看一个简单的例子:SELECT factory,department, SUM(quantity)FROM productionGROUP BY CUBE(factory, department)ORDER BY factory,department;结果:可以看出来首先对 FACTORY,DEPARTMENT进行分组汇总,然后对FACTORY 分组汇总,之后对 DEPARTMENT 分组汇总,最后有一行全表汇总。GROUPINGGROUPING()函数只能配合 ROLLUP 和 CUBE 使用,GROUPING()接收一列,如果此列不为空则返回0,如果为空则返回1.我们用第一个ROLLUP例子举例SELECT GROUPING(factory), factory, department, SUM(quantity)FROM productionGROUP BY ROLLUP(factory, department)ORDER BY factory, department;结果:看到,最后一行的 FACTORY 为空,所以 GROUPING()返回 1.也可以与CUBE结合使用,方法是一样的。GROUPING SETSGROUPING SETS 与 CUBE 有点类似,CUBE是对参数进行自由组合进行分组。GROUPING SETS则对每个参数分别进行分组,GROUPING SETS(A,B)就代表先按照 A 分组,再按照 B分组。SELECT factory, department, SUM(quantity)FROM productionGROUP BY GROUPING SETS(factory, department)ORDER BY factory, department结果:可以看出来结果是按照工厂和部门分别分组汇总的。GROUPING_ID()GROUPING_ID()配合GROUPING()函数使用,GROUPING_ID(A,B)的值由GROUPING(A)与GROUPING(B)的值决定,如果GROUPING(A)为1,GROUPING(B)为0,则GROUPING_ID(A,B)的值为 10,十进制的 3.SELECT factory, department, GROUPING(factory), GROUPING(department), GROUPING_ID(factory,department), SUM(quantity)FROM productionGROUP BY CUBE(factory, department)ORDER BY factory, department;结果:有了GROUPING_ID列,我们就可以使用 HAVING 字句来对查询结果进行过滤。选择GROUPING_ID=0的就表示 FACTORY,DEPARTMENT两列都不为空。 ...

March 8, 2019 · 1 min · jiezi

Mysql DDL出现长时间等待MDL问题分析

给表新增字段时,发现锁表了,查看进程,提示Waiting for table metadata lock,等待锁释放;然而蛋疼的是几分钟过去了,依然没有任何的进展,特此记录下这个问题的定位过程以及MDL的相关背景知识看到上面的表现,基本问题就来了Metadata Lock 是什么鬼是什么原因导致一直等待<!– more –>I. 问题定位首先需要确认什么地方加锁,从mysql出发,应该怎么定位?1. 定位过程对于mysql而言,一般来讲上锁和事物时伴生关系,所以我们的直观出发点就是查找db当前正在执行的事物– 查询当前正在执行的事物的sqlSELECT * FROM information_schema.INNODB_TRX;输出结果如下,首先拿到事物对应的进程id拿到id之后,则可以分析对应的进程信息– 查询进程信息show processlist– 查询所有的进程信息show full processlist然后定位到具体的进程然后登陆到目标机器,查看端口号对应的进程,通过lsof命令查看lsof -i tcp:52951从图中可以看出,是一个python进程的mysql连接开启的事物,进程id为5436接着查看进程对应的信息ps aux | grep 5436这个脚本正是测试aiomysql的python脚本,内容比较简单import asyncioimport aiomysqlloop = asyncio.get_event_loop()@asyncio.coroutinedef test_example(): conn = yield from aiomysql.connect(host=‘127.0.0.1’, port=3306, user=‘root’, password=’’, db=‘test’, loop=loop, autocommit=False) cur = yield from conn.cursor() yield from cur.execute(“SELECT * from test_table”) print(cur.description) r = yield from cur.fetchall() print(r) yield from cur.close() conn.close()loop.run_until_complete(test_example())2. 原因分析对python不太熟,直接借助google查一下,发现有同样的问题[Why aiomysql locks the table even when using context manager?](https://stackoverflow.com/que…这个问题抛出,在通过with打开连接获取游标后,执行mysql,但是没有commit之前,会锁表,这个期间修改表都会出现等待下面近给出了解答,并没有看到更多的深层次的说明,先记录下,解决办法就是在创建连接池的时候,选择自动提交方式,然后就不会有这个问题了pool = await aiomysql.create_pool( host=“localhost”, user=“test”, password=“test”, db=“test”, autocommit=True, cursorclass=DictCursor, loop=loop)II. Metadata Lock说明找到一篇文章说MDL的,推荐详细阅读 MySQL表结构变更你不可不知的Metadata Lock详解1. MDL 说明抓一下核心的要点,简单说一下看完这篇文章之后的朴素理解MetaData Lock 简称为MDL,简单来说就是表的元数据锁;当修改表结构的时候,就需要持有这个锁a. 作用MDL的主要作用只有一点,保护一个正在执行的事物表结构不被修改有一个原则,MDL是事物级别的,只有事物结束之后才会释放,而这里面说的事物分为两类显示事物:关闭autocommit以begin或start transaction开始的操作AC-NL-RO(auto-commit non-locking read-only):auto commit 开启之下的select操作b. 实例说明直接看上面的说明,不太直观,一个经典的case如下session1 开启了一个事物,执行查询操作;但是现在session2 要删除表,如果执行成功,那么session1的第二次查询就跪了,这样就违背了事物的原则,所有在5.5版本引入了MDL,来保证在事物执行期间,表结构不被修改2. 出现MDL等待原因及解决方法当我们出现修改表结构,就需要获取MDL的排他锁,因此只有这个表没有事物在执行时,才能获取成功;当持有独占锁之后,这个表的其他操作将被阻塞(即不能插入数据,修改数据,也不能开启事物操作)因此在执行DDL时,一直出现等待MDL的时候,常见的原因有下面三个a. 长事物,阻塞DDL,从而阻塞所有同表的后续操作通过 show processlist看到表上有正在进行的操作(包括读),此时修改表时也会等待获取MDL,这种时候解决办法要么就是等待执行完毕,要么就是直接kill掉进程b. 未提交事物,阻塞DDL通过 show processlist没有找到表上的操作,但是通过information_schema.innodb_trx发现有未提交的事物,c. 异常的状况通过 show processlist 和事物查询都没有的情况下,可能的场景是一个显示的事物中,对表的操作出现了异常,虽然事物失败,但是持有的锁还没有释放,也会导致这个原因可以在performance_schema.events_statements_current表中查询失败的语句3. MDL分类与sql实例前面两小节,分别说明什么是MDL(朴素理解为表的元数据锁),以及当修改表时出现长时间的等待MDL的原因分析;正常看完之后,应该会有下面的疑惑MDL有哪些类型哪些sql会持有MDL对于MDL的类型,从网上截一张图接下来需要分析下不同锁模式对应的sql属性含义事例MDL_INTENTION_EXCLUSIVE(IX)意向排他锁用于global和commit的加锁。truncate table t1; insert into t1 values(3,’abcde’); 会加如下锁 (GLOBAL,MDL_STATEMENT,MDL_INTENTION_EXCLUSIVE)(SCHEMA,MDL_TRANSACTION,MDL_INTENTION_EXCLUSIVE)MDL_SHARED(S)只访问元数据 比如表结构,不访问数据。set golbal_read_only =on 加锁 (GLOBAL,MDL_EXPLICIT,MDL_SHARED)MDL_SHARED_HIGH_PRIO(SH)用于访问information_scheam表,不涉及数据。select * from information_schema.tables;show create table xx; desc xxx; 会加如下锁: (TABLE,MDL_TRANSACTION,MDL_SHARED_HIGH_PRIO)MDL_SHARED_READ(SR)访问表结构并且读表数据select * from t1; lock table t1 read; 会加如下锁: (TABLE,MDL_TRANSACTION,MDL_SHARE_READ)MDL_SHARED_WRITE(SW)访问表结构并且写表数据insert/update/delete/select .. for update 会加如下锁:(TABLE,MDL_TRANSACTION,MDL_SHARE_WRITE)MDL_SHARED_UPGRADABLE(SU)是mysql5.6引入的新的metadata lock,可以说是为了online ddl 才引入的。特点是允许DML,防止DDL;alter table/create index/drop index 会加该锁; 加入下锁 (TABLE,MDL_TRANSACTION,MDL_SHARED_UPGRADABLE)MDL_SHARED_NO_WRITE(SNW)可升级锁,访问表结构并且读写表数据,并且禁止其它事务写。alter table t1 modify c bigint; (非onlineddl) (TABLE,MDL_TRANSACTION,MDL_SHARED_NO_WRITE)MDL_SHARED_NO_READ_WRITE(SNRW)可升级锁,访问表结构并且读写表数据,并且禁止其它事务读写。lock table t1 write; 加锁 (TABLE,MDL_TRANSACTION,MDL_SHARED_NO_READ_WRITEMDL_EXCLUSIVE(X)防止其他线程读写元数据CREATE/DROP/RENAME TABLE,其他online DDL在rename阶段也持有X锁(TABLE,MDL_TRANSACTION,MDL_EXCLUSIVE)4, 小结上面的内容,可能信息量比较大,特别是MDL的锁分类情况,很难抓住重点,针对我们日常接触中,简单给出小结MDL是为了保证事物执行过程中,表结构不被修改引入的;因此修改表结构的前提是这个表上没有事物(没有正在执行,失败,或者未提交的事物)DDL执行,一般来讲是需要获取排他的MDLDML都会开启事物,因此会获取 MDL_SW 锁DQL语句会获取 MDL_SR 锁几个简称的说明MDL: metadata lock,可以简单理解为表的元数据锁DDL: 数据定义语言,可以简单理解为表的操作,如创建,修改,删除表、视图等,新增索引、字段等操作DML: 数据操作语言,也就是我们常规理解的 insert, update, delete 语句DQL: 数据查询语言,常见的select语句几个常见疑问解答a. 为什么同一张表的多个DDL不能并行执行MDL读锁是互相兼容的,可以有多个增删查改MDL写锁是互斥的,只能有一个表的DDLb. 为什么有时候DDL会卡住MDL读写锁之间是互斥的,所以如果DDL卡住,就证明有事务在执行,不能申请MDL写锁c. 常见卡住的场景非常频繁的业务高峰期有慢查询把持着MDL读锁有事物一直未提交d. 为什么需要MDL锁当事务本身执行的时候理论上是不能容忍表结构在中途发生改变的5. 更多参考相关博文或者问答[Why aiomysql locks the table even when using context manager?](https://stackoverflow.com/que...MySQL表结构变更你不可不知的Metadata Lock详解理解MySQL的MDL元数据锁II. 其他1. 一灰灰Blog: https://liuyueyi.github.io/he…一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛2. 声明尽信书则不如,已上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激微博地址: 小灰灰BlogQQ: 一灰灰/33027978403. 扫描关注一灰灰blog知识星球 ...

March 8, 2019 · 1 min · jiezi

PHP面试MySQL数据库的面试题

你好,是我琉忆,PHP程序员面试笔试系列图书的作者。本周(2019.3.4至3.8)的一三五更新的文章如下:周一:PHP面试MySQL数据库的基础知识周三:PHP面试MySQL数据库的索引周五:PHP面试MySQL数据库的面试真题今天周五,提前祝各位周末愉快。自己整理了一篇“什么是数据库三级封锁协议?”的文章,关注公众号:“琉忆编程库”,回复:“锁”,我发给你。以下内容部分来自《PHP程序员面试笔试宝典》和《PHP程序员面试笔试真题解析》如需转载请注明出处。【真题1】执行以下 SQL 语句后将发生( )。BEGIN TRANSACTIONDELETE FROM MYTABLE WHERE ID=1DELETE FROM OTHERTABLEROLLBACK TRANSACTIONA.OTHERTABLE 中的内容将被删除B.OTHERTABLE 和 MYTABLE 中的内容都会被删除C.OTHERTABLE 中的内容将被删除,MYTABLE 中 ID 是 1 的内容将被删除D.数据库没有变化参考答案:D。分析:这个查询是一个事务,并且这个事务的最后有回滚,数据库不会有变化。【真题2】如何进行数据库优化?数据库优化的过程可以使用以下的方法进行:1)选取最适用的字段属性,尽可能减少定义字段长度,尽量把字段设置NOT NULL,例如’省份、性别’,最好设置为ENUM。2)使用连接(JOIN)来代替子查询。① 删除没有任何订单客户:DELETE FROM customerinfo WHERE customerid NOT in(SELECT customerid FROM orderinfo)。② 提取所有没有订单客户: SELECT FROM customerinfo WHERE customerid NOT in(SELECT customerid FROM orderinfo)。③ 提高b的速度优化:SELECT FROM customerinfo LEFT JOIN orderid customerinfo. customerid=orderinfo.customerid WHERE orderinfo.customerid IS NULL。3)使用联合(UNION)来代替手动创建的临时表。创建临时表:SELECT name FROM ’nametest’ UNION SELECT username FROM ’nametest2’。4)事务处理。保证数据完整性,例如添加和修改。同时,如果两者成立,则都执行,一者失败都失败:mysql_query(“BEGIN”);mysql_query(“INSERT INTO customerinfo (name) VALUES (’$name1’)";mysql_query(“SELECT * FROM ‘orderinfo’ where customerid=”.$id”);mysql_query(“COMMIT”);5)锁定表,优化事务处理。用一个SELECT语句取出初始数据,通过一些计算,用UPDATE语句将新值更新到表中。包含有WRITE关键字的LOCK TABLE语句可以保证在UNLOCK TABLES命令被执行之前,不会有其他的访问来对customerinfo表进行插入、更新或者删除的操作。mysql_query(“LOCK TABLE customerinfo READ, orderinfo WRITE”);mysql_query(“SELECT customerid FROM ‘customerinfo’ where id=”.$id);mysql_query(“UPDATE ‘orderinfo’ SET ordertitle=’$title’ where customerid=”.$id);mysql_query(“UNLOCK TABLES”);6)使用外键,优化锁定表。把customerinfo里的customerid映射到orderinfo里的customerid,任何一条没有合法的customerid的记录不会写到orderinfo里。 CREATE TABLE customerinfo ( customerid INT NOT NULL, PRIMARY KEY(customerid) )TYPE = INNODB; CREATE TABLE orderinfo ( orderid INT NOT NULL, customerid INT NOT NULL, PRIMARY KEY(customerid,orderid), FOREIGN KEY (customerid) REFERENCES customerinfo (customerid) ON DELETE CASCADE )TYPE = INNODB;注意:‘ON DELETE CASCADE’,该参数保证当customerinfo表中的一条记录删除的话同时也会删除order。表中的该用户的所有记录,注意使用外键时要定义数据库引擎为INNODB。【真题3】如何选择正确的存储引擎?在MySQL中有两个存储引擎:MyISAM和InnoDB,每个引擎都有利有弊。MyISAM适合于一些需要大量查询的应用,但其对于有大量写操作的支持并不是很好。甚至只是需要update一个字段,整个表都会被锁起来,而其他进程,就算是读进程都无法操作直到读操作完成。另外,MyISAM 对于 SELECT COUNT(*) 这类的计算是超快无比的。InnoDB 的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。但是它支持“行锁”,于是在写操作比较多的时候,会更优秀。并且,它还支持更多的高级应用,例如事务。【真题4】用什么方法检查PHP脚本的执行效率(通常是脚本执行时间)和数据库SQL的效率(通常是数据库query时间),并定位和分析脚本执行和数据库查询的瓶颈所在?参考答案:检查PHP脚本的执行效率的方法如下:可以在检查的代码开头记录一个时间,然后在代码的结尾也记录一个时间,结尾时间减去开头时间取这个时间的差值,从而检查PHP的脚本执行效率,记录时间可以使用microtime()函数。检查数据库SQL的效率的方法如下:可以通过explain显示MySQL如何使用索引来处理select语句及连接表,帮助选择更好的索引和写出更优化的查询语句。然后启用slow query log记录慢查询,通过查看SQL的执行时间和效率来定位分析脚本执行的问题和瓶颈所在。自己整理了一篇“什么是数据库三级封锁协议?”的文章,关注公众号:“琉忆编程库”,回复:“锁”,我发给你。【真题5】 以下说法正确的是( )。A.使用索引能加快插入数据的速度B.良好的索引策略有助于防止跨站攻击C.应当根据数据库的实际应用合理设计索引D.删除一条记录将导致整个表的索引被破坏参考答案:C。分析:索引的作用主要是帮助数据库快速查找到对应的数据,并不能加快插入数据的速度,所以,选项A错误。索引不能够帮助防止跨站攻击,所以,选项B错误。创建合理的索引需要分析数据库的实际用途并找出它的弱点。优化脚本中的冗余查询同样也能提高数据库效率。索引是占用物理空间的,所以在实际的应用中是要合理设计使用索引的。所以,选项C正确。索引是一种表结构,删除一条数据也不会影响到整个表的索引,并且索引不一定是数字,也可以是字符串。所以,选项D错误。【真题6】下列关于全文检索技术的说法中,不正确的是( )。A.Sphinx是一个基于SQL的全文检索引擎,可以结合MySQL做全文搜索,它可以提供比数据库本身更专业的搜索功能B.Solr是新一代的全文检索组件,它比Lucene的搜索效率高很多,还能支持HTTP的访问方式,PHP调用Solr也很方便C.MySQL中把一个字段建立FULLTEXT索引,就可以实现全文检索,目前MyISAM和InnoDB的table都支持FULLTEXT索引D.Lucene附带的二元分词分析器CJKAnalyzer切词速度很快,能满足一般的全文检索需要参考答案:B。分析:Sphinx是一个基于SQL的全文检索引擎,可以结合MySQL、PostgreSQL做全文搜索,它可以提供比数据库本身更专业的搜索功能,使得应用程序更容易实现专业化的全文检索。Solr是一个独立的企业级搜索应用服务器,用户可以通过HTTP请求访问,它是采用JAVA5开发,基于Lucene的全文搜索服务器,同时对其进行了扩展,提供了比Lucene更为丰富的查询语言,同时实现了可配置、可扩展并对查询性能进行了优化,并且提供了一个完善的功能管理界面,是一款非常优秀的全文搜索引擎。并且Solr比Lucene的搜索效率高很多,但是PHP调用Solr并不方便,选项B的说法错误。MySQL中的MyISAM和InnoDB都是支持FULLTEXT全文索引的。全文搜索引擎可以在不使用模板匹配操作的情况下查找单词或短语。【真题7】考虑如下SQL语句,哪个选项能对返回记录的条数进行限制?( )(双选)SELECT * FROM MY_TABLEA.如果可能,那么把查询转换成存储例程B.如果程序允许,那么给查询指定返回记录的范围C.如果可能,那么添加 where 条件D.如果DBMS允许,那么把查询转换成视图参考答案:B、C。分析:有两个方法能限制返回记录的条数——使用 where 条件或limit关键字指定查询返回的记录的范围。通常情况下,如果没有特殊需要,那么尽量不要用 select *,这会浪费大量的数据缓存。以上内容摘自《PHP程序员面试笔试宝典》和《PHP程序员面试笔试真题解析》书籍,目前本书没有电子版,可到各大电商平台购买纸质版。更多PHP相关的面试知识、考题可以关注公众号获取:琉忆编程库对本文有什么问题或建议都可以进行留言,我将不断完善追求极致,感谢你们的支持。 ...

March 8, 2019 · 1 min · jiezi

干货|一次MySQL两千万数据大表的优化过程,三种解决方案!

问题概述使用阿里云rds for MySQL数据库(就是MySQL5.6版本),有个用户上网记录表6个月的数据量近2000万,保留最近一年的数据量达到4000万,查询速度极慢,日常卡死。严重影响业务。问题前提:老系统,当时设计系统的人大概是大学没毕业,表设计和sql语句写的不仅仅是垃圾,简直无法直视。原开发人员都已离职,到我来维护,这就是传说中的维护不了就跑路,然后我就是掉坑的那个!!!我尝试解决该问题,so,有个这个日志。方案概述以上三种方案,按顺序使用即可,数据量在亿级别一下的没必要换nosql,开发成本太高。三种方案我都试了一遍,而且都形成了落地解决方案。该过程心中慰问跑路的那几个开发者一万遍 :)方案一详细说明:优化现有mysql数据库跟阿里云数据库大佬电话沟通 and Google解决方案 and 问群里大佬,总结如下(都是精华):1.数据库设计和表创建时就要考虑性能2.sql的编写需要注意优化3.分区4.分表5.分库1.数据库设计和表创建时就要考虑性能mysql数据库本身高度灵活,造成性能不足,严重依赖开发人员能力。也就是说开发人员能力高,则mysql性能高。这也是很多关系型数据库的通病,所以公司的dba通常工资巨高。设计表时要注意:1.表字段避免null值出现,null值很难查询优化且占用额外的索引空间,推荐默认数字0代替null。2.尽量使用INT而非BIGINT,如果非负则加上UNSIGNED(这样数值容量会扩大一倍),当然能使用TINYINT、SMALLINT、MEDIUM_INT更好。3.使用枚举或整数代替字符串类型4.尽量使用TIMESTAMP而非DATETIME5.单表不要有太多字段,建议在20以内6.用整型来存IP索引1.索引并不是越多越好,要根据查询有针对性的创建,考虑在WHERE和ORDER BY命令上涉及的列建立索引,可根据EXPLAIN来查看是否用了索引还是全表扫描2.应尽量避免在WHERE子句中对字段进行NULL值判断,否则将导致引擎放弃使用索引而进行全表扫描3.值分布很稀少的字段不适合建索引,例如"性别"这种只有两三个值的字段4.字符字段只建前缀索引5.字符字段最好不要做主键6.不用外键,由程序保证约束7.尽量不用UNIQUE,由程序保证约束8.使用多列索引时主意顺序和查询条件保持一致,同时删除不必要的单列索引简言之就是使用合适的数据类型,选择合适的索引2.sql的编写需要注意优化1.使用limit对查询结果的记录进行限定2.避免select *,将需要查找的字段列出来3.使用连接(join)来代替子查询4.拆分大的delete或insert语句5.可通过开启慢查询日志来找出较慢的SQL6.不做列运算:SELECT id WHERE age + 1 = 10,任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边7.sql语句尽可能简单:一条sql只能在一个cpu运算;大语句拆小语句,减少锁时间;一条大sql可以堵死整个库8.OR改写成IN:OR的效率是n级别,IN的效率是log(n)级别,in的个数建议控制在200以内9.不用函数和触发器,在应用程序实现10.避免%xxx式查询11.少用JOIN12.使用同类型进行比较,比如用'123’和'123’比,123和123比13.尽量避免在WHERE子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描14.对于连续数值,使用BETWEEN不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 515.列表数据不要拿全表,要使用LIMIT来分页,每页数量也不要太大引擎引擎目前广泛使用的是MyISAM和InnoDB两种引擎:MyISAMMyISAM引擎是MySQL 5.1及之前版本的默认引擎,它的特点是:1.不支持行锁,读取时对需要读到的所有表加锁,写入时则对表加排它锁2.不支持事务3.不支持外键4.不支持崩溃后的安全恢复5.在表有读取查询的同时,支持往表中插入新纪录6.支持BLOB和TEXT的前500个字符索引,支持全文索引7.支持延迟更新索引,极大提升写入性能8.对于不会进行修改的表,支持压缩表,极大减少磁盘空间占用InnoDBInnoDB在MySQL 5.5后成为默认索引,它的特点是:1.支持行锁,采用MVCC来支持高并发2.支持事务3.支持外键4.支持崩溃后的安全恢复5.不支持全文索引总体来讲,MyISAM适合SELECT密集型的表,而InnoDB适合INSERT和UPDATE密集型的表3.分区MySQL在5.1版引入的分区是一种简单的水平拆分,用户需要在建表的时候加上分区参数,对应用是透明的无需修改代码对用户来说,分区表是一个独立的逻辑表,但是底层由多个物理子表组成,实现分区的代码实际上是通过对一组底层表的对象封装,但对SQL层来说是一个完全封装底层的黑盒子。MySQL实现分区的方式也意味着索引也是按照分区的子表定义,没有全局索引用户的SQL语句是需要针对分区表做优化,SQL条件中要带上分区条件的列,从而使查询定位到少量的分区上,否则就会扫描全部分区,可以通过EXPLAIN PARTITIONS来查看某条SQL语句会落在那些分区上,从而进行SQL优化,我测试,查询时不带分区条件的列,也会提高速度,故该措施值得一试。分区的好处是:1.可以让单表存储更多的数据2.分区表的数据更容易维护,可以通过清楚整个分区批量删除大量数据,也可以增加新的分区来支持新插入的数据。另外,还可以对一个独立分区进行优化、检查、修复等操作3.部分查询能够从查询条件确定只落在少数分区上,速度会很快4.分区表的数据还可以分布在不同的物理设备上,从而搞笑利用多个硬件设备5.可以使用分区表赖避免某些特殊瓶颈,例如InnoDB单个索引的互斥访问、ext3文件系统的inode锁竞争6.可以备份和恢复单个分区分区的限制和缺点:1.一个表最多只能有1024个分区2.如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进来3.分区表无法使用外键约束4.NULL值会使分区过滤无效5.所有分区必须使用相同的存储引擎分区的类型:1.RANGE分区:基于属于一个给定连续区间的列值,把多行分配给分区2.LIST分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择3.HASH分区:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL中有效的、产生非负整数值的任何表达式4.KEY分区:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值5.具体关于mysql分区的概念请自行google或查询官方文档,我这里只是抛砖引玉了。4.分表分表就是把一张大表,按照如上过程都优化了,还是查询卡死,那就把这个表分成多张表,把一次查询分成多次查询,然后把结果组合返回给用户。分表分为垂直拆分和水平拆分,通常以某个字段做拆分项。比如以id字段拆分为100张表: 表名为 tableName_id%100但:分表需要修改源程序代码,会给开发带来大量工作,极大的增加了开发成本,故:只适合在开发初期就考虑到了大量数据存在,做好了分表处理,不适合应用上线了再做修改,成本太高!!!而且选择这个方案,都不如选择我提供的第二第三个方案的成本低!故不建议采用。5.分库把一个数据库分成多个,建议做个读写分离就行了,真正的做分库也会带来大量的开发成本,得不偿失!不推荐使用。方案二详细说明:升级数据库,换一个100%兼容mysql的数据库mysql性能不行,那就换个。为保证源程序代码不修改,保证现有业务平稳迁移,故需要换一个100%兼容mysql的数据库。开源选择1.tiDB https://github.com/pingcap/tidb2.Cubrid https://www.cubrid.org/3.开源数据库会带来大量的运维成本且其工业品质和MySQL尚有差距,有很多坑要踩,如果你公司要求必须自建数据库,那么选择该类型产品。云数据选择1.阿里云POLARDB2.https://www.aliyun.com/produc…我开通测试了一下,支持免费mysql的数据迁移,无操作成本,性能提升在10倍左右,价格跟rds相差不多,是个很好的备选解决方案!1.阿里云OcenanBase2.淘宝使用的,扛得住双十一,性能卓著,但是在公测中,我无法尝试,但值得期待3.阿里云HybridDB for MySQL (原PetaData)4.https://www.aliyun.com/produc…我也测试了一下,是一个olap和oltp兼容的解决方案,但是价格太高,每小时高达10块钱,用来做存储太浪费了,适合存储和分析一起用的业务。1.腾讯云DCDB2.https://cloud.tencent.com/pro…腾讯的我不喜欢用,不多说。原因是出了问题找不到人,线上问题无法解决头疼!但是他价格便宜,适合超小公司,玩玩。方案三详细说明:去掉mysql,换大数据引擎处理数据数据量过亿了,没得选了,只能上大数据了。开源解决方案hadoop家族。hbase/hive怼上就是了。但是有很高的运维成本,一般公司是玩不起的,没十万投入是不会有很好的产出的!云解决方案这个就比较多了,也是一种未来趋势,大数据由专业的公司提供专业的服务,小公司或个人购买服务,大数据就像水/电等公共设施一样,存在于社会的方方面面。国内做的最好的当属阿里云。我选择了阿里云的MaxCompute配合DataWorks,使用超级舒服,按量付费,成本极低。MaxCompute可以理解为开源的Hive,提供sql/mapreduce/ai算法/python脚本/shell脚本等方式操作数据,数据以表格的形式展现,以分布式方式存储,采用定时任务和批处理的方式处理数据。DataWorks提供了一种工作流的方式管理你的数据处理任务和调度监控。当然你也可以选择阿里云hbase等其他产品,我这里主要是离线处理,故选择MaxCompute,基本都是图形界面操作,大概写了300行sql,费用不超过100块钱就解决了数据处理问题。

March 7, 2019 · 1 min · jiezi

如何选择合适的数据库?

如何选择合适的数据库?云数据库这个新大陆,并且对比了腾讯云数据库和自建数据库的性能,发现在不对自建数据库进行优化的前提下,云数据库相比较自建数据库还是有很大性能上的优势的。拿云数据库跟自建数据库比,不是开玩笑吗。确实,云数据库跟自建数据库在具体的硬件配置上没法完全统一,比如硬盘是普通磁盘还是SSD,网络带宽跟稳定性是否有差异等。于是萌生了一个想法,借了朋友的阿里云账号,来看看同样都是云数据库,同样配置的情况下,阿里云和腾讯云各自能给出多优秀的答卷。同样的,这次选择了阿里云数据库RDS的4核8G和8核32G版本,对比同样配置的腾讯云数据库,在不修改参数、不做配置改动的前提下,使用sysbench进行分别进行压力测试,详细配置如下表所示:对象 内存 CPU 硬盘 CVM操作系统 MySQL版本 sysbench版本阿里云 8G/32G 4核/8核 200G云盘 CentOS 7.2 64位 5.7 1.0.9腾讯云 8G/32G 4核/8核 200G云盘 CentOS 7.6 64位 5.7 1.0.9为了保证公平公正,需要控制大部分变量,包括用来进行测试的云主机的软硬件配置和网络带宽(均通过内网连接)。接下来就可以进行测试了,这次测试的数据依然是由sysbench自动生成,20张表x1000万行数据,线程数从20-500,每组测试两次取平均值,依然采用读写混合的模式比较QPS和TPS等性能参数指标。结果比想象的有趣,腾讯云在50线程以下的时候表现超出阿里云太多,维持在1.5-2倍,在100-200线程的时候,腾讯云的吞吐量维持在阿里云的1.2倍左右,但是阿里云对并发能力的支持效果好过腾讯云。在500线程的时候,阿里云依然能保持在46000+并发的水平,远高于腾讯云的28000+。看起来整体走势基本相同,腾讯云数据库的峰值表现在50-100线程之间,不过腾讯云数据库的性能力真心强啊,最好的时候甩了阿里云一倍以上。在32G内存的支持下,阿里云和腾讯云并发性能也有了提升,在500线程的时候阿里云一直保持恐怖的稳定,笔者特地又加测了750和1000线程,知道1000线程时阿里云仍保持70000+的QPS,不过腾讯云也不甘示弱,维持在60000的高水平。TPS(Transactions Per Second)表示服务器每秒处理的事务,因为跟事务挂钩,也是MySQL在OLTP场景一个非常重要的测量指标。整体来看,腾讯云的可用性高于阿里云,不过对多线程的支持上还是较阿里云稍差一些。此外还比较了延时等其他稳定性指标,腾讯云跟阿里云基本相同,因为篇幅问题就不在文章中贴图了,感兴趣的同学也可以自己动手测一下。另外要给阿里云开发小哥哥们提个建议,在测试过程中,使用阿里云主机的远程连接Web版输入长指令实在是太痛苦了,一旦输入就很难修改,往往要删掉重来。另外远程连接的反应很慢,而且屏幕一滚动就开始显示乱码,建议开发小哥哥们改进一下。不过这里要称赞一下阿里云数据库的性能监控,做的实在是高大上,看着很舒心.在测试的时候可以实时监控QPS/TPS,还有连接数和网络,比起看sysbench的平均数据信息量大多了,可以看到的性能的抖动。还可以翻历史记录。到这里才发现,原来云数据库卖的可不仅仅是数据库,还有数据管理、性能监控、系统告警等等功能呢,这下明白朋友公司为什么要用云数据库了。不说了,找老板聊聊去。最后还是要给出一个结论,通过的简单测试,发现MySQL的吞吐量性能(QPS/TPS),在较低线程数的时候腾讯云远好于阿里云,随着线程数增加,腾讯云数据库在性能上增长不及阿里云,但总体仍强于阿里云,在500线程+的战场,腾讯云落后于阿里云。不过随着硬件配置增长,二者对并发的支持也在不断增加,到8核32G内存往上,腾讯云跟阿里云在高并发场景下差距已经不大。把测试结果给朋友看,朋友翻了个白眼:“高并发还用什么MySQL,那时候应该用Redis啦。”什么?Redis,不熟啊,有没有云数据库Redis版给玩一下。一搜,嘿,还真有,下次来测一下Redis。

March 7, 2019 · 1 min · jiezi

MySQL性能基准测试对比:5.7 VS 8.0

本文由云+社区发表作者:数据库版权声明:本文由腾讯云数据库产品团队整理,页面原始内容来自于severalnines英文官网,若转载请注明出处。翻译目的在于传递更多全球最新数据库领域相关信息,并不意味着腾讯云数据库产品团队赞同其观点或证实其内容的真实性。如果其他媒体、网站或其他任何形式的法律实体和个人使用,必须经过著作权人合法书面授权并自负全部法律责任。不得擅自使用腾讯云数据库团队的名义进行转载,或盗用腾讯云数据库团队名义发布信息。原文链接:https://severalnines.com/blog…在Oracle MySQL团队的推动下,MySQL 8.0发生了巨大的变化和修改。物理文件已更改。例如,.frm, .TRG,.TRN和 .par 不再存在。添加了大量的新特性,如通用表表达式(Common Table Expressions CTE),窗口函数(Window Functions),不可见索引( Invisible Indexes),正则表达式(regexp) -MySQL8.0现在已经完全支持Unicode,且具有多字节安全特性。数据字典也发生了变化。它现在与一个事务性数据字典合并,该字典存储有关数据库对象的信息。与以前的版本不同,字典数据存储在元数据文件和非事务表中。安全性得到了改进,caching_sha2_password认证方式取代了之前的mysql_native_password认证方式,成为默认的身份验证方式。它提供了更强大的灵活性,而且也加强了安全性,即它要求必须使用安全连接或通过RSA密钥对实现的支持密码交换的未加密链接。有了MySQL 8.0提供的所有这些很出色的功能,以及进行的增强和改进,我们团队很有兴趣来了解下MySQL 8.0当前版本的性能情况。特别是考虑到我们针对MySQL 8.0.x设计的ClusterControl正在进行中(请继续保持关注)。这篇博文不会讨论MySQL8.0的特性,但打算将其性能与MySQL 5.7进行对比,看看它是如何改进的。Server Setup and Environment服务器设置和环境对于此基准测试,我打算使用基于AWS EC2最小配置的系统环境: · 实例类型:t2.xlarge实例· 存储:gp2(SSD存储,最小100 IOPS,最大16000 IOPS)· 虚拟CPU:4 · 内存:16GiB · MySQL5.7版本:MySQLCommunity Server (GPL) 5.7.24· MySQL8.0版本:MySQLCommunity Server - GPL 8.0.14在这个基准测试中,我也针对一些参数项的取值进行了配置,它们是:· innodb_max_dirty_pages_pct= 90 ##这是MySQL 8.0中的默认值。· innodb_max_dirty_pages_pct_lwm= 10 ##这是MySQL 8.0中的默认值· innodb_flush_neighbors=0· innodb_buffer_pool_instances=8· innodb_buffer_pool_size=8GiB这里对两个版本(MySQL 5.7和MySQL 8.0)其余参数项的配置是参照ClusterControl的my.cnf模板进行调优。此外,我在这里不使用MySQL8.0的新身份验证方式,即caching_sha2_password认证方式。替代的是在这两个版本中都使用mysql_native_password,外加配置innodb_dedicated_serve=OFF(默认值),因为innodb_dedicated_serve是MySQL 8.0的新特性。为了简化工作,我使用ClusterControl配置MySQL 5.7 Community version节点,然后把该节点从集群中的剔除,使其成为一个单独主机,并关闭集群控制主机,使MySQL 5.7节点处于休眠状态(不监控流量)。从技术上讲,MySQL 5.7和MySQL8.0都是休眠节点,在节点上没有活动连接通,因此它基本上是一个纯粹的基准测试。搜索关注“腾讯云数据库”官方微信,立得10元腾讯云无门槛代金券,体验移动端一键管理数据库,更有从初阶到高阶数据库实战教程等你来约!Commands and Scripts Used使用的命令和脚本对于此任务,sysbench用于测试和负载模拟这两个环境。以下测试中使用的命令和脚本:sb-prepare.sh #!/bin/bash host=$1#host192.168.10.110port=3306user=‘sysbench’password=‘MysqP@55w0rd’table_size=500000rate=20ps_mode=‘disable’sysbench/usr/share/sysbench/oltp_read_write.lua –db-driver=mysql –threads=1–max-requests=0 –time=3600 –mysql-host=$host –mysql-user=$user–mysql-password=$password –mysql-port=$port –tables=10 –report-interval=1–skip-trx=on –table-size=$table_size –rate=$rate –db-ps-mode=$ps_modepreparesb-run.sh#!/usr/bin/envbash2 host=$1port=3306user=“sysbench"password=“MysqP@55w0rd"table_size=100000tables=10rate=20ps_mode=‘disable’threads=1events=0time=5trx=100path=$PWD counter=1 echo “thread,cpu” >${host}-cpu.csv for i in 16 32 64 128 256 512 1024 2048; do threads=$i mysql -h $host -e"SHOW GLOBAL STATUS” >> $host-global-status.logtmpfile=$path/${host}-tmp${threads}touch $tmpfile/bin/bashcpu-checker.sh $tmpfile $host $threads & /usr/share/sysbench/oltp_read_write.lua–db-driver=mysql –events=$events –threads=$threads –time=$time–mysql-host=$host –mysql-user=$user –mysql-password=$password–mysql-port=$port –report-interval=1 –skip-trx=on –tables=$tables–table-size=$table_size –rate=$rate –delete_inserts=$trx –order_ranges=$trx–range_selects=on –range-size=$trx –simple_ranges=$trx –db-ps-mode=$ps_mode–mysql-ignore-errors=all run | tee -a $host-sysbench.log echo”${i},"cat ${tmpfile} | sort -nr | head -1 >> ${host}-cpu.csvunlink ${tmpfile} mysql -h $host -e"SHOW GLOBAL STATUS" >> $host-global-status.logdone python $path/innodb-ops-parser.py $host mysql -h $host -e “SHOW GLOBALVARIABLES” >> $host-global-vars.log因此,脚本只是准备sbtestschema并填充表和记录。然后,它使用/usr/share/sysbench/oltp_read_write.lua脚本执行读/写负载测试。该脚本转储全局状态和MySQL变量,收集CPU利用率,并解析由脚本innodb-ops-parser.py处理的InnoDB行操作。脚本根据基准测试期间收集的转储日志生成 .csv文件,我在这里使用Excel电子表格从 .csv文件生成图表。请检查 github中提交的代码。现在,让我们继续处理图表结果!InnoDB行操作基本上在这里,我只提取了InnoDB行操作,它执行查找(读取),删除,插入和更新。当线程数量增加时,MySQL 8.0明显优于MySQL 5.7!在这两个版本中都没有针对配置项进行任何个性化变更,只有我统一配置的参数项。所以这两个版本中的配置几乎都使用默认值。有趣的是,MySQL团队关于新版本中读写性能的声明,这些图表指出了性能的显著提高,特别是在高负载服务器上。想一下MySQL 5.7和MySQL 8.0在InnoDB行操作上的区别,确实存在有很大的不同,特别是当线程数增加的时候。MySQL 8.0表明,无论工作负载如何,它都能高效地运行。事务处理如上图所示,MySQL 8.0的结果趋势显示出其处理事务所需的时间的巨大变化。纵轴数值越低,代表性能越好,意味着处理事务的速度更快。处理的事务统计表(第二张表)还显示出这两个版本处理事务的数量没有差异。这意味着,两个版本处理的事务数量几乎相同,但它们的完成速度不同。虽然MySQL 5.7在较低的负载下可以大量事务,但是实际的负载,特别是在生产中,可能会更高——尤其是在最繁忙的时期。上面的图仍然显示的是两个版本能够处理的事务数量,只是将读和写分离开来。然而,图中实际上是存在异常值,而我没有将这些值包括在内,因为它们是这一小部分异常结果会扭曲图形。MySQL 8.0体现出一个很大的改进,特别是对于读取。表现在写操作的效率上,特别是对于高工作负载的服务器。在8.0版本中,影响MySQL读取性能的重要新增支持是:可以按降序(或正向索引扫描)创建索引的能力。以前的版本只有升序或反向索引扫描,如果需要降序,MySQL必须执行filesort(如果需要filesort,需要检查max_length_for_sort_data的值)。当最有效的扫描顺序混合某些列的升序和其他列的降序时,降序索引还使优化器可以使用多列索引。有关详细信息,请参见此处。CPU资源在此基准测试中,我决定测试一些硬件资源,尤其是CPU利用率。让我先解释一下如何在基准测试中获取CPU使用率。在对数据库进行基准测试时,sysbench测试结果中不包括在此过程中使用的硬件资源的统计信息。因此,我所做的是通过创建文件的方式来创建标识,通过SSH连接到目标主机,然后用Linux命令“top”收集数据并在测试结束前进行解析,然后再次收集。然后分析出mysqld进程占用最大的CPU使用量,最后删除该标识文件。你可以查看我在github上的代码。让我们再次讨论图表结果,似乎表明MySQL 8.0消耗了大量的CPU,超过MySQL 5.7。然而,MySQL 8.0可能必须消耗额外的CPU在新的变量配置上。例如,这些变量可能会影响您的MySQL 8.0:· innodb_log_spin_cpu_abs_lwm = 80· innodb_log_spin_cpu_pct_hwm = 50· innodb_log_wait_for_flush_spin_hwm = 400· innodb_parallel_read_threads = 4在此基准测试中,具有默认值的变量将保留其默认值。由于MySQL 8.0重新设计了InnoDB写入REDO日志的方式(这是一个改进),前三个变量可配置处理重做日志的使用的CPU资源。例如,变量innodb_log_spin_cpu_pct_hwm具有CPU亲和性,这意味着如果mysqld仅绑定到4个内核,它将忽略其他CPU内核。对于并行读取线程,在MySQL 8.0中添加了一个新变量,您可以调整要使用的线程数。然而,我没有深入研究这个问题。可以通过利用MySQL8.0提供的特性来提高性能。结论MySQL 8.0中有许多改进。基准测试结果显示,与MySQL 5.7相比,MySQL 8.0不仅在处理读负载时,而且在读写混合的高负载下的性能都取得了令人瞩目的进步。搜索关注“腾讯云数据库”官方微信,立得10元腾讯云无门槛代金券,体验移动端一键管理数据库,更有从初阶到高阶数据库实战教程等你来约!再来看MySQL 8.0的新特性,看起来它不仅利用了最新的软件技术(如Memcached的改进,远程管理以获得更好的DevOps工作性能等),还有硬件。例如,用UTF8MB4替换latin1作为默认字符编码。这意味着它需要更多的磁盘空间,因为UTF8在非US-ASCII字符上需要2个字节。虽然此基准测试没有利用使用caching_sha2_password的新身份验证方法,但它是否使用加密不会影响性能。一旦经过身份验证,它就会存储在缓存中,这意味着身份验证只进行一次。因此,如果您在客户端只使用一个用户,则不会出现问题,并且比以前的版本更安全。由于MySQL利用最新的硬件和软件,因此会更改其默认变量。你可以在这里阅读更多细节。总的来说,MySQL 8.0的性能已经远超过MySQL 5.7了。此文已由腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号 ...

March 7, 2019 · 1 min · jiezi

mysql三种安装方式之-rpm方式安装

查看安装以及卸载# 查看rpm -qa | grep mysql# 卸载yum -y remove mysql-libs-5.1.66-2.el6_3.x86_641.centos7需要先卸载mariadb查看命令rpm -qa | grep mariadb安装包资源地址mysql下载地址# 下载资源名称mysql-5.7.22-1.el7.x86_64.rpm-bundle.tar解压资源tar -xvf mysql-5.7.22-1.el7.x86_64.rpm-bundle.tar 获取rpm包mysql-community-common-5.7.19-1.el6.x86_64.rpmmysql-community-client-5.7.19-1.el6.x86_64.rpmmysql-community-server-5.7.19-1.el6.x86_64.rpmmysql-community-libs-5.7.19-1.el6.x86_64.rpm安装# 安装顺序# common –> libs –> clients –> server# 安装命令rpm -ivh mysql-community-common-5.7.19-1.el6.x86_64.rpm mysql-community-libs-5.7.19-1.el6.x86_64.rpm mysql-community-client-5.7.19-1.el6.x86_64.rpm mysql-community-server-5.7.19-1.el6.x86_64.rpm可能需要依赖包libaio安装命令yum -y install libaio初始化mysqld –initialize –user=mysql启动service mysqld start登陆# 查看初始密码cat /var/logs/mysql.log#或者grep ‘password’ /var/logs/mysql.log -n -B 5# 登陆mysql -uroot -p'123456’# 修改初始密码set PASSWORD=PASSWORD(‘密码’);授权远程链接### 授权192.168.0.1可以远程链接*[所有数据库].[所有表] 账号:root 密码123456grant all privileges on . to ‘root’@‘192.168.0.1’ identified by ‘123456’;### 授权所有ipgrant all privileges on . to ‘root’@’%’ identified by ‘123456’;### 以上.*表示[数据库].[表]### 使授权立刻生效flush privileges; ...

March 7, 2019 · 1 min · jiezi

mysql主从读写分离实战

环境mysql-5.7.19Centos7master: 192.168.111.64slave: 192.168.111.66主从配置master配置## GTIDserver_id = 100 # 服务器id,从库最好大于主库,注意要唯一gtid_mode = on #开启gtid模式enforce_gtid_consistency = on #强制gtid一致性,开启后对特定的create table不被支持log-bin = mysql-bin #开启二进制日志binlog_format = row #默认为mixed混合模式,更改成row复制,为了数据一致性log-slave-updates = 1 #从库binlog才会记录主库同步的操作日志skip_slave_start=1 #跳过slave复制线程slave配置## 设置server_id,注意要唯一server_id=101log-bin = mysql-binbinlog_format = rowlog-slave-updates = 1gtid_mode = onenforce_gtid_consistency = onskip_slave_start=1# 从库设置为只读read_only=on重启数据库service mysqld restart创建用户# 用户从库同步GRANT SELECT,RELOAD,SHOW DATABASES,LOCK TABLES,EVENT,REPLICATION CLIENT ON . TO ‘repl_user’@‘192.168.111.64’ IDENTIFIED BY ‘123456’;# 创建只读用户,用户从库读取数据GRANT Select ON . TO ‘reader’@‘192.168.111.%’ IDENTIFIED BY “123456”;数据导出# 在192.169.111.66上操作nohup mysqldump –single-transaction –master-data=2 –triggers –routines –all-databases –events -h192.168.111.64 -u’root’ -p’password’ > /home/backup.sql &因为数据量较大(导出大概30G),使用nohup [commend] & 使用后台导出,防止导出时间过长连接断开,可以使用jobs -l命令查看数据导出也可以直接在主库服务器上导出,然后通过scp backup.sql 192.168.111.66:/home命令传到从库服务器从库数据导入连接mysql导入登陆mysql# 连接到数据库mysql -uroot -p'123456’;执行导入sql命令source /home/backup.sql命令直接导入nohup mysql -uroot -p’123456’ < /home/backup.sql &推荐第二种方式,尤其是数据量较大的情况下在192.168.111.66上设置主从连接连接mysqlmysql -uroot -p'123456’设置# 设置CHANGE MASTER TO MASTER_HOST=‘192.168.111.64’, MASTER_PORT=3306,MASTER_USER=‘repl_user’,MASTER_PASSWORD=‘123456’,MASTER_AUTO_POSITION=1;# 开启主从start slave;# 查看主从状态show slave status \G;常用命令:停止同步:stop slave重置从库状态:reset slave重置主库状态:reset master ...

March 7, 2019 · 1 min · jiezi

推荐一下学习Redis和Mysql的两个小册

买了就是精通了

March 7, 2019 · 1 min · jiezi

掌握 MySQL 这 19 个骚操作,效率至少提高3倍

本文我们来谈谈项目中常用的MySQL优化方法,共19条,利用好这19条方法,会让你的效率提升至少3倍。1、EXPLAIN做MySQL优化,我们要善用EXPLAIN查看SQL执行计划。下面来个简单的示例,标注(1、2、3、4、5)我们要重点关注的数据:type列,连接类型。一个好的SQL语句至少要达到range级别。杜绝出现all级别。key列,使用到的索引名。如果没有选择索引,值是NULL。可以采取强制索引方式。key_len列,索引长度。rows列,扫描行数。该值是个预估值。extra列,详细说明。注意,常见的不太友好的值,如下:Using filesort,Using temporary。2、SQL语句中IN包含的值不应过多MySQL对于IN做了相应的优化,即将IN中的常量全部存储在一个数组里面,而且这个数组是排好序的。但是如果数值较多,产生的消耗也是比较大的。再例如:select id from t where num in(1,2,3) 对于连续的数值,能用between就不要用in了;再或者使用连接来替换。3、SELECT语句务必指明字段名称SELECT增加很多不必要的消耗(CPU、IO、内存、网络带宽);增加了使用覆盖索引的可能性;当表结构发生改变时,前断也需要更新。所以要求直接在select后面接上字段名。4、当只需要一条数据的时候,使用limit 1这是为了使EXPLAIN中type列达到const类型5、如果排序字段没有用到索引,就尽量少排序6、如果限制条件中其他字段没有索引,尽量少用oror两边的字段中,如果有一个不是索引字段,而其他条件也不是索引字段,会造成该查询不走索引的情况。很多时候使用union all或者是union(必要的时候)的方式来代替“or”会得到更好的效果。7、尽量用union all代替unionunion和union all的差异主要是前者需要将结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加大量的CPU运算,加大资源消耗及延迟。当然,union all的前提条件是两个结果集没有重复数据。8、不使用ORDER BY RAND()select id from dynamic order by rand() limit 1000;上面的SQL语句,可优化为:select id from dynamic t1 join (select rand() * (select max(id) from dynamic) as nid) t2 on t1.id > t2.nidlimit 1000;9、区分in和exists、not in和not existsselect * from 表A where id in (select id from 表B)上面SQL语句相当于select from 表A where exists(select from 表B where 表B.id=表A.id)区分in和exists主要是造成了驱动顺序的改变(这是性能变化的关键),如果是exists,那么以外层表为驱动表,先被访问,如果是IN,那么先执行子查询。所以IN适合于外表大而内表小的情况;EXISTS适合于外表小而内表大的情况。关于not in和not exists,推荐使用not exists,不仅仅是效率问题,not in可能存在逻辑问题。如何高效的写出一个替代not exists的SQL语句?原SQL语句:select colname … from A表 where a.id not in (select b.id from B表)高效的SQL语句:select colname … from A表 Left join B表 on where a.id = b.id where b.id is null取出的结果集如下图表示,A表不在B表中的数据:10、使用合理的分页方式以提高分页的效率select id,name from product limit 866613, 20使用上述SQL语句做分页的时候,可能有人会发现,随着表数据量的增加,直接使用limit分页查询会越来越慢。优化的方法如下:可以取前一页的最大行数的id,然后根据这个最大的id来限制下一页的起点。比如此列中,上一页最大的id是866612。SQL可以采用如下的写法:select id,name from product where id> 866612 limit 2011、分段查询在一些用户选择页面中,可能一些用户选择的时间范围过大,造成查询缓慢。主要的原因是扫描行数过多。这个时候可以通过程序,分段进行查询,循环遍历,将结果合并处理进行展示。如下图这个SQL语句,扫描的行数成百万级以上的时候就可以使用分段查询:12、避免在where子句中对字段进行null值判断对于null的判断会导致引擎放弃使用索引而进行全表扫描。13、不建议使用%前缀模糊查询例如LIKE“%name”或者LIKE“%name%”,这种查询会导致索引失效而进行全表扫描。但是可以使用LIKE “name%”。那如何查询%name%?如下图所示,虽然给secret字段添加了索引,但在explain结果并没有使用:那么如何解决这个问题呢,答案:使用全文索引。在我们查询中经常会用到select id,fnum,fdst from dynamic_201606 where user_name like ‘%zhangsan%’; 。这样的语句,普通索引是无法满足查询需求的。庆幸的是在MySQL中,有全文索引来帮助我们。创建全文索引的SQL语法是:ALTER TABLE dynamic_201606 ADD FULLTEXT INDEX idx_user_name (user_name);使用全文索引的SQL语句是:select id,fnum,fdst from dynamic_201606 where match(user_name) against(‘zhangsan’ in boolean mode);注意:在需要创建全文索引之前,请联系DBA确定能否创建。同时需要注意的是查询语句的写法与普通索引的区别。14、避免在where子句中对字段进行表达式操作比如:select user_id,user_project from user_base where age2=36;中对字段就行了算术运算,这会造成引擎放弃使用索引,建议改成:select user_id,user_project from user_base where age=36/2;15、避免隐式类型转换where子句中出现column字段的类型和传入的参数类型不一致的时候发生的类型转换,建议先确定where中的参数类型。16、对于联合索引来说,要遵守最左前缀法则举列来说索引含有字段id、name、school,可以直接用id字段,也可以id、name这样的顺序,但是name;school都无法使用这个索引。所以在创建联合索引的时候一定要注意索引字段顺序,常用的查询字段放在最前面。17、必要时可以使用force index来强制查询走某个索引有的时候MySQL优化器采取它认为合适的索引来检索SQL语句,但是可能它所采用的索引并不是我们想要的。这时就可以采用forceindex来强制优化器使用我们制定的索引。18、注意范围查询语句对于联合索引来说,如果存在范围查询,比如between、>、<等条件时,会造成后面的索引字段失效。19、关于JOIN优化LEFT JOIN A表为驱动表,INNER JOIN MySQL会自动找出那个数据少的表作用驱动表,RIGHT JOIN B表为驱动表。注意:1)MySQL中没有full join,可以用以下方式来解决:select from A left join B on B.name = A.namewhere B.name is nullunion allselect from B;2)尽量使用inner join,避免left join:参与联合查询的表至少为2张表,一般都存在大小之分。如果连接方式是inner join,在没有其他过滤条件的情况下MySQL会自动选择小表作为驱动表,但是left join在驱动表的选择上遵循的是左边驱动右边的原则,即left join左边的表名为驱动表。3)合理利用索引:被驱动表的索引字段作为on的限制字段。4)利用小表去驱动大表:从原理图能够直观的看出如果能够减少驱动表的话,减少嵌套循环中的循环次数,以减少 IO总量及CPU运算的次数。5)巧用STRAIGHT_JOIN:inner join是由MySQL选择驱动表,但是有些特殊情况需要选择另个表作为驱动表,比如有group by、order by等「Using filesort」、「Using temporary」时。STRAIGHT_JOIN来强制连接顺序,在STRAIGHT_JOIN左边的表名就是驱动表,右边则是被驱动表。在使用STRAIGHT_JOIN有个前提条件是该查询是内连接,也就是inner join。其他链接不推荐使用STRAIGHT_JOIN,否则可能造成查询结果不准确。这个方式有时能减少3倍的时间。以上19条MySQL优化方法希望对大家有所帮助呐,如果觉得有帮助的话,欢迎转发分享哦 ...

March 6, 2019 · 1 min · jiezi

Navicat for MySQL破解

1、安装Navicat软件按步骤一步步进行2、安装成功之后进行破解点击PathNavicat.exe运行找到安装好的Navicat安装路径选中navicat.exe文件打开,即可激活成功

March 6, 2019 · 1 min · jiezi

Mysql主从复制配置及介绍

一,MySQL安装配置1.下载 mysql 源安装包$ curl -LO http://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm2.安装 mysql 源$ sudo yum localinstall mysql57-community-release-el7-11.noarch.rpm注意:执行过程中如果报错如下则通过修改python版本, 修改yum配置文件,将python版本指向以前的旧版本# 修改yum配置文件,将python版本指向以前的旧版本# vi /usr/bin/yum#!/usr/bin/python2.7# 修改urlgrabber-ext-down文件,更改python版本# vi /usr/libexec/urlgrabber-ext-down#!/usr/bin/python2.7检查 yum 源是否安装成功$ sudo yum repolist enabled|grep “mysql.-community.“mysql-connectors-community/x86_64 MySQL Connectors Community 95mysql-tools-community/x86_64 MySQL Tools Community 84mysql57-community/x86_64 MySQL 5.7 Community Server 3273.安装server$ sudo yum install mysql-community-server4.启动mysql服务安装服务 $ sudo systemctl enable mysqld启动服务$ sudo systemctl start mysqld查看服务状态 $ sudo systemctl status mysqld ● mysqld.service - MySQL Server Loaded: loaded (/usr/lib/systemd/system/mysqld.service; enabled; vendor preset: disabled) Active: active (running) since 一 2019-03-04 16:36:09 CST; 17s ago Docs: man:mysqld(8) http://dev.mysql.com/doc/refman/en/using-systemd.html Process: 3126 ExecStart=/usr/sbin/mysqld –daemonize –pid-file=/var/run/mysqld/mysqld.pid $MYSQLD_OPTS (code=exited, status=0/SUCCESS) Process: 3053 ExecStartPre=/usr/bin/mysqld_pre_systemd (code=exited, status=0/SUCCESS) Main PID: 3129 (mysqld) Tasks: 27 CGroup: /system.slice/mysqld.service └─3129 /usr/sbin/mysqld –daemonize –pid-file=/var/run/mysqld/mysqld.pid 3月 04 16:36:02 lvmama02 systemd[1]: Starting MySQL Server… 3月 04 16:36:09 lvmama02 systemd[1]: Started MySQL Server.5.修改默认密码MySQL 5.7启动后,在 /var/log/mysqld.log 文件中给 root 生成了一个默认密码。通过下面的方式找到 root 默认密码,然后登录 mysql 进行修改 $ grep ‘password’ /var/log/mysqld.log 2019-03-04T08:36:04.854935Z 1 [Note] A temporary password is generated for root@localhost: pdU7wuS/YhSG登录mysql修改密码 #注意:MySQL 5.7 默认安装了密码安全检查插件(validate_password),默认密码检查策略要求密码必须包 #含:大小写字母、数字和特殊符号,并且长度不能少于 8 位。 $ ALTER USER ‘root’@’localhost’ IDENTIFIED BY ‘xxxxxx’ 注意:测试环境我们可以设置禁用密码校验,毕竟密码太难记了,可以通过如下方式禁用:$ sudo vi /etc/my.cnf# 添加如下配置# 禁用密码校验策略validate_password = off# 重新启动mysql 重新修改密码mysql> ALTER USER ‘root’@’localhost’ IDENTIFIED BY ‘root'6.添加远程登录用户# MySQL 默认只允许 root 帐户在本地登录,如果要在其它机器上连接 MySQL,必须修改 root 允许远程连接,# 或者添加一个允许远程连接的帐户,为了安全起见,本例添加一个新的帐户:mysql> GRANT ALL PRIVILEGES ON . TO ‘admin’@’%’ IDENTIFIED BY ‘admin’ WITH GRANT OPTION;Query OK, 0 rows affected, 1 warning (0.00 sec) 7.配置默认编码为 utf8MySQL 默认为 latin1, 一般修改为 UTF-8$ vi /etc/my.cnf[mysqld]# 在myslqd下添加如下键值对character_set_server=utf8init_connect=‘SET NAMES utf8’重启测试查看字符集mysql> show variables like ‘character%’+————————–+—————————-+| Variable_name | Value |+————————–+—————————-+| character_set_client | utf8 || character_set_connection | utf8 || character_set_database | utf8 || character_set_filesystem | binary || character_set_results | utf8 || character_set_server | utf8 || character_set_system | utf8 || character_sets_dir | /usr/share/mysql/charsets/ |+————————–+—————————-+8.开启端口 (如果防火墙没关闭则需要做如下操作)$ sudo firewall-cmd –zone=public –add-port=3306/tcp –permanentFirewallD is not running$ sudo firewall-cmd –reloadFirewallD is not running二,MySQL主从配置配置主库1,主库 mysql11 上开启设置# 添加如下配置# 参数必须唯一, 本例主库设置为 11 ,从库设置为 12server_id=101log_bin=/var/log/mysql/mysql-bin2,二进制日志文件的目录不是默认的,需要新建一下# 创建文件夹$ sudo mkdir /var/log/mysql# 分配权限$ sudo chown mysql:mysql /var/log/mysql3,重启主库的 MySQL 服务$ sudo systemctl restart mysqld4,测试mysql> show master status+——————+———-+————–+——————+——————-+| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |+——————+———-+————–+——————+——————-+| mysql-bin.000001 | 154 | | | |+——————+———-+————–+——————+——————-+从结果看到, File 字段有值,并且前面与配置文件一致,说明配置正确。后面的 000001 说明是第一次,如果 MySQL 重启服务,这个值会递增为 mysql-bin.000002配置从库1,从库 mysql11 上配置# 从库配置server_id=12log_bin=/var/log/mysql/mysql-bin.logrelay_log=/var/log/mysql/mysql-relay-bin.log#库设为只读的read_only=12,从库设置的二进制日志文件的目录不是默认的,需要新建一下$ sudo mkdir /var/log/mysql# 分配权限$ sudo chown mysql:mysql /var/log/mysql3,重启从库的 MySQL 服务$ sudo systemctl restart mysqld4,设置从库的复制参数mysql> CHANGE MASTER TO MASTER_HOST=‘192.168.187.11’, -> MASTER_USER=‘admin’, -> MASTER_PASSWORD=‘admin’, #此选项初始化设置时需要跟主库中的一致。设置好后,如果主 #库发生重启等,不需再次设置,从库会跟着更新 -> MASTER_LOG_FILE=‘mysql-bin.000001’, # master Position的值 -> MASTER_LOG_POS=154; 5.查看从库状态mysql> show slave status \G*************************** 1. row *************************** Slave_IO_State: Master_Host: 192.168.187.11 Master_User: admin Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000001 Read_Master_Log_Pos: 154 Relay_Log_File: mysql-relay-bin.000001 Relay_Log_Pos: 4 Relay_Master_Log_File: mysql-bin.000001 Slave_IO_Running: No Slave_SQL_Running: No*************************** 略 6,从 Slave_IO_State, Slave_IO_Running: No, Slave_SQL_Running: No 表明当前从库的复制服务还没有启动,启动从库mysql> start slave;Query OK, 0 rows affected (0.03 sec)再次查看show slave status G 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.187.11 Master_User: admin Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000001 Read_Master_Log_Pos: 154 Relay_Log_File: mysql-relay-bin.000002 Relay_Log_Pos: 320 Relay_Master_Log_File: mysql-bin.000001 Slave_IO_Running: Yes Slave_SQL_Running: Yes*************************** 略 ***************************7,测试在主库中新建一个库,查看从库是否同步复制主库数据# ———mater——–mysql> show databases;+——————–+| Database |+——————–+| information_schema || mysql || performance_schema || sys |+——————–+4 rows in set (0.02 sec)mysql> create database test;Query OK, 1 row affected (0.02 sec)# ———slave——–mysql> show databases;+——————–+| Database |+——————–+| information_schema || mysql || performance_schema || sys || test |+——————–+5 rows in set (0.01 sec)三,MySQL主从配置原理1.mysql支持的复制格式基于语句复制(STATEMENT)(优点)基于statement复制的优点很明显,简单的记录执行语句同步到从库执行同样的语句,占用磁盘空间小,网络传输快,并且通过mysqlbinlog工具容易读懂其中的内容 。(缺点)并不是所有语句都能复制的比如:insert into table1(create_time) values(now()),取的是数据当前时间,不同的数据可能时间不一致,另外像存储过程和触发器也可能存在问题。基于行复制(ROW)(优点)从MySQL5.1开始支持基于行的复制,最大的好处是可以正确地复制每一行数据。一些语句可以被更加有效地复制,另外就是几乎没有基于行的复制模式无法处理的场景,对于所有的SQL构造、触发器、存储过程等都能正确执行。(缺点)主要的缺点就是二进制日志可能会很大,比如:update table1 set name=‘admin’ where id<1000,基于行复制可能需要复制1000条记录,而基于语句复制只有一条语句,另外一个缺点就是不直观,所以,你不能使用mysqlbinlog来查看二进制日志。混合类型的复制(MIXED)混合复制是借用语句复制和行复制的有点进行整合,MIXED也是MySQL默认使用的二进制日志记录方式,但MIXED格式默认采用基于语句的复制,一旦发现基于语句的无法精确的复制时,就会采用基于行的复制。比如用到UUID()、USER()、CURRENT_USER()、ROW_COUNT()等无法确定的函数。2.mysql主从复制作用数据分布。主从分摊负载。高可用性和故障切换。数据备份。利用从服务器做查询。3.mysql主从复制原理binlog Events我们知道binlog日志用于记录所有对MySQL的操作的变更,而这每一个变更都会对应的事件,也就是Event。index文件记录了所有的binlog位置 每个binlog会有heade, event,rotate三个event,binlog的结构如下。常见event如下:Format_desc:一个全新的binlog日志文件event信息Rotate :日志分割时结束event。Table_map:表,列等元数据的event。Query:查询,就是DDL这类的Event,如果binlog格式为STATEMENT格式,增删改都属于Qeury event。Write_rows:Binlog为ROW格式时的插入event。Update_rows:Binlog为ROW格式时的更新event。Delete_rows:Binlog为ROW格式时的删除event。我们也可以通过binlog 看到这些事件,通过mysql提供的工具查看binlog日志,如下:主从复制流程当从库发出 start slave命令时,从库会创建I/O线程和SQL thread(SQL线程)从库的IO和主库的dump线程建立连接 并监听binlog二进制日志事件从库根据change master to 语句提供的file名和position号,IO线程向主库发起binlog的请求主库dump线程根据从库的请求,将本地binlog以events的方式发给从库IO线程从库IO线程接收binlog evnets,并存放到本地relay-log中,传送过来的信息,会记录到master.info中。从库SQL线程应用relay-log,并且把应用过的记录到relay-log.info,默认情况下,已经应用过的relay会自动被清理purge。四,MySQL只从配置缺陷MySQL的复制(replication)功能配置简单,深受开发人员的喜欢,基于复制的读写分离方案也非常流行。而MySQL数据库高可用大多也是基于复制技术,但是MySQL复制本身依然存在部分缺陷,最为主要的问题如下: 数据丢失问题(consistency)数据同步延迟问题(delay)扩展性问题(scalability)从MySQL 5.7的lossless semi-sync replication已经解决了主从数据丢失的问题,MySQL 5.7的multi-thread slave也很大程度地解决了数据同步延迟的问题,MySQL 5.7的Group replication也很大程度地解决了扩展性问题。另外,MySQL 5.7.22 backlog了MySQL 8.0中的基于WriteSet的并行复制,可以说完全解决了主从数据延迟的问题。可以看出,MySQL正在朝着一个非常好的方向发展 ...

March 6, 2019 · 3 min · jiezi

zabbix 4.0.3 use docker-compose deploy

CentOS 7 使用 docker-compose 部署zabbix 4.0.3docker-compose.yaml 配置文件如下:version: ‘3.1’services: db: image: mysql command: –default-authentication-plugin=mysql_native_password –character-set-server=utf8mb4 –collation-server=utf8mb4_unicode_ci –default-time-zone=’+08:00’ restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: “MysqlPasswd” MYSQL_ALLOW_EMPTY_PASSWORD: “no” ports: - 9306:3306 volumes: - /data/dockerdata/db/data:/var/lib/mysql - /data/dockerdata/db/conf:/etc/mysql/conf.d - /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime server: image: zabbix/zabbix-server-mysql:centos-4.0.3 restart: unless-stopped environment: DB_SERVER_HOST: db MYSQL_USER: root MYSQL_PASSWORD: MysqlPasswd ZBX_TIMEOUT: 30 ports: - 9051:10051 - 8444:10051 volumes: - /data/dockerdata/server/alertscripts:/usr/lib/zabbix/alertscripts - /data/dockerdata/server/externalscripts:/usr/lib/zabbix/externalscripts - /data/dockerdata/server/modules:/var/lib/zabbix/modules - /data/dockerdata/server/enc:/var/lib/zabbix/enc - /data/dockerdata/server/ssh_keys:/var/lib/zabbix/ssh_keys - /data/dockerdata/server/ssl/certs:/var/lib/zabbix/ssl/certs - /data/dockerdata/server/ssl/keys:/var/lib/zabbix/ssl/keys - /data/dockerdata/server/ssl/ssl_ca:/var/lib/zabbix/ssl/ssl_ca - /data/dockerdata/server/snmptraps:/var/lib/zabbix/snmptraps - /data/dockerdata/server/mibs:/var/lib/zabbix/mibs - /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime web: image: zabbix/zabbix-web-nginx-mysql:centos-4.0.3 restart: unless-stopped environment: DB_SERVER_HOST: db MYSQL_USER: root MYSQL_PASSWORD: MysqlPasswd ZBX_SERVER_HOST: server PHP_TZ: “Asia/Shanghai” ZBX_SERVER_NAME: aws ports: - 8081:80 volumes: - /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime ...

March 6, 2019 · 1 min · jiezi

Ubuntu18.04搭建LNMP

在csdn上发布了一遍,然后又在这里发布一遍,哈哈,以下是csdn地址:https://blog.csdn.net/sinat_3… 最近闲来无事,遂下载了vmware,尝试搭建下lnmp,之前有试过搭建宝塔,lamp等,但最后结果都不怎么好,这次再次尝试搭建linux下的php开发环境,借鉴了很多网友的文章,历经千辛,搭建成功,遂将过程总结总结,分享出来。 这里是主要参考的两篇博客:快速搭建lnmp https://www.cnblogs.com/zhangbobo/p/9597446.html修改数据库密码 https://www.cnblogs.com/super-zhangkun/p/9435974.html开始 更新源 sudo apt-get update安装nginx sudo apt-get install nginx安装mysql sudo apt-get install mysql-server mysql-client这里会有个问题,有的文章说,安装过程会提示设置密码,然而。。。,这样,你将无法登录mysql,那么就这样放弃了吗?如下: cd /etc/mysql sudo cat debian.cnf 可以看到账号密码 使用这个账号密码登录mysql mysql -u :user -p :password use mysql; update user set authentication_string=PASSWORD(“密码”) where user=‘root’; update user set plugin=“mysql_native_password”; flush privileges; quit; 重启mysql /etc/init.d/mysql restart OJBK 什么?找不到debian?可以试一下find命令或者查看mysql安装位置find / debianps -ef|grep mysql 还是找不到?额,我也不知道。安装PHP 我安装的php7.2,安装前把已安装的php版本禁掉,怎么禁?百度或谷歌!!!至于想安装其他版本的PHP?看完我下面的命令,只要不是白痴应该都知道。 安装 apt install php7.2-cli 下载需要的扩展 apt install php7.2-fpm 将最后的fpm改成需要的扩展就行了 配置fpm cd /etc/php/7.2/fpm/pool.d sudo vim www.conf 找到 listen = /run/php/php7.2-fpm.sock 大概在36行,取消注释,或者修改为 listen = 127.0.0.1:9000 重启fpm sudo service php7.2-fpm restart 测试php-fpm sudo php-fpm7.2 -t // 出现successful就行了配置nginx 有点懒,直接把我写在subline里面的 Ctrl + C出来吧cd /etc/nginx/sites-enabled sudo vim default 将第一个service的 #location ~ .php$ { #} 前面的#去掉(去掉这两个就行了,其余的需要用到时再去掉) 添加( 或修改:去掉# )fastcgi_pass unix:/run/php/php7.2-fpm.sock;(与之前在fpm里面的listen一致) 坑:这时重启nginx:sudo service nginx restart 然后在 /var/www/html 里面添加 phpinfo.php 输出phpinfo();页面一片空白 做以下修改: 在fastcgi_pass 后面添加: fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_script_name; include fastcgi_params; 重启nginx 在此步骤中,如果之前步骤没出错,但是重启nginx失败,请仔细检查自己的配置文件是否缺少’;’,或者 配置的开闭 {} 之前的 # 号去除的不对 再者检查php-fpm监听路径是否正确,如果是127.0.0.1:9000,还可以通过 netstat -tlnp 检查运行情况写在最后 以上就是我搭建lnmp的全过程,比我之前搭建的宝塔和lamp要顺利点,但也是有点曲折的,凡事还是要自己亲身体验体验,才能理解里面的艰辛。这篇教程,不说多么详细吧,但是还是可以避免一些坑的产生!!!如果看了之后还是搭建不成功,我建议多看看别人的博客,了解了解linux的基础命令再来重新搭建一次,或者,使用windows和mac!最后,Enjoy It ! ...

March 6, 2019 · 1 min · jiezi

PHP面试MySQL数据库的索引

你好,是我琉忆,PHP程序员面试笔试系列图书的作者。本周(2019.3.4至3.8)的一三五更新的文章如下:周一:PHP面试MySQL数据库的基础知识周三:PHP面试MySQL数据库的索引周五:PHP面试MySQL数据库的面试真题自己整理了一篇“索引有哪些优缺点和使用原则?”的文章,关注公众号:“琉忆编程库”,回复:“索引”,我发给你。以下内容部分来自《PHP程序员面试笔试宝典》如需转载请注明出处。一、什么是索引?索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。它主要提供指向存储在表的指定列中的数据值的指针,然后根据指定的排序顺序对这些指针排序。数据库使用索引以找到特定值,然后顺指针找到包含该值的行。这样可以使对应于表的SQL语句执行得更快,可快速访问数据库表中的特定信息。索引的特点如下:①可以提高数据库的检索速度;②降低了数据库插入、修改、删除等维护任务的速度;③可以直接或间接创建;④只能创建在表上,不能创建在视图上;⑤使用查询处理器执行SQL语句时,一个表上,一次只能使用一个索引;⑥可以在优化隐藏中使用索引。索引的分类和使用如下:1.直接创建索引和间接创建索引直接创建索引:CREATE INDEX mycolumn_index ON mytable (myclumn)。间接创建索引:定义主键约束或者唯一性键约束,可以间接创建索引。2.普通索引和唯一性索引普通索引:CREATE INDEX mycolumn_index ON mytable (myclumn)。唯一性索引:保证在索引列中的全部数据是唯一的,对聚簇索引和非聚簇索引都可以使用。CREATE UNIQUE COUSTERED INDEX myclumn_cindex ON mytable(mycolumn)3.单个索引和复合索引单个索引:即非复合索引。复合索引:又称为组合索引,在索引建立语句中同时包含多个字段名,最多16个字段。CREATE INDEX name_index ON username(firstname,lastname)4.聚簇索引和非聚簇索引(聚集索引,群集索引)聚簇索引:物理索引,与基表的物理顺序相同,数据值的顺序总是按照顺序排列。CREATE CLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn) WITHALLOW_DUP_ROW(允许有重复记录的聚簇索引)非聚簇索引:CREATE UNCLUSTERED INDEX mycolumn_cindex ON mytable(mycolumn)。二、索引的原理索引的目的在于提高查询效率,与我们查阅图书所用的目录是一个道理:先定位到章,然后定位到该章下的一个小节,然后找到页数。相似的例子还有:查字典,查火车车次,飞机航班等本质都是:通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,我们可以总是用同一种查找方式来锁定数据。数据库也是一样,但显然要复杂的多,因为不仅面临着等值查询,还有范围查询(>、<、between、in)、模糊查询(like)、并集查询(or)等等。数据库应该选择怎么样的方式来应对所有的问题呢?我们回想字典的例子,能不能把数据分成段,然后分段查询呢?最简单的如果1000条数据,1到100分成第一段,101到200分成第二段,201到300分成第三段……这样查第250条数据,只要找第三段就可以了,一下子去除了90%的无效数据。但如果是1千万的记录呢,分成几段比较好?稍有算法基础的同学会想到搜索树,其平均复杂度是lgN,具有不错的查询性能。但这里我们忽略了一个关键的问题,复杂度模型是基于每次相同的操作成本来考虑的。而数据库实现比较复杂,一方面数据是保存在磁盘上的,另外一方面为了提高性能,每次又可以把部分数据读入内存来计算,因为我们知道访问磁盘的成本大概是访问内存的十万倍左右,所以简单的搜索树难以满足复杂的应用场景。自己整理了一篇“索引有哪些优缺点和使用原则?”的文章,关注公众号:“琉忆编程库”,回复:“索引”,我发给你。三、索引的数据结构任何一种数据结构都不是凭空产生的,一定会有它的背景和使用场景,我们现在总结一下,我们需要这种数据结构能够做些什么,其实很简单,那就是:每次查找数据时把磁盘IO次数控制在一个很小的数量级,最好是常数数量级。那么我们就想到如果一个高度可控的多路搜索树是否能满足需求呢?就这样,b+树应运而生。如上图,是一颗b+树,关于b+树的定义可以参见B+树,这里只说一些重点,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1、P2、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。b+树的查找过程如图所示,如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。b+树性质1.索引字段要尽量的小:通过上面的分析,我们知道IO次数取决于b+数的高度h,假设当前数据表的数据为N,每个磁盘块的数据项的数量是m,则有h=㏒(m+1)N,当数据量N一定的情况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比如int占4字节,要比bigint8字节少一半。这也是为什么b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下降,导致树增高。当数据项等于1时将会退化成线性表。2.索引的最左匹配特性(即从左往右匹配):当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。预告:本周五(3.8)将更新PHP面试MySQL数据库的面试题,敬请期待。以上内容摘自《PHP程序员面试笔试宝典》书籍,目前本书没有电子版,可到各大电商平台购买纸质版。更多PHP相关的面试知识、考题可以关注公众号获取:琉忆编程库对本文有什么问题或建议都可以进行留言,我将不断完善追求极致,感谢你们的支持。

March 6, 2019 · 1 min · jiezi

Cookie和Session的区别,Koa2+Mysql+Redis实现登录逻辑

为什么需要登录态?因为需要识别用户是谁,否则怎么在网站上看到个人相关信息呢?为什么需要登录体系?因为HTTP是无状态的,什么是无状态呢?就是说这一次请求和上一次请求是没有任何关系的,互不认识的,没有关联的。我们的网站都是靠HTTP请求服务端获得相关数据,因为HTTP是无状态的,所以我们无法知道用户是谁。所以我们需要其他方式保障我们的用户数据。当然了,这种无状态的的好处是快速。什么叫保持登录状态?比如说我在百度A页面进行了登录,但是不找个地方记录这个登录态的话。那我去B页面,我的登录态怎么保持呢?难道要url携带吗?这肯定是不安全的。你让用户再登录一次?登个鬼,再见???? 用户体验不友好。所以我们需要找个地方,存储用户的登录数据。这样可以给用户良好的用户体验。但是这个状态一般是有保质期的,主要原因也是为了安全。为了解决这个问题,Cookie出现了。CookieCookie的作用就是为了解决HTTP协议无状态的缺陷所作的努力。Cookie是存在浏览器端的。也就是可以存储我们的用户信息。一般Cookie 会根据从服务器端发送的响应的一个叫做Set-Cookie的首部字段信息,通知浏览器保存Cookie。当下次发送请求时,会自动在请求报文中加入Cookie 值后发送出去。当然我们也可以自己操作Cookie。如下图所示(图来源《图解HTTP》)这样我们就可以通过Cookie中的信息来和服务端通信。服务端如何配合?Session!需要看起来Cookie已经达到了保持用户登录态的效果。但是Cookie中存储用户信息,显然不是很安全。所以这个时候我们需要存储一个唯一的标识。这个标识就像一把钥匙一样,比较复杂,看起来没什么规律,也没有用户的信息。只有我们自己的服务器可以知道用户是谁,但是其他人无法模拟。这个时候Session就出现了,Session存储用户会话所需的信息。简单理解主要存储那把钥匙Session_ID,用这个钥匙Session_ID再去查询用户信息。但是这个标识需要存在Cookie中,所以Session机制需要借助于Cookie机制来达到保存标识Session_ID的目的。如下图所示。这个时候你可能会想,那这个Session有啥用?生成了一个复杂的ID,在服务器上存储。那好像我们自己生成一个Session_ID,存在Mysql也可以啊!没错,就是这样!个人认为Session其实已经发展为一个抽象的概念,已经形成了业界的一种解决方案。可能它最开始出现的时候有自己规则,但是现在经过发展。随着业务的复杂,各大公司早就自己实现了方案。Session_id你想搞成什么样,就什么样,想存在哪里就存在哪里。一般服务端会把这个Session_id存在缓存,不会和用户信息表混在一起。一个是为了快速拿到Session_id。第二个是因为前面也讲到过,Session_id是有保质期的,为了安全一段时间就会失效,所以放在缓存里就可以了。常见的就是放在redis、memcached里。也有一些情况放在mysql里的,可能是用户数据比较多。但都不会和用户信息表混在一起。Cookie 和 Session 的区别登录态保持总结浏览器第一次请求网站, 服务端生成 Session ID。把生成的 Session ID 保存到服务端存储中。把生成的 Session ID 返回给浏览器,通过 set-cookie。浏览器收到 Session ID, 在下一次发送请求时就会带上这个 Session ID。服务端收到浏览器发来的 Session ID,从 Session 存储中找到用户状态数据,会话建立。此后的请求都会交换这个 Session ID,进行有状态的会话。登录流程图实现案例(koa2+ Mysql)本案例适合对服务端有一定概念的同学哦,下面仅是核心代码。数据库配置第一步就是进行数据库配置,这里我单独配置了一个文件。因为当项目大起来,需要对开发环境、测试环境、正式的环境的数据库进行区分。let dbConf = null;const DEV = { database: ‘dandelion’, //数据库 user: ‘root’, //用户 password: ‘xxx’, //密码 port: ‘3306’, //端口 host: ‘127.0.0.1’ //服务ip地址}dbConf = DEV;module.exports = dbConf;数据库连接。const mysql = require(‘mysql’);const dbConf = require(’./../config/dbConf’);const pool = mysql.createPool({ host: dbConf.host, user: dbConf.user, password: dbConf.password, database: dbConf.database,})let query = function( sql, values ) { return new Promise(( resolve, reject ) => { pool.getConnection(function(err, connection) { if (err) { reject( err ) } else { connection.query(sql, values, ( err, rows) => { if ( err ) { reject( err ) } else { resolve( rows ) } connection.release() }) } }) })}module.exports = { query,}路由配置这里我也是单独抽离出了文件,让路由看起来更舒服,更加好管理。const Router = require(‘koa-router’);const router = new Router();const koaCompose = require(‘koa-compose’);const {login} = require(’../controllers/login’);// 加前缀router.prefix(’/api’);module.exports = () => { // 登录 router.post(’/login’, login); return koaCompose([router.routes(), router.allowedMethods()]);}中间件注册路由。const routers = require(’../routers’);module.exports = (app) => { app.use(routers());}Session_id的生成和存储我的session_id生成用了koa-session2库,存储是存在redis里的,用了一个ioredis库。配置文件。const Redis = require(“ioredis”);const { Store } = require(“koa-session2”); class RedisStore extends Store { constructor() { super(); this.redis = new Redis(); } async get(sid, ctx) { let data = await this.redis.get(SESSION:${sid}); return JSON.parse(data); } async set(session, { sid = this.getID(24), maxAge = 1000 * 60 * 60 } = {}, ctx) { try { console.log(SESSION:${sid}); // Use redis set EX to automatically drop expired sessions await this.redis.set(SESSION:${sid}, JSON.stringify(session), ‘EX’, maxAge / 1000); } catch (e) {} return sid; } async destroy(sid, ctx) { return await this.redis.del(SESSION:${sid}); }} module.exports = RedisStore;入口文件(index.js)const Koa = require(‘koa’);const middleware = require(’./middleware’); //中间件,目前注册了路由const session = require(“koa-session2”); // sessionconst Store = require("./utils/Store.js"); //redisconst body = require(‘koa-body’);const app = new Koa();// session配置app.use(session({ store: new Store(), key: “SESSIONID”,}));// 解析 post 参数app.use(body());// 注册中间件middleware(app);const PORT = 3001;// 启动服务app.listen(PORT);console.log(server is starting at port ${PORT});登录接口实现这里主要是根据用户的账号密码,拿到用户信息。然后将用户uid存储到session中,并将session_id设置到浏览器中。代码很少,因为用了现成的库,人家都帮你做好了。这里我没有把session_id设置过期时间,这样用户关闭浏览器就没了。const UserModel = require(’../model/UserModel’); //用户表相关sql语句const userModel = new UserModel();/** * @description: 登录接口 * @param {account} 账号 * @param {password} 密码 * @return: 登录结果 */async function login(ctx, next) { // 获取用户名密码 get const {account, password} = ctx.request.body; // 根据用户名密码获取用户信息 const userInfo = await userModel.getUserInfoByAccount(account, password); // 生成session_id ctx.session.uid = JSON.stringify(userInfo[0].uid); ctx.body = { mes: ‘登录成功’, data: userInfo[0].uid, success: true, };};module.exports = { login,};登录之后其他的接口就可以通过这个session_id获取到登录态。// 业务接口,获取用户所有的需求const DemandModel = require(’../../model/DemandModel’);const demandModel = new DemandModel();const shortid = require(‘js-shortid’); const Store = require("../../utils/Store.js");const redis = new Store();async function selectUserDemand(ctx, next) { // 判断用户是否登录,获取cookie里的SESSIONID const SESSIONID = ctx.cookies.get(‘SESSIONID’); if (!SESSIONID) { console.log(‘没有携带SESSIONID,去登录吧~’); return false; } // 如果有SESSIONID,就去redis里拿数据 const redisData = await redis.get(SESSIONID); if (!redisData) { console.log(‘SESSIONID已经过期,去登录吧~’); return false; } if (redisData && redisData.uid) { console.log(登录了,uid为${redisData.uid}); } const uid = JSON.parse(redisData.uid); // 根据session里的uid 处理业务逻辑 const data = await demandModel.selectDemandByUid(uid); console.log(data); ctx.body = { mes: ‘’, data, success: true, };};module.exports = { selectUserDemand,}坑点注意注意1、注意跨域问题2、处理OPTIONS多发预检测问题app.use(async (ctx, next) => { ctx.set(‘Access-Control-Allow-Origin’, ‘http://test.xue.com’); ctx.set(‘Access-Control-Allow-Credentials’, true); ctx.set(‘Access-Control-Allow-Headers’, ‘content-type’); ctx.set(‘Access-Control-Allow-Methods’, ‘OPTIONS, GET, HEAD, PUT, POST, DELETE, PATCH’); // 这个响应头的意义在于,设置一个相对时间,在该非简单请求在服务器端通过检验的那一刻起, // 当流逝的时间的毫秒数不足Access-Control-Max-Age时,就不需要再进行预检,可以直接发送一次请求。 ctx.set(‘Access-Control-Max-Age’, 3600 * 24); if (ctx.method == ‘OPTIONS’) { ctx.body = 200; } else { await next(); }});3、允许携带cookie发请求的时候设置这个参数withCredentials: true,请求才能携带cookieaxios({ url: ‘http://test.xue.com:3001/api/login', method: ‘post’, data: { account: this.account, password: this.password, }, withCredentials: true, // 允许设置凭证}).then(res => { console.log(res.data); if (res.data.success) { this.$router.push({ path: ‘/index’ }) }})源码以上的代码只是贴了核心的,源码如下前端 和 后端但是练手的项目还在开发中,网站其他功能还没有全部实现。代码写的比较挫????????????但是登录完全没有问题的~但是你需要提前了解Redis、Mysql、Nginx和基本的服务器操作哦!如有错误,请指教???? ...

March 5, 2019 · 3 min · jiezi

Centos下Mysql乱码

问题出现原因刚刚装完的 mysql 中 sever 的字符编码是latin1 ,所以导入sql后乱码了查询MySQL中字符方法show variables like ‘character_set%’;解决方法将 mysql-server 端的字符编码修改utf-8解决步骤停止 mysql 服务service mysqld stop 在 /etc/my.cnf 中的 mysqld 的最后一行加入character-set-server=utf8重启 mysql 服务service mysqld start 结果截图

March 5, 2019 · 1 min · jiezi

内网穿透与反向代理,浅谈前后台分离

自去年毕业来到杭州,想想也该有大半年了,本身是软件工程的科班出身,在校时理论掌握的还可以,但应用到实践当中时,有些还是不大理解,于是,不停地向带我的人请教,毕竟,三人行,必有我师焉。经过一段时间理论加实践,多少也掌握了其中的门路。前端和后端(服务器端、客户端)分离前后端不分离在从业的过程中,也和其他程序员交流过,他们很多人都没有前后端(服务器和客户端)分离,而是前后端一起做掉。如果前后端不分离,此时的服务器端主要是指java代码,客户端主要是指jsp,通过spring MVC 将数据封装到ResponseBody中,再返回给jSP。JSP拿到数据,渲染页面。这里 不需要考虑端口号的问题。比如: /** * Created By zby on 16:03 2019/3/5 / @RequestMapping(value = “/”, method = RequestMethod.GET) @ResponseBody public Result fun() { return null; }前后端分离当然,前后端分离时,后端还是以java代码为主,前端就变化多端了。. 后端java通过springMVC的Rest模式的Controller层,接收前端页面传来的接口和参数,经过一系列的入参校验,调用事务层(也就是service层)这里主要是hibernate(mybatis)的事务层,实现数据库的事务操作。再调用dao(data Access object)层实现事务的原子性操作,即将顺时态的java对象转化为持久状态的数据库对象。层层深入,层层返回,将通过Result回传给前端。. 前端前端主要用h5进行页面布局,CSS3实现页面的美化。JavaScript配合jQuery调用后端的接口,传递参数和获取后端回传的数据。通过vue.js实现回传的数据的双向绑定。还可能涉及到其他框架,比如页面布局的bootstrap,数据table方式展示的jqgrid等等。前后端分离,如何实现数据交互我们将写好的java代码部署在服务器上,比如Tomcat、Jboss主流服务器。这里以Tomcat来讲解,我们将项目部署在Tomcat的上,具体如何部署Tomcat,可以参考这篇教程,Tomcat8权威指南。我们现在一般在maven中以插件的方式配置Tomcat,便于本地测试,路径为根路径,如以下代码: <build> <defaultGoal>install</defaultGoal> //maven生成的war生成的名字 <finalName>cloudCodeSaleManager</finalName> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <port>58081</port> <path>/</path> <uriEncoding>UTF-8</uriEncoding> <finalName>zfounder-custmom-member-portal</finalName> <server>tomcat7</server> </configuration> </plugin> </plugins> </build>在真实的项目中,一般会有测试服和正式服,测试服我们用户的测试数据库和测试服务器,正式服我们用到的是正式数据库和正式服务器,有人说,这样输简直是废话。但是,我们测试数据库和正式数据库是不一样的,因而,如果都写在同一个配置文件中,修改势必麻烦。因而,我们可以在打包时,会有测试包和正式包,这里就涉及到maven的profile的配置文件(是在pom中配置): <profiles> <profile> <id>dev</id> <activation> <activeByDefault>true</activeByDefault> </activation> <build> <filters> <filter>../../platform-dev.properties</filter> </filters> </build> </profile> <profile> <id>prd</id> <build> <filters> <filter>../../platform-prd.properties</filter> </filters> </build> </profile> </profiles>我们Tomcat启动后,访问后端接口(url)的格式如下:scheme://host.domain:port/path/filenamescheme - 定义因特网服务的类型。最常见的类型是 httphost - 定义域主机(http 的默认主机是 www)domain - 定义因特网域名,比如 runoob.com:port - 定义主机上的端口号(http 的默认端口号是 80)path - 定义服务器上的路径(如果省略,则文档必须位于网站的根目录中)。filename - 定义文档/资源的名称当然,如果没有域名的话,我们想要访问本地,也可以是这样的:http://ip:port/path/filename这里的ip涉及到内网和本机地址。内网也就是局域网,一般以192.168..打头。本机地址是:127.0.0.1。它们两个有什么区别呢?假设访问我的server_path如下所示constant = { dev: { server_path: “http://127.0.0.1:58081/”, imgPre: “http://web.cs.wentonghuishou.com/", commonParams: {} },}_env = “dev”;window.constant = constant[_env];我做后端Java的,开启了Tomcat。我的同事是做前端的,他用上面的server_path访问我,也就是说,想通过我本机ip请求我的接口,是没办法访问我后端的接口。因为他,这是我本机的ip,只有我个人才能访问。相反,我自己是可以访问的。如图所示:如果他把server_path改成了server_path: “http://192.168.40.177:58081/",,那么,他想通过局域网访问我的接口,这是可以访问我的。因为,我们同处在这个局域网下的。如图所示:外网如何访问,也就是,内网穿透假如,我和我的同事,不在同一局域网,但他,想访问我后端的接口,这时该怎么办?应该是需要摆脱网域限制,能够访问我的内网,也就是访问的本机。这时,就出现了,内网穿透的软件,比如ngrok,小米求等。小米球可以实现内网穿透,他是怎么实现内网穿透,主要是通过域名的反向代理,这也就是所谓的反向代理。其实,反向代理没那么高大上,不要被它吓到了。当然,这里需要输入端口号,这里前端的hbuilder的端口号,也就是8020端口号。为什么需要端口号,端口号能够确定本机唯一的进程。比如mysql的3306端口号,Tomcat的80端口号等。为什么是前端的端口号,因为我们首先访问的是页面,页面通过server_path来访问后端接口,这里我们不需要考虑这方面的。小米求的配置如下,这里是免费版的:当我们,在浏览器的地址栏输入http://zby.ngrok.xiaomiqiu.cn…,你会发现,它能访问到我的前端页面,并调用了我后端的接口,这就实现了ip的反向代理。域名解析也是同样的道理,利用了ip的反向代理。如图所示: ...

March 5, 2019 · 1 min · jiezi

laravel-admin 文件上传阿里OSS

前言因为项目需求,需要把图片上传至阿里云 OSS,我的 Api 接口和后台项目是分开的,都使用的 laravel 框架开发,Api 接入 OSS 这里就不做讨论了,这里主要说一下 laravel-admin 上传阿里 OSS 的问题。网上的一些教程也有非常好的,但只说了使用流程,很少有说碰到的问题之类的情况,这里主要就是讲述我在 laravel-admin 接入阿里 OSS 时所遇到的一些问题,以后还有问题时,也会在这里更新。开发环境下面是我的 composer.json 内容(只列出本文需要):“require”: { “php”: “>=7.0.0”, “encore/laravel-admin”: “^1.6”, “jacobcyl/ali-oss-storage”: “^2.1”, “laravel/framework”: “5.5.*”, …}具体流程1、下载合适的第三方包在 composer.json 文件中的 require 添加 “jacobcyl/ali-oss-storage”: “^2.1”;或者直接运行 composer require jacobcyl/ali-oss-storage:^2.1 亦可。2、添加服务提供者在 config/app.php 文件下增加 Jacobcyl\AliOSS\AliOssServiceProvider::class,,如下图所示:3、在 config/filesystems.php 增加 OSS 配置信息如下:‘disks’ => [ ’local’ => [ ‘driver’ => ’local’, ‘root’ => storage_path(‘app’), ], ‘public’ => [ ‘driver’ => ’local’, ‘root’ => storage_path(‘app/public’), ‘url’ => env(‘APP_URL’).’/storage’, ‘visibility’ => ‘public’, ], ‘s3’ => [ ‘driver’ => ‘s3’, ‘key’ => env(‘AWS_ACCESS_KEY_ID’), ‘secret’ => env(‘AWS_SECRET_ACCESS_KEY’), ‘region’ => env(‘AWS_DEFAULT_REGION’), ‘bucket’ => env(‘AWS_BUCKET’), ], // 这里是新增 ‘oss’ => [ ‘driver’ => ‘oss’, ‘access_id’ => // 这里是你的 OSS 的 accessId, ‘access_key’ => // 这里是你的 OSS 的 accessKey, ‘bucket’ => // 这里是你的 OSS 自定义的存储空间名称, ’endpoint’ => ‘oss-cn-hangzhou.aliyuncs.com’, // 这里以杭州为例 ‘cdnDomain’ => ‘’, // 使用 cdn 时才需要写, https://加上 Bucket 域名 ‘ssl’ => true, // true 使用 ‘https://’ false 使用 ‘http://’. 默认 false, ‘isCName’ => false, // 是否使用自定义域名,true: Storage.url() 会使用自定义的 cdn 或域名生成文件 url,false: 使用外部节点生成url ‘debug’ => false, ], ],4、在 config/filesystems.php 更改 ‘default’ 配置信息如下:‘default’ => env(‘FILESYSTEM_DRIVER’, ‘oss’),也可以在 env 文件中定义 FILESYSTEM_DRIVER = oss 也可。5、在 config/admin.php 修改 upload 配置如下:‘upload’ => [ // Disk in config/filesystem.php. ‘disk’ => ‘oss’, // 这里就是指向 disks 下面的 oss 配置 // Image and file upload path under the disk above. ‘directory’ => [ ‘image’ => ‘images’, ‘file’ => ‘files’, ],],网上的步骤一般就是到这里了,上面的流程参考:laravel-admin 文件上传 oss;问题出现但是这时候问题就出现了, laravel-admin 本身为了开发者快速开发,本身就完成了一部分功能,当我们使用默认账号 admin 登录进去后,在后台的页面右上角和左上角都有默认的头像显示,这个默认头像是存放在本地 local 下的,在 vendor/encore/laravel-admin/resources/views/partials 下 header.blade.php 和 sidebar.blade.php 两个视图文件中显示,请看下图:header.blade.phpsidebar.blade.php而我们在 具体流程 的 5个步骤中已经把上传的配置改成了 oss 了,这时访问后台时,就会抛出一个异常:一开始我以为是 config/filesystems.php 的 default 还写成 local 会解决,但结果并没有。由于时间的原因,我还没有深入去研究,对于 laravel 框架文件上传的原理,我还是个新手,不过这里放上我的解决方法,如果有更好的解决方案,欢迎下方指正,谢谢!解决把 header.blade.php 和 sidebar.blade.php 两个视图文件中的图片的 src 改成阿里云 OSS 存放图片的路径,比如: https://xxx.oss-cn-hangzhou.aliyuncs.com/xxx/xxx/5c77a20012963.jpg ,这张图片就是你想要上传的头像图片地址。这里只是举个例子,当然这样写还是不方便,万一以后更改,还是需要找到这两个文件手动改,很麻烦,可根据自身需求进行解耦优化,这里就不做讨论了。道路阻且长,仍需不断前行!文章参考: [https://blog.csdn.net/zxdf123/article/details/82752145][6][https://blog.csdn.net/guyaofei/article/details/79918697][7] ...

March 4, 2019 · 2 min · jiezi

Mysql面试知识点总结(进阶篇)

上一篇主要介绍一些基础的mysql知识点,这一篇我们介绍一下mysql比较重要但在开发中我们程序员很少知道的几个大点(自以为是的观点)。数据库设计三范式:第一范式:数据库表的每一列都是不可分割的原子数据项,即列不可拆分。第二范式:建立在第一范式的基础上,要求数据库表中的每个实例或记录必须是可以唯一被区分的,即唯一标识。第三范式:建立在第二范式的基础上,任何非主属性不依赖与其他非主属性,即引用主键。视图视图是虚拟表,并不储存数据,只包含定义时的语句的动态数据。create view view_name as sql查询语句存储过程一条或多条sql语句集合,其优点为(浓缩:简单/安全/高性能):存储过程能实现较快的执行速度存储过程允许标准组件是编程。存储过程可以用流程控制语句编写,有很强的灵活性,可以完成复杂的判断和较复杂的运算。存储过程可被作为一种安全机制来充分利用。存储过程能够减少网络流量delimiter 分隔符create procedure|proc proc_name()begin sql语句end 分隔符delimiter ; –还原分隔符,为了不影响后面的语句的使用默认的分隔符是;但是为了能在整个存储过程中重用,因此一般需要自定义分隔符(除\外)show procedure status like “”; –查询存储过程,可以不适用like进行过滤drop procedure if exists;–删除存储过程存储过程和函数的区别?相同点:存储过程和函数都是为了可重复的执行操作数据库的 sql 语句的集合。1)存储过程和函数都是一次编译,就会被缓存起来,下次使用就直接命中已经编译好的 sql 语句,不需要重复使用。减少网络交互,减少网络访问流量。不同点:标识符不同,函数的标识符是 function,存储过程是 proceduce。1)函数中有返回值,且必须有返回值,而过程没有返回值,但是可以通过设置参数类型(in,out)来实现多个参数或者返回值。2)存储函数使用 select 调用,存储过程需要使用 call 调用。3)select 语句可以在存储过程中调用,但是除了 select..into 之外的 select 语句都不能在函数中使用。4)通过 in out 参数,过程相关函数更加灵活,可以返回多个结果。触发器在对表数据进行变动的时候进行具体的操作,有六种,分别为增删改的前后操作。create trigger trigger_name ALTER|BEFORE select|update|deleteon 表for each rowtrigger_stmt重点:只有表才支持触发器,视图和临时表都不支持触发器不支持更新和覆盖,修改必须先删除然后创建日志Mysql主要有四种日志文件:错误日志:记录启动,运行或者停止 mysql 时出现的问题;查询日志:记录所有msyql的活动二进制日志:记录更新过数据的所有语句缓慢查询日志:记录查询缓慢的任何查询

March 4, 2019 · 1 min · jiezi

mysql innodb索引原理

聚集索引(clustered index)innodb存储引擎表是索引组织表,表中数据按照主键顺序存放。其聚集索引就是按照每张表的主键顺序构造一颗B+树,其叶子结点中存放的就是整张表的行记录数据,这些叶子节点成为数据页。聚集索引的存储并不是物理上连续的,而是逻辑上连续的,叶子结点间按照主键顺序排序,通过双向链表连接。多数情况下,查询优化器倾向于采用聚集索引,因为聚集索引能在叶子结点直接找到数据,并且因为定义了数据的逻辑顺序,能特别快的访问针对范围值的查询。聚集索引的这个特性决定了索引组织表中的数据也是索引的一部分。由于表里的数据只能按照一颗B+树排序,因此一张表只能有一个聚簇索引。在Innodb中,聚簇索引默认就是主键索引。如果没有主键,则按照下列规则来建聚簇索引:没有主键时,会用一个非空并且唯一的索引列做为主键,成为此表的聚簇索引;如果没有这样的索引,InnoDB会隐式定义一个主键来作为聚簇索引。由于主键使用了聚簇索引,如果主键是自增id,那么对应的数据也会相邻地存放在磁盘上,写入性能较高。如果是uuid等字符串形式,频繁的插入会使innodb频繁地移动磁盘块,写入性能就比较低了。B+树(多路平衡查找树)我们知道了innodb引擎索引使用了B+树结构,那么为什么不是其他类型树结构,例如二叉树呢?计算机在存储数据的时候,有最小存储单元,这就好比人民币流通最小单位是分一样。文件系统的最小单元是块,一个块的大小是4k(这个值根据系统不同并且可设置),InnoDB存储引擎也有自己的最小储存单元—页(Page),一个页的大小是16K(这个值也是可设置的)。文件系统中一个文件大小只有1个字节,但不得不占磁盘上4KB的空间。同理,innodb的所有数据文件的大小始终都是16384(16k)的整数倍。所以在MySQL中,存放索引的一个块节点占16k,mysql每次IO操作会利用系统的预读能力一次加载16K。这样,如果这一个节点只放1个索引值是非常浪费的,因为一次IO只能获取一个索引值,所以不能使用二叉树。B+树是多路查找树,一个节点能放n个值,n = 16K / 每个索引值的大小。例如索引字段大小1Kb,这时候每个节点能放的索引值理论上是16个,这种情况下,二叉树一次IO只能加载一个索引值,而B+树则能加载16个。B+树的路数为n+1,n是每个节点存在的值数量,例如每个节点存放16个值,那么这棵树就是17路。从这里也能看出,B+树节点可存储多个值,所以B+树索引并不能找到一个给定键值的具体行。B+树只能找到存放数据行的具体页,然后把页读入到内存中,再在内存中查找指定的数据。附:B树和B+树的区别在于,B+树的非叶子结点只包含导航信息,不包含实际的值,所有的叶子结点和相连的节点使用链表相连,便于区间查找和遍历。辅助索引也称为非聚集索引,其叶子节点不包含行记录的全部数据,叶子结点除了包含键值以外,每个叶子结点中的索引行还包含一个书签,该书签就是相应行的聚集索引键。如下图可以表示辅助索引和聚集索引的关系(图片源自网络,看大概意思即可):当通过辅助索引来寻找数据时,innodb存储引擎会通过辅助索引叶子节点获得只想主键索引的主键,既然后再通过主键索引找到完整的行记录。例如在一棵高度为3的辅助索引树中查找数据,那需要对这颗辅助索引树进行3次IO找到指定主键,如果聚集索引树的高度同样为3,那么还需要对聚集索引树进行3次查找,最终找到一个完整的行数据所在的页,因此一共需要6次IO访问来得到最终的数据页。创建的索引,如联合索引、唯一索引等,都属于非聚簇索引。联合索引联合索引是指对表上的多个列进行索引。联合索引也是一颗B+树,不同的是联合索引的键值数量不是1,而是大于等于2。例如有user表,字段为id,age,name,现发现如下两条sql使用频率最多:Select * from user where age = ? ;Select * from user where age = ? and name = ?;这时候不需要为age和name单独建两个索引,只需要建如下一个联合索引即可:create index idx_age_name on user(age, name)联合索引的另一个好处已经对第二个键值进行了排序处理,有时候可以避免多一次的排序操作。覆盖索引覆盖索引,即从辅助索引中就可以得到查询所需要的所有字段值,而不需要查询聚集索引中的记录。覆盖索引的好处是辅助索引不包含整行记录的所有信息,故其大小要远小于聚集索引,因此可以减少大量的IO操作。例如上面有联合索引(age,name),如果如下:select age,name from user where age=?就能使用覆盖索引了。覆盖索引的另一个好处是对于统计问题,例如:select count(*) from userinnodb存储引擎并不会选择通过查询聚集索引来进行统计。由于user表上还有辅助索引,而辅助索引远小于聚集索引,选择辅助索引可以减少IO操作。注意事项索引只建合适的,不建多余的因为每当增删数据时,B+树都要进行调整,如果建立多个索引,多个B+树都要进行调整,而树越多、结构越庞大,这个调整越是耗时耗资源。如果减少了这些不必要的索引,磁盘的使用率可能会大大降低。索引列的数据长度能少则少。索引数据长度越小,每个块中存储的索引数量越多,一次IO获取的值更多。匹配列前缀可用到索引 like 9999%,like %9999%、like %9999用不到索引;Where 条件中in和or可以使用索引, not in 和 <>操作无法使用索引;如果是not in或<>,面对B+树,引擎根本不知道应该从哪个节点入手。匹配范围值,order by 也可用到索引;多用指定列查询,只返回自己想到的数据列,少用select ;不需要查询无用字段,并且不使用可能还会命中覆盖索引哦;联合索引中如果不是按照索引最左列开始查找,无法使用索引;最左匹配原则;联合索引中精确匹配最左前列并范围匹配另外一列可以用到索引;联合索引中如果查询中有某个列的范围查询,则其右边的所有列都无法使用索本文作者:信~仰阅读原文本文为云栖社区原创内容,未经允许不得转载。

March 4, 2019 · 1 min · jiezi

PHP面试之MySQL数据库部分基础知识

你好,是我琉忆,PHP程序员面试笔试系列图书的作者。本周(2019.3.4至3.8)的一三五更新的文章如下:周一:PHP面试MySQL数据库的基础知识周三:PHP面试MySQL数据库的索引周五:PHP面试MySQL数据库的面试真题自己整理了一篇“一二三四范式有何区别?”的文章,关注公众号:“琉忆编程库”,回复:“范式”,我发给你。以下内容来自《PHP程序员面试笔试宝典》如需转载请注明出处。一、几款开源数据库的对比和介绍二、SQL语言的功能有哪些?SQL是结构化查询语言(Structured Query Language)的缩写,其功能包括数据查询、数据操纵、数据定义和数据控制四个部分。数据查询是数据库中最常见的操作,通过select语句可以得到所需的信息。SQL语言的数据操纵语句(Data Manipulation Language,DML)主要包括插入数据、修改数据以及删除数据三种语句。SQL语言使用数据定义语言(Data Definition Language,DDL)实现数据定义功能,可对数据库用户、基本表、视图、索引进行定义与撤销。数据控制语句(Data Control Language,DCL)用于对数据库进行统一的控制管理,保证数据在多用户共享的情况下能够安全。基本的SQL语句有select、insert、update、delete、create、drop、grant、revoke等。其具体使用方式见下表。自己整理了一篇“一二三四范式有何区别?”的文章,关注公众号:“琉忆编程库”,回复:“范式”,我发给你。三、什么是事务?事务是数据库中一个单独的执行单元(Unit),它通常由高级数据库操作语言(例如SQL)或编程语言(例如C++、Java等)书写的用户程序的执行所引起。当在数据库中更改数据成功时,在事务中更改的数据便会提交,不再改变;否则,事务就取消或者回滚,更改无效。例如网上购物,其交易过程至少包括以下几个步骤的操作:1)更新客户所购商品的库存信息。2)保存客户付款信息。3)生成订单并且保存到数据库中。4)更新用户相关信息,如购物数量等。在正常的情况下,这些操作都将顺利进行,最终交易成功,与交易相关的所有数据库信息也成功地更新。但是,如果遇到突然掉电或是其他意外情况,导致这一系列过程中任何一个环节出了差错,例如在更新商品库存信息时发生异常、顾客银行账户余额不足等,都将导致整个交易过程失败。而一旦交易失败,数据库中所有信息都必须保持交易前的状态不变,例如最后一步更新用户信息时失败而导致交易失败,那么必须保证这笔失败的交易不影响数据库的状态,即原有的库存信息没有被更新、用户也没有付款、订单也没有生成。否则,数据库的信息将会不一致,或者出现更为严重的不可预测的后果,数据库事务正是用来保证这种情况下交易的平稳性和可预测性的技术。事务必须满足四个属性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),即ACID四种属性。(1)原子性事务是一个不可分割的整体,为了保证事务的总体目标,事务必须具有原子性,即当数据修改时,要么全执行,要么全都不执行,即不允许事务部分地完成,避免了只执行这些操作的一部分而带来的错误。原子性要求事务必须被完整执行。(2)一致性一个事务执行之前和执行之后数据库数据必须保持一致性状态。数据库的一致性状态应该满足模式锁指定的约束,那么在完整执行该事务后数据库仍然处于一致性状态。为了维护所有数据的完整性,在关系型数据库中,所有的规则必须应用到事务的修改上。数据库的一致性状态由用户来负责,由并发控制机制实现,例如银行转账,转账前后两个账户金额之和应保持不变,由于并发操作带来的数据不一致性包括丢失数据修改、读“脏”数据、不可重复读和产生幽灵数据。(3)隔离性隔离性也被称为独立性,当两个或多个事务并发执行时,为了保证数据的安全性,将一个事物内部的操作与事务的操作隔离起来,不被其他正在进行的事务看到。例如对任何一对事务T1、T2,对T1而言,T2要么在T1开始之前已经结束,要么在T1完成之后再开始执行。数据库有四种类型的事务隔离级别:不提交的读、提交的读、可重复的读和串行化。因为隔离性使得每个事务的更新在它被提交之前,对其他事务都是不可见的,所以,实施隔离性是解决临时更新与消除级联回滚问题的一种方式。(4)持久性持久性也被称为永久性,事务完成以后,数据库管理系统(DBMS)保证它对数据库中的数据的修改是永久性的,当系统或介质发生故障时,该修改也永久保持。持久性一般通过数据库备份与恢复来保证。严格来说,数据库事务属性(ACID)都是由数据库管理系统来进行保证的,在整个应用程序运行过程中应用无须去考虑数据库的ACID实现。一般情况下,通过执行COMMIT或ROLLBACK语句来终止事务,当执行COMMIT语句时,自从事务启动以来对数据库所做的一切更改就成为永久性的了,即被写入磁盘,而当执行ROLLBACK语句时,自动事务启动以来对数据库所做的一切更改都会被撤销,并且数据库中内容返回到事务开始之前所处的状态。无论什么情况,在事务完成时,都能保证回到一致状态。四、什么是触发器?触发器是一种特殊类型的存储过程,它由事件触发,而不是程序调用或手工启动,当数据库有特殊的操作时,对这些操作由数据库中的事件来触发,自动完成这些SQL语句。使用触发器可以用来保证数据的有效性和完整性,完成比约束更复杂的数据约束。具体而言,触发器与存储过程的区别见下表。根据SQL语句的不同,触发器可分为两类:DML触发器和DLL触发器。DML触发器是当数据库服务器发生数据操作语言事件时执行的存储过程,有After和Instead Of这两种触发器。After触发器被激活触发是在记录改变之后进行的一种触发器。Instead Of触发器是在记录变更之前,去执行触发器本身所定义的操作,而不是执行原来SQL语句里的操作。DLL触发器是在响应数据定义语言事件时执行的存储过程。具体而言,触发器的主要作用表现为如下几个方面:1)增加安全性。2)利用触发器记录所进行的修改以及相关信息,跟踪用户对数据库的操作,实现审计。3)维护那些通过创建表时的声明约束不可能实现的复杂的完整性约束以及对数据库中特定事件进行监控与响应。4)实现复杂的非标准的数据库相关完整性规则、同步实时地复制表中的数据。5)触发器是自动的,它们在对表的数据做了任何修改之后就会被激活,例如可以自动计算数据值,如果数据的值达到了一定的要求,则进行特定的处理。以某企业财务管理为例,如果企业的资金链出现短缺,并且达到某种程度,则发送警告信息。下面是一个触发器的例子,该触发器的功能是在每周末进行数据表更新,如果当前用户没有访问WEEKEND_UPDATE_OK表的权限,那么需要重新赋予权限。CREATE OR REPLACE TRIGGER update_on_weekends_checkBEFORE UPDATE OF sal ON EMPFOR EACH ROW DECLAREmy_count number(4);BEGINSELECT COUNT(u_name) FROM WEEKEND_UPDATE_OK INTO my_count WHERE u_name = user_name; IF my_count=0 THEN RAISE_APPLICATION_ERROR(20508, ‘Update not allowed’); END IF; END;五、触发器分为事前触发和事后触发,二者有什么区别?语句级触发和行级触发有什么区别?事前触发发生在事件发生之前验证一些条件或进行一些准备工作;事后触发发生在事件发生之后,做收尾工作,保证事务的完整性。而事前触发可以获得之前和新的字段值。语句级触发器可以在语句执行之前或之后执行,而行级触发在触发器所影响的每一行触发一次。预告:本周三(3.6)将更新PHP面试MySQL数据库的索引,敬请期待。以上内容摘自《PHP程序员面试笔试宝典》书籍,目前本书没有电子版,可到各大电商平台购买纸质版。更多PHP相关的面试知识、考题可以关注公众号获取:琉忆编程库对本文有什么问题或建议都可以进行留言,我将不断完善追求极致,感谢你们的支持。

March 4, 2019 · 1 min · jiezi

sql排序之rank,row_number,dense_rank的区别

–创建测试表create table te.sc(id int, name varchar(20),class varchar(20), score int);–给测试表插入数据insert into te.sc values (1,‘张飞’,‘一年一班’,100);insert into te.sc values (2,‘刘备’,‘一年一班’,99);insert into te.sc values (3,‘李逵’,‘一年一班’,95);insert into te.sc values (4,‘小动’,‘一年一班’,97);insert into te.sc values (5,‘小智’,‘一年一班’,80);insert into te.sc values (6,‘吕布’,‘一年二班’,67);insert into te.sc values (7,‘赵云’,‘一年二班’,90);insert into te.sc values (8,‘典韦’,‘一年二班’,89);insert into te.sc values (9,‘关羽’,‘一年二班’,70);insert into te.sc values (10,‘马超’,‘一年二班’,98);insert into te.sc values (11,‘张媛’,‘一年一班’,100);不管在oracle,还是在8.0版的mysql中,在排序的时候都可以用到三个函数:rank,row_number,dense_rank–列出每个班分数排名前三的学生 select * from (select id, name, class, score , row_number() over (partition by class order by score desc) as r1, rank() over (partition by class order by score desc) as r2 , dense_rank() over (partition by class order by score desc) as r3 from te.sc) B where r1<=3 ;id name class score r1 r2 r31 张飞 一年一班 100 1 1 111 张媛 一年一班 100 2 1 12 刘备 一年一班 99 3 3 210 马超 一年二班 98 1 1 17 赵云 一年二班 90 2 2 28 典韦 一年二班 89 3 3 3这三个函数的区别主要在分数一致的情况下,row_number()不重复排序,rank()重复且跳数字排序,dense_rank()重复且不跳数字排序。 ...

March 2, 2019 · 1 min · jiezi

mysql数据库重命名

工具:navicat数据库:mysql导入或新建数据库时候会发现,突然发现名字起错了,然后你会发现是f2或者右键没有重命名这项功能的,网上很多办法,但是发现有个更加简单的——就是另外新建一个新的数据库,然后将需要重命名的数据库的表拖动到这个新的数据库即可,如图:

March 1, 2019 · 1 min · jiezi

Mysql常用函数

数学floor()向下取整ceil()向上取整round(,)四舍五入truncate(,)不四舍五入mod(,)取余数abs()绝对值power(,)次方pi()圆周率六位rand()随机数sign()大于0返回1,小于0返回-1,否则为0exp() e的几次方字符串char_length()字符数length()字符串长度concat()拼接字符串,包含Null,则返回值就是nullconcat_wa()以指定分隔符拼接字符串concat_wa(null,’’,’’) 返回nullconcat_wa(’-’,‘a’,‘b’,null) 返回a-bupper()=ucase() 转大写lower()=lcase() 转小写reverse() 转换大小写left(,)|right(,)字符串前几个|后几个字符lpad(x,y,z)|rpad(x,y,z) x按y长度在前|后添加z字符trim()|ltrim()|rtrim() 去空格repeat(,次数) 重复指定次数replace(x,y,z)x字符串把y字符串替换为zsubstring(x,y,z) x字符串从y开始截取z长度,y从1开始strcmp(x,y)字符串比较日期时间curdate()=current_date()当前日期curtime()=current_time()当前时间now()=current_timestamp()=sysdate()当前日期和时间month(curdate())当前月份monthname(curdate())当前月份的名字dayname()周几名字dayofweek()周几,周天为1week(now())一年中第几周year/month/day/hour/minute/second年月日时分秒datediff() 两个日期相差天数date_format(日期,"%Y%m%d")其他version()版本connection_id()连接数database()=schema()当前数据库user()=current_user()=system_user()=session_user()当前用户last_insert_id()当前表的上一次auto_increment值md5() password()加密format(数字,2)千分位表示并只保留两位小数

March 1, 2019 · 1 min · jiezi

mysql设置唯一键

【一】设置字符唯一键(非自增)String uuid = UUID.randomUUID().toString().replace("-", “”);对象.setId(uuid);【二】重命名为唯一键:String fileName = FileUtil.renameToUUID(fname);【三】获取文件后缀:String fileType = fname.substring(fname.lastIndexOf(".") + 1, fname.length()).toLowerCase();

March 1, 2019 · 1 min · jiezi

mysql 数据库四种事务隔离级别

熟悉 mysql 数据库四种事务隔离级别:查询mysql中事务隔离级别SELECT @@tx_isolation;read uncommitted(RU)读未提交:一个事务中,可以读取到其他事务未提交的变更read committed(RC)读已提交:一个事务中,可以读取到其他事务已经提交的变更repetable read,(RR)可重复读:一个事务中,直到事务结束前,都可以反复读取到事务刚开始看到的数据,不会发生变化mysql的默认隔离级别是RRRR和RC的区别是在一个事务中RR隔离级别的读到一张表的数据都是一样事务A事务Bbegin; select * from a insert into a(…)select * from a RR隔离级别下:事务A二次select查询的结果是一样的,看不到事务B中插入的数据RC隔离级别下:事务A第二次select查询是可以看到事务B中插入的数据serializable(串行读):即便每次读都需要获得表级共享锁,每次写都加表级排它锁,两个会话间读写会相互阻塞。

March 1, 2019 · 1 min · jiezi

innodb Cardinality学习笔记

github 传送门链接描述 欢迎过来star呀~背景1、之前对innodb的Cardinality没概念,只知道要高选择性的列上建索引,比如用户名而不是性别,因为性别区分度不高,但是这过程没有一个量化的思维。2、有幸接触到:《MySQL技术内幕:InnoDB存储引擎-姜承尧》,碰巧有一章讲到Cardinality,遂做下脑图笔记。总结

February 28, 2019 · 1 min · jiezi

数据库连接池

一、数据库连接池的原理基本原理对于一个简单的数据库应用,由于对于数据库的访问不是很频繁。这时可以简单地在需要访问数据库时,就新创建一个连接,用完后就关闭它,这样做也不会带来什么明显的性能上的开销。但是对于一个复杂的数据库应用,情况就完全不同了。频繁的建立、关闭连接,会极大的减低系统的性能,因为对于连接的使用成了系统性能的瓶颈。连接复用。通过建立一个数据库连接池以及一套连接使用管理策略,使得一个数据库连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。对于共享资源,有一个很著名的设计模式:资源池。该模式正是为了解决资源频繁分配、释放所造成的问题的。把该模式应用到数据库连接管理领域,就是建立一个数据库连接池,提供一套高效的连接分配、使用策略,最终目标是实现连接的高效、安全的复用。数据库连接池的基本原理是在内部对象池中维护一定数量的数据库连接,并对外暴露数据库连接获取和返回方法。如:外部使用者可通过getConnection 方法获取连接,使用完毕后再通过releaseConnection 方法将连接返回,注意此时连接并没有关闭,而是由连接池管理器回收,并为下一次使用做好准备。数据库连接池技术带来的优势:1. 资源重用由于数据库连接得到重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增进了系统运行环境的平稳性(减少内存碎片以及数据库临时进程/线程的数量)。2. 更快的系统响应速度数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。3. 新的资源分配手段对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接的配置,实现数据库连接池技术,几年钱也许还是个新鲜话题,对于目前的业务系统而言,如果设计中还没有考虑到连接池的应用,那么…….快在设计文档中加上这部分的内容吧。某一应用最大可用数据库连接数的限制,避免某一应用独占所有数据库资源。4. 统一的连接管理,避免数据库连接泄漏在较为完备的数据库连接池实现中,可根据预先的连接占用超时设定,强制收回被占用连接。从而避免了常规数据库连接操作中可能出现的资源泄漏。二、一个最小化的数据库连接池实现1.前言数据库应用,在许多软件系统中经常用到,是开发中大型系统不可缺少的辅助。但如果对数据库资源没有很好地管理(如:没有及时回收数据库的游标(ResultSet)、Statement、连接 (Connection)等资源),往往会直接导致系统的稳定。这类不稳定因素,不单单由数据库或者系统本身一方引起,只有系统正式使用后,随着流量、用户的增加,才会逐步显露。在基于Java开发的系统中,JDBC是程序员和数据库打交道的主要途径,提供了完备的数据库操作方法接口。但考虑到规范的适用性,JDBC只提供了最直接的数据库操作规范,对数据库资源管理,如:对物理连接的管理及缓冲,期望第三方应用服务器(Application Server)的提供。本文,以JDBC规范为基础,介绍相关的数据库连接池机制,并就如果以简单的方式,实现有效地管理数据库资源介绍相关实现技术。2.连接池技术背景2.1 JDBCJDBC是一个规范,遵循JDBC接口规范,各个数据库厂家各自实现自己的驱动程序(Driver),如下图所示:数据库连接池的实现及原理应用在获取数据库连接时,需要以URL的方式指定是那种类型的Driver,在获得特定的连接后,可按照固定的接口操作不同类型的数据库,如: 分别获取Statement、执行SQL获得ResultSet等,如下面的例子 :import java.sql.;…DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());Connection dbConn = DriverManager.getConnection(“jdbc:oracle:thin:@127.0.0.1:1521:oracle”,“username”,“password”);Statement st = dbConn.createStatement();ResultSet rs = st.executeQuery(“select * from demo_table”);…some data source operation in herers.close();st.close();dbConn.close();在完成数据操作后,还一定要关闭所有涉及到的数据库资源。这虽然对应用程序的逻辑没有任何影响,但是关键的操作。上面是个简单的例子,如果搀和众多的if-else、exception,资源的管理也难免百密一疏。如同C中的内存泄漏问题,Java系统也同样会面临崩溃的恶运。所以数据库资源的管理依赖于应用系统本身,是不安全、不稳定的一种隐患。2.2 JDBC连接池在标准JDBC对应用的接口中,并没有提供资源的管理方法。所以,缺省的资源管理由应用自己负责。虽然在JDBC规范中,多次提及资源的关闭/回收及其他的合理运用。但最稳妥的方式,还是为应用提供有效的管理手段。所以,JDBC为第三方应用服务器(Application Server)提供了一个由数据库厂家实现的管理标准接口:连接缓冲(connection pooling)。引入了连接池( Connection Pool )的概念 ,也就是以缓冲池的机制管理数据库的资源。JDBC最常用的资源有三类:— Connection: 数据库连接。— Statement: 会话声明。— ResultSet: 结果集游标。分别存在以下的关系 :数据库连接池的实现及原理这是一种“爷—父—子”的关系,对Connection的管理,就是对数据库资源的管理。举个例子: 如果想确定某个数据库连接(Connection)是否超时,则需要确定其(所有的)子Statement是否超时,同样,需要确定所有相关的 ResultSet是否超时;在关闭Connection前,需要关闭所有相关的Statement和ResultSet。因此,连接池(Connection Pool)所起到的作用,不仅仅简单地管理Connection,还涉及到 Statement和ResultSet。2.3 连接池(ConnectionPool)与资源管理ConnectionPool以缓冲池的机制,在一定数量上限范围内,控制管理Connection,Statement和ResultSet。任何数据库的资源是有限的,如果被耗尽,则无法获得更多的数据服务。在大多数情况下,资源的耗尽不是由于应用的正常负载过高,而是程序原因。在实际工作中,数据资源往往是瓶颈资源,不同的应用都会访问同一数据源。其中某个应用耗尽了数据库资源后,意味其他的应用也无法正常运行。因此,ConnectionPool的第一个任务是限制:每个应用或系统可以拥有的最大资源。也就是确定连接池的大小(PoolSize)。ConnectionPool的第二个任务:在连接池的大小(PoolSize)范围内,最大限度地使用资源,缩短数据库访问的使用周期。许多数据库中,连接(Connection)并不是资源的最小单元,控制Statement资源比Connection更重要。以Oracle为例:每申请一个连接(Connection)会在物理网络(如 TCP/IP网络)上建立一个用于通讯的连接,在此连接上还可以申请一定数量的Statement。同一连接可提供的活跃Statement数量可以达到几百。在节约网络资源的同时,缩短了每次会话周期(物理连接的建立是个费时的操作)。但在一般的应用中,多数按照2.1范例操作,这样有10个程序调用,则会产生10次物理连接,每个Statement单独占用一个物理连接,这是极大的资源浪费。 ConnectionPool可以解决这个问题,让几十、几百个Statement只占用同一个物理连接, 发挥数据库原有的优点。通过ConnectionPool对资源的有效管理,应用可以获得的Statement总数到达 :(并发物理连接数)×(每个连接可提供的Statement数量)例如某种数据库可同时建立的物理连接数为 200个,每个连接可同时提供250个Statement,那么ConnectionPool最终为应用提供的并发Statement总数为: 200 × 250 = 50,000个。这是个并发数字,很少有系统会突破这个量级。所以在本节的开始,指出资源的耗尽与应用程序直接管理有关。对资源的优化管理,很大程度上依靠数据库自身的JDBC Driver是否具备。有些数据库的JDBC Driver并不支持Connection与Statement之间的逻辑连接功能,如SQLServer,我们只能等待她自身的更新版本了。对资源的申请、释放、回收、共享和同步,这些管理是复杂精密的。所以,ConnectionPool另一个功能就是,封装这些操作,为应用提供简单的,甚至是不改变应用风格的调用接口。3.简单JDBC连接池的实现根据第二章中原理机制,Snap-ConnectionPool(一种简单快速的连接池工具,可在www.snapbug.net下载)按照部分的JDBC规范,实现了连接池所具备的对数据库资源有效管理功能。3.1 体系描述在JDBC规范中,应用通过驱动接口(Driver Interface)直接方法数据库的资源。为了有效、合理地管理资源,在应用与JDBC Driver之间,增加了连接池: Snap-ConnectionPool。并且通过面向对象的机制,使连接池的大部分操作是透明的。参见下图,Snap-ConnectionPool的体系:数据库连接池的实现及原理图中所示,通过实现JDBC的部分资源对象接口( Connection, Statement, ResultSet ),在 Snap-ConnectionPool内部分别产生三种逻辑资源对象: PooledConnection, PooledStatement和 PooledResultSet。它们也是连接池主要的管理操作对象,并且继承了JDBC中相应的从属关系。这样的体系有以下几个特点:— 透明性。在不改变应用原有的使用JDBC驱动接口的前提下,提供资源管理的服务。应用系统,如同原有的 JDBC,使用连接池提供的逻辑对象资源。简化了应用程序的连接池改造。— 资源封装。复杂的资源管理被封装在 Snap-ConnectionPool内部,不需要应用系统过多的干涉。管理操作的可靠性、安全性由连接池保证。应用的干涉(如:主动关闭资源),只起到优化系统性能的作用,遗漏操作不会带来负面影响。— 资源合理应用。按照JDBC中资源的从属关系,Snap-ConnectionPool不仅对Connection进行缓冲处理,对Statement也有相应的机制处理。在2.3已描述,合理运用Connection和Statement之间的关系,可以更大限度地使用资源。所以,Snap- ConnectionPool封装了Connection资源,通过内部管理PooledConnection,为应用系统提供更多的Statement 资源。— 资源连锁管理。Snap-ConnectionPool包含的三种逻辑对象,继承了JDBC中相应对象之间的从属关系。在内部管理中,也依照从属关系进行连锁管理。例如:判断一个Connection是否超时,需要根据所包含的Statement是否活跃;判断Statement也要根据 ResultSet的活跃程度。3.2 连接池集中管理ConnectionManagerConnectionPool是Snap-ConnectionPool的连接池对象。在Snap-ConnectionPool内部,可以指定多个不同的连接池(ConnectionPool)为应用服务。ConnectionManager管理所有的连接池,每个连接池以不同的名称区别。通过配置文件适应不同的数据库种类。如下图所示:数据库连接池的实现及原理 通过ConnectionManager,可以同时管理多个不同的连接池,提供通一的管理界面。在应用系统中通过 ConnectionManager和相关的配置文件,可以将凌乱散落在各自应用程序中的数据库配置信息(包括:数据库名、用户、密码等信息),集中在一个文件中。便于系统的维护工作。3.3 连接池使用范例 对2.1的标准JDBC的使用范例,改为使用连接池,结果如下:import java.sql.;import net.snapbug.util.dbtool.;//…ConnectionPool dbConn = ConnectionManager.getConnectionPool(“testOracle” );Statement st = dbConn.createStatement();ResultSet rs = st.executeQuery(“select * from demo_table” );//…some data source operationin herers.close();st.close(); 在例子中,Snap-ConnectionPool封装了应用对Connection的管理。只要改变JDBC获取Connection的方法,为获取连接池(ConnectionPool)(粗体部分),其他的数据操作都可以不做修改。按照这样的方式,Snap- ConnectionPool可帮助应用有效地管理数据库资源。如果应用忽视了最后资源的释放: rs.close() 和 st.close(),连接池会通过超时(time-out)机制,自动回收。4.小结 无论是Snap-ConnectionPool还是其他的数据库连接池,都应当具备一下基本功能: -对源数据库资源的保护 -充分利用发挥数据库的有效资源 -简化应用的数据库接口,封闭资源管理。 -对应用遗留资源的自动回收和整理,提高资源的再次利用率。 在这个前提下,应用程序才能投入更多的精力于各自的业务逻辑中。数据库资源也不再成为系统的瓶颈。三、Druid连接池(新版starter)在SpringBoot下的使用1.更新pom.xml<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version></dependency>2.编写application.yml,部分说明写在注释了:spring: application: name: springboot-test-exam1 datasource: # 使用阿里的Druid连接池 type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver # 填写你数据库的url、登录名、密码和数据库名 url: jdbc:mysql://localhost:3306/databaseName?useSSL=false&characterEncoding=utf8 username: root password: root druid: # 连接池的配置信息 # 初始化大小,最小,最大 initial-size: 5 min-idle: 5 maxActive: 20 # 配置获取连接等待超时的时间 maxWait: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 timeBetweenEvictionRunsMillis: 60000 # 配置一个连接在池中最小生存的时间,单位是毫秒 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false # 打开PSCache,并且指定每个连接上PSCache的大小 poolPreparedStatements: true maxPoolPreparedStatementPerConnectionSize: 20 # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,‘wall’用于防火墙 filters: stat,wall,log4j # 通过connectProperties属性来打开mergeSql功能;慢SQL记录 connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 配置DruidStatFilter web-stat-filter: enabled: true url-pattern: “/” exclusions: “.js,.gif,.jpg,.bmp,.png,.css,.ico,/druid/” # 配置DruidStatViewServlet stat-view-servlet: url-pattern: “/druid/” # IP白名单(没有配置或者为空,则允许所有访问) allow: 127.0.0.1,192.168.163.1 # IP黑名单 (存在共同时,deny优先于allow) deny: 192.168.1.73 # 禁用HTML页面上的“Reset All”功能 reset-enable: false # 登录名 login-username: admin # 登录密码 login-password: 123456为了方便使用application.properties的读者,使用下面的配置和上面相同server.port=8080 spring.application.name=springboot-test-exam1 spring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.url=jdbc:mysql://localhost:3306/databaseName?useSSL=false&characterEncoding=utf8spring.datasource.username=rootspring.datasource.password=rootspring.datasource.druid.initial-size=5spring.datasource.druid.min-idle=5spring.datasource.druid.maxActive=20spring.datasource.druid.maxWait=60000spring.datasource.druid.timeBetweenEvictionRunsMillis=60000spring.datasource.druid.minEvictableIdleTimeMillis=300000spring.datasource.druid.validationQuery=SELECT 1 FROM DUALspring.datasource.druid.testWhileIdle=truespring.datasource.druid.testOnBorrow=falsespring.datasource.druid.testOnReturn=falsespring.datasource.druid.poolPreparedStatements=truespring.datasource.druid.maxPoolPreparedStatementPerConnectionSize=20spring.datasource.druid.filters=stat,wall,log4jspring.datasource.druid.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000spring.datasource.druid.web-stat-filter.enabled=truespring.datasource.druid.web-stat-filter.url-pattern=/spring.datasource.druid.web-stat-filter.exclusions=.js,.gif,.jpg,.bmp,.png,.css,*.ico,/druid/*spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*spring.datasource.druid.stat-view-servlet.allow=127.0.0.1,192.168.163.1spring.datasource.druid.stat-view-servlet.deny=192.168.1.73spring.datasource.druid.stat-view-servlet.reset-enable=falsespring.datasource.druid.stat-view-servlet.login-username=adminspring.datasource.druid.stat-view-servlet.login-password=123456运行结果访问:http://localhost:8080/druid/,登录名:admin,密码123456四、参考引用https://blog.csdn.net/weixin_… https://www.cnblogs.com/wym78… ...

February 28, 2019 · 2 min · jiezi

MySQL8.0.14 - 新特性 - InnoDB Parallel Read简述

最近的MySQL8.0.14版本增加了其第一个并行查询特性,可以支持在聚集索引上做SELECT COUNT()和check table操作。本文简单的介绍下这个特性。用法增加了一个session级别参数: innodb_parallel_read_threads要执行并行查询,需要满足如下条件(ref: row_scan_index_for_mysql)无锁查询聚集索引不是Insert…select需要参数设置为>1相关代码入口函数:row_scan_index_for_mysql parallel_select_count_star // for select count() parallel_check_table // for check tableInnoDB里实现了两种查询方式,一种是基于key的(key reader), 根据叶子节点上的值做分区,需要判断可见性;另外一种是基于page的(physical read),根据page no来做分区,无需判断可见性。目前支持的两种查询都是key reader的方式。使用如下代码创建一个reader,并调用接口函数,read()函数里的回调函数包含了如何对获取到的行数据进行处理:Key_reader reader(prebuilt->table, trx, index, prebuilt, n_threads);reader.read(func), 其中func是回调函数,用于告诉线程怎么处理得到的每一行分区并计算线程数分区入口:template <typename T, typename R>typename Reader<T, R>::Ranges Reader<T, R>::partition()流程:搜集btree的最左节点page no从root page开始向下,尝试构建子树:如果该level的page个数不足线程数,继续往下走否则,使用该level, 搜集该level的每个page的最左记录向下直到叶子节点的最左链表如上搜集到的是多条代表自上而下的page no数组,需要根据这些数组创建分区range,这里有两种创建方式:Key_reader::Ranges Key_reader::create_ranges: 基于键值创建分区找到每个链表的叶子节点的第一条记录,存储其cursor作为当前range的起点和上一个range的终点Phy_reader::Ranges Phy_reader::create_ranges:基于物理页创建分区找到每个链表的叶子节点,相邻链表的叶子节点组成一个range线程数取分区数和配置线程数的最小值启动线程启动线程各自扫描: start_parallel_load为每个分区创建context(class Reader::Ctx),加入到队列中实现了一个Lock-free的队列模型,多线程可以并发的从队列中取context: 实现细节在文件include/ut0mpmcbq.h中,对应类 class mpmc_bq, 实现思路见链接线程函数:dberr_t Reader<T, R>::worker(size_t id, Queue &ctxq, Function &f)每取一个分区,调用处理函数去遍历分区:Key_reader::traverse对于获得的每条记录,判断其可见性(共享事务对象trx_t),调用回调函数处理记录(在Key_reader::read()作为参数传递),对于select count(), 就是累加记录的计数器Phy_reader::traverse读取每条非标记删除的记录并调用回调函数处理,无需判断可见性对于异常情况,只返回最后一个context的错误码。该特性只是MySQL在并行查询的第一步,甚至定义了一些接口还没有使用,例如接口函数pread_adapter_scan_get_num_threads, 估计是给未来server层做并行查询使用的。代码里对应两个适配类:Parallel_reader_adapterParallel_partition_reader_adapter另外一个可以用到的地方是创建二级索引,我们知道InnoDB创建二级索引,是先从聚集索引读取记录,生成多个merge file,然后再做归并排序,但无论是生成merge file,还是排序,都可以做到并行化。官方也提到这是未来的一个优化点,相信不久的将来,我们就能看到MySQL更为强大的并行查询功能。ReferenceWL#11720: InnoDB: Parallel read of indexMySQL 8.0.14: A Road to Parallel Query Execution is Wide Open!本文作者:zhaiwx_yinfeng.阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

February 28, 2019 · 1 min · jiezi

回顾 | 开源分布式中间件DBLE社区分享活动总结

1月24日,我们发布了为期30天的「如何获取全国 25场 MySQL 主题大会免费入场券」有奖社区分享活动,希望社区同学能够分享测试或生产环境中DBLE使用上的难题,困惑,创新或收获,分享与DBLE相关的社区故事。在经历春节在内的1个月中我们收到共了3位社区同学的4篇投稿,内容涉及DBLE的基础配置系列,DBLE的进阶使用系列,DBLE实际应用案例分析,每篇内容都是社区同学自己在实际应用中的总结与整理,相信这些内容除了与大家分享外更重要的是对产品的具体使用与认知上的沉淀。案例类 社区投稿 | DBLE和MyCat跨分片查询结果不一致案例分析投稿:杨严豪这篇文章的背景是社区同学刚好发现一条跨节点 join 查询在 DBLE、Mycat 的查询得到的结果不一致这样一个比较有意思的场景,于是准备测试环境进行分析比对并最终验证出了正确的数据来源。Tips:故障及案例分析思路可参考,同时提醒同学们重视数据的正确性基础知识类社区投稿 | DBLE rule.xml 配置解析投稿:余朝飞rule.xml 定义实际用到的拆分算法,熟悉各种拆分区算法的详细配置及其适用场景,方便我们在众多数据拆分场景选择并配置合适的拆分规则,同时这也是试用分库分表中间件的第一步。将表的详细拆分算法写在配置中,这是一种很"傻"的方式,但是这也是万不得已的一种选择,如果不通过配置文件的方式告诉中间件这些信息,那么中间件就无从得知底层具体的数据分布情况,也就达不到我们最终想要的目的了。社区投稿 | DBLE Server.xml 配置解析投稿:余朝飞本文简单介绍了Server.xml中的三个重要的配置段落,分别是DBLE的系统配置,用户配置以及黑白名单功能,针对用户配置则介绍了实际应用场景下的配置以及对应的DML权限配置,并详细介绍了DBLE黑白名单配置实践。Tips:以上两篇文章是社区同学在使用DBLE时对具体配置的理解,虽说是基础知识,但认知偏差或粗心大意很可能会为后期埋雷,建议详读,打牢基础。进阶使用类 社区投稿 | DBLE 自定义拆分算法 投稿:钟悦DBLE默认支持数十种数据拆分算法,基本能满足大部分的社区用户的使用需求;为了满足更广的业务场景,DBLE还支持更加灵活的自定义拆分算法;本文对面向有类似需求的DBLE开发者,提供了一个如何开发和部署自定义的拆分规则的一个指引。Tips:此文属于DBLE的进阶使用,为社区提供了DBLE如何开发和部署自定义的拆分规则的一个指引,非常适用于有类似需求的开发者。▽奖励回顾符合活动规则的投稿即可获得 100 RMB 京东E卡经 DBLE 团队审核优质文章可再额外获得:100 RMB 京东E卡在「爱可生开源社区」官微、官网及多个官方自媒体平台的推广展示全国 25场 MySQL主题大会免费入场券(仅限作者本人,不可转让)甲骨文官方MySQL技术交流大会姜承尧老师的IMG社区嘉年华叶金荣&吴炳锡老师的「3306」(包含但不仅限于以上社区活动)▽奖品展示1.「爱可生开源社区」推送的全国25场社区活动免费入场券请3位同学私信小编(wechat:mg116611)领取2.京东E卡(长这样子~)关于以上3位同学200元的京东E卡已发送,预计2个工作日即到;MySQL主题大会免费入场券请私信领取哦。后期我们会继续开放邀请分享,错过的同学请关注下期活动,稿子可以先攒起来~往期精选| 使用指南开源分布式中间件 DBLE 快速入门指南DBLE 自定义拆分算法DBLE Server.xml 配置解析DBLE Schema.xml 配置解析DBLE rule.xml 配置解析| 案例分析DBLE和Mycat跨分片查询结果不一致案例分析| 社区活动如何获取全国 25场 MySQL 主题大会免费入场券开源分布式中间件DBLE GitHub主页:https://github.com/actiontech…技术交流群:669663113开源数据传输中间件DTLEGitHub主页:https://github.com/actiontech…技术交流群:852990221

February 28, 2019 · 1 min · jiezi

mysql中生成时间维度的存储过程(存储过程示例)

本文主要记录在BI和数据分析过程中碰到的生成时间维度的问题,另外也是一个mysql的存储过程基础示例包含:存储过程基本语法、变量定义、while循环、异常处理以下存储过程生成了以当前日期为基准前后3650天的日期记录sql如下:创建表:CREATE TABLE dim_date ( id int(8) NOT NULL DEFAULT ‘0’, key date NOT NULL DEFAULT ‘0000-00-00’, year int(4) NOT NULL, quarter int(1) NOT NULL, month int(2) NOT NULL, week int(1) NOT NULL COMMENT ‘星期’, weekofyear int(2) NOT NULL COMMENT ‘一年中的第几周’, day int(2) NOT NULL COMMENT ‘日’, dayofyear int(3) NOT NULL COMMENT ‘一年总的第几天’, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8;存储过程:delimiter //DROP PROCEDURE IF EXISTS getAllDate; CREATE PROCEDURE getAllDate()BEGIN DECLARE count int default 0; DECLARE startDay DATE DEFAULT date(now()); DECLARE endDay DATE DEFAULT DATE(NOW()); – 定义异常处理方式 http://www.cnblogs.com/cookiehu/p/4994278.html DECLARE out_status VARCHAR(200) DEFAULT ‘OK’; DECLARE CONTINUE HANDLER FOR 1062 SET out_status=‘Duplicate Entry’; – 异常处理方式完毕 WHILE count<3650 DO INSERT INTO dim_date(id, key, year, quarter, month, week, weekofyear, day, dayofyear) VALUES (cast(DATE_FORMAT(startDay,’%Y%m%d’) as UNSIGNED), startDay, YEAR(startDay), QUARTER(startDay), MONTH(startDay), WEEKDAY(startDay)+1, week(startDay,1), DAY(startDay), DAYOFYEAR(startDay)); set count = count +1; set startDay = DATE_ADD(DATE(now()),INTERVAL count DAY); SET endDay = DATE_SUB(DATE(NOW()),INTERVAL count DAY); INSERT INTO dim_date(id, key, year, quarter, month, week, weekofyear, day, dayofyear) VALUES (cast(DATE_FORMAT(endDay,’%Y%m%d’) as UNSIGNED), endDay, YEAR(endDay), QUARTER(endDay), MONTH(endDay), WEEKDAY(endDay)+1, week(endDay,1), DAY(endDay), DAYOFYEAR(endDay)); END WHILE;END//delimiter ;调用存储过程– TRUNCATE table dim_date;call getAllDate(); ...

February 28, 2019 · 1 min · jiezi

MySQL基础部分总结

MySQL1、选择数据库use dbnameshow databases;2、数据表show tablesmysql> show columns from customers;mysql> desc customers;3、show 语句show statusshow create databasesshow create tableshow grants4、select 检索4.1.1版本后不再区分大小写,但是为了容易阅读和调试,建议还是使用。mysql> select cust_name from customers;mysql> select cust_name cust_status from customers;mysql> select distinct vend_id from products;mysql> select prod_name from products limit 5;mysql> select prod_name from products limit 5,5;//分页问题从行0开始计数,limit5,5是从第5行开始(不包括第五行),取5行,结果是:6:10行因为这个很容易引起误解,所以MySQL5开始支持另一个语法:limit 4 offset 3,意思是从行3开始取4行,等同于limit 3,44-1、排序数据//单个字段排序mysql> select prod_name from products order by prod_name;//多个字段排序,如果第一个字段全部唯一则第二个字段就不会生效mysql> select prod_id,prod_price,prod_name from products order by prod_price ,prod_name;4-2、指定排序方向desc 降序asc 升序-默认注意顺序,from>ordrr by >limitmysql> select prod_id,prod_price,prod_name from products order by prod_price desc;mysql> select prod_id,prod_price,prod_name from products order by prod_price asc;mysql> select prod_price from products order by prod_price desc limit 1;5、where 条件相关操作符:= 等于<> 不等于!= 不等于< 小于> 大于>= 大于或者等于<= 小于或等于between 两者之间 andand 的优先级大于or,需要处理or,则需要括号mysql> select prod_price,prod_name from products where prod_price = 2.50;mysql> select prod_price,prod_name from products where prod_price between 5 and 10;// IS NULLmysql> select cust_id from customers where cust_email is null;重点:空值检查空值既是:NULLMySQL中判断是否是空值的子句是: IS NULLexample: mysql> select cust_id FROM customers where cust_email IS NULL; +———+| cust_id |+———+| 10002 || 10005 |+———+6、where 数据过滤(logical operator)逻辑操作符:and - ormysql> select prod_id,prod_price,prod_name from products where vend_id = 1003 and prod_price<= 10;mysql> select prod_id,prod_price,prod_name from products where vend_id = 1003 or vend_id = 1002;运算符优先级问题: 下列SQL中实际先运行 vend_id = 1002 and prod_price >= 10;,再运行vend_id = 1003.因为and的优先级大于or,如果要按理想执行,加括号!mysql> select prod_id,prod_price,prod_name from products where vend_id = 1003 or vend_id = 1002 and prod_price >= 10;mysql> select prod_id,prod_price,prod_name from products where (vend_id = 1003 or vend_id = 1002 )and prod_price >= 10;6-1、 in操作符 (not in)mysql> select prod_id,prod_price,prod_name from products where vend_id in (1002,1003) order by prod_name;6-2、 or操作符mysql> select prod_id,prod_price,prod_name from products where vend_id not in (1002,1003) order by prod_name;7、用通配符过滤like 和 _ 的区别是后者只能匹配一个字符7-1、like**注意NULL 虽然似乎 % 通配符可以匹配任何东西,但有一个例外,即 NULL 。即使是 WHERE prod_name LIKE ‘%’ 也不能匹配用值 NULL 作为产品名的行。*mysql> select prod_id,prod_price,prod_name from products where prod_name LIKE ‘jet%’;mysql> select prod_id,prod_price,prod_name from products where prod_name LIKE ‘%anv%’;7-2、mysql> select prod_id,prod_price,prod_name from products where prod_name LIKE ‘ ton anvil’;8、正则表达式like是匹配全部,REGEXP可以匹配全部和部分mysql> select prod_name from products where prod_name =‘JetPack 1000’;+————–+| prod_name |+————–+| JetPack 1000 |+————–+1 row in set (0.00 sec)mysql> select prod_name from products where prod_name REGEXP ‘1000’;+————–+| prod_name |+————–+| JetPack 1000 |+————–+1 row in set (0.00 sec)默认不区分大小写,需要区分大小写binarymysql> select prod_name from products where prod_name REGEXP ‘jetpack .000’;mysql> select prod_name from products where prod_name REGEXP binary ‘JetPack .000’;10、计算字段concat 合并 讲两个字段合并成一个新的字段mysql> select concat (vend_name , ‘C’,vend_country,’)’) from vendors order by vend_name;+——————————————-+| concat (vend_name , ‘C’,vend_country,’)’) |+——————————————-+| ACMECUSA) || Anvils R UsCUSA) || Furball Inc.CUSA) || Jet SetCEngland) || Jouets Et OursCFrance) || LT SuppliesCUSA) |+——————————————-+6 rows in set (0.00 sec)rtrim (ltrim ,trim) 去掉空格mysql> select concat (rtrim(vend_name) , ‘C’,vend_country,’)’) from vendors order by vend_name;as 别名mysql> select concat (rtrim(vend_name) , ‘(’,rtrim(vend_country),’)’) as vend_title from vendors order by vend__name;计算+、-、 、\mysql> select quantityitem_price as expand_price from orderitems where order_num =20005;11、函数trim、ltrim、rtrim 去掉空值Upper 转为大写mysql> select vend_name,upper(vend_name) as ven_name_upcase from vendors order by vend_name;11-2 时间函数AddDate() 增加一个日期(天、周等)AddTime() 增加一个时间(时、分等)CurDate() 返回当前日期CurTime() 返回当前时间==Date() 返回日期时间的日期部分==DateDiff() 计算两个日期之差Date_Add() 高度灵活的日期运算函数Date_Format() 返回一个格式化的日期或时间串Day() 返回一个日期的天数部分DayOfWeek() 对于一个日期,返回对应的星期几Hour() 返回一个时间的小时部分Minute() 返回一个时间的分钟部分Month() 返回一个日期的月份部分Now() 返回当前日期和时间Second() 返回一个时间的秒部分Time() 返回一个日期时间的时间部分Year() 返回一个日期的年份部分取9月某一天的数据mysql> select cust_id,order_num from orders where Date(order_date) = ‘2005-09-01’; +———+———–+| cust_id | order_num |+———+———–+| 10001 | 20005 |+———+———–+1 row in set (0.00 sec)取9月整个月的订单mysql> select cust_id,order_num from orders where Date(order_date) between ‘2005-09-01’ and ‘2005-09-30’; +———+———–+| cust_id | order_num |+———+———–+| 10001 | 20005 || 10003 | 20006 || 10004 | 20007 |+———+———–+3 rows in set (0.00 sec)mysql> select cust_id,order_num from orders where Year(order_date) and month(order_date) = 9;+———+———–+| cust_id | order_num |+———+———–+| 10001 | 20005 || 10003 | 20006 || 10004 | 20007 |+———+———–+3 rows in set (0.00 sec)11-4 数值处理函数Abs() 返回一个数的绝对值Cos() 返回一个角度的余弦Exp() 返回一个数的指数值Mod() 返回除操作的余数Pi() 返回圆周率Rand() 返回一个随机数Sin() 返回一个角度的正弦Sqrt() 返回一个数的平方根Tan() 返回一个角度的正切11-5 聚集函数AVG() 返回某列的平均值COUNT() 返回某列的行数MAX() 返回某列的最大值MIN() 返回某列的最小值SUM() 返回某列值之和DISTINCTmysql> select avg(prod_price) as avg_price from products;分组数据GROUP BY子句和HAVING子句mysql> select vend_id,count() as num_prods from products group by vend_id; +———+———–+| vend_id | num_prods |+———+———–+| 1001 | 3 || 1002 | 2 || 1003 | 7 || 1005 | 2 |+———+———–+4 rows in set (0.00 sec)mysql> select vend_id,count() as num_prods from products group by vend_id with rollup;+———+———–+| vend_id | num_prods |+———+———–+| 1001 | 3 || 1002 | 2 || 1003 | 7 || 1005 | 2 || NULL | 14 |+———+———–+5 rows in set (0.00 sec)having唯一的差别是 WHERE过滤行,而HAVING过滤分组。WHERE在数据 分组前进行过滤,HAVING在数据分组后进行过滤mysql> select vend_id,count() as num_prods from products group by vend_id having count()>=2;+———+———–+| vend_id | num_prods |+———+———–+| 1001 | 3 || 1002 | 2 || 1003 | 7 || 1005 | 2 |+———+———–+4 rows in set (0.00 sec)mysql> select vend_id,count() as num_prods from products where prod_price>=10 group by vend_id having count()>=2; +———+———–+| vend_id | num_prods |+———+———–+| 1003 | 4 || 1005 | 2 |+———+———–+2 rows in set (0.00 sec)mysql> select order_num ,sum(quantityitem_price) as ordertotal from orderitems -> group by order_num-> having sum(quantity*item_price) >= 50-> order by ordertotal;+———–+————+| order_num | ordertotal |+———–+————+| 20006 | 55.00 || 20008 | 125.00 || 20005 | 149.87 || 20007 | 1000.00 |+———–+————+4 rows in set (0.00 sec)顺序selectfromwheregroup byhavingorder bylimit12 子查询mysql> select cust_id from orders where order_num in (select order_num from orderitems where prod_id =‘TNT2’);+———+| cust_id |+———+| 10001 || 10004 |+———+15 连接表笛卡儿积(cartesian product)如果将两个表同时作为数据源(from后的表名),不加任何的匹配条件,那么产生的结果集就是一个迪卡尔积。 迪卡尔积的结果没有意义,但是迪卡尔积是联合查询、连接查询的基础。1. 交叉连接 cross join使用表A中的1条记录去表B中连接所有的记录,就是笛卡尔积2. 内连接select 字段列表 from 表A 【inner】 join 表B ,匹配到的成功的记录3. 外连接 分为左连接和右连接,左连接保留左边的所有,右边匹配到的部分4. using关键字在进行连接时,如果进行连接的两个字段的名子相同,则可以使用using using(‘cid’)当前笔记出自 《MySQL必知必会》 ...

February 28, 2019 · 4 min · jiezi

《高性能Mysql》备忘录

第一章节 Schema与数据类型优化1. 准则此文所述均基于InnoDB由于字符集和校对规则(排序规则),字符串比整数操作代价更高在索引列中使用NOT NULL(稀疏数据除外,其有很好的空间效率)在非索引列中使用NOT NULL带来的性能提升较小DATETIME 和 TIMESTAMP 的区别(DATE精确到天,TIME最大精度为天)默认值分别为NULL,当前时间分别使用8字节,4字节存储区间分别为1970-01-01 00:00:01.000000 - 2038-01-19 03:14:07.999999,1000-01-01 00:00:00.000000 - 9999-12-31 23:59:59.999999TIMESTAMP在UPDATE时未指定,则更新为当前时间TIMESTAMP随时区自动更新2.整数

February 28, 2019 · 1 min · jiezi

mysql锁(Innodb)

锁的隔离级别事务隔离级别脏读不可重复读幻读读未提交(read-uncommitted)是是是读已提交(read-committed)否是是可重复读(repeatable-read)否否是串行化(serializable))否否否锁的分类粒度划分行锁:Record Lock、Gap Lock、Next-Key Lock表锁页面锁级别划分读锁(Share Locks,S锁)写锁(Exclusive Locks,简称X锁)意向读锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁意向写锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁是否兼容XIXSISX冲突冲突冲突冲突IX冲突兼容冲突兼容S冲突冲突兼容兼容IS冲突兼容兼容兼容加锁流程加锁的基本单位是 next-key lock,锁是加在索引上的查找过程中,访问到的对象才会加锁索引上的等值查询,给唯一索引加锁的时候,next-key lock退化为行锁索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁唯一索引上的范围查询会访问到不满足条件的第一个值为止。

February 27, 2019 · 1 min · jiezi

mysql-事务管理(进阶)--待续

请口述以下问题:什么是事务到特性,具体说说你到理解请举一个案例描述为什么要用到事务(转账)事务的隔离级别待续。。。1、事务到特性(ACID)Atomicty 原子性事务不可再分。现实中的一个需求,由多条SQL实现时,现实中的一件事只有两种结果,要么成功要么失败。事务机制就实现了将多条sql“当成”一条sql来执行,通过这种机制加上人为的判断,实现多条sql要么都成功,要第都失败。Consistence 一致性事务的执行过程中,对数据表的影响是没有的。Isolation 隔离性当一个事件的执行,不会影响其他客户端的数据表中查询到的结果。Duration 永久性当一个事务执行交,其影响就是永久的。A->B(A给B转账100) 正常mysql流程:A账户B账户A-100B+100可能情况:情况A账户B账户第一种A-100B+100第二种A-100B第三种AB+100第四种AB2、隔离性与隔离级别当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。SQL标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到。事务没提交,做等变更别等事务能看到读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。事务提交了,它做的变更别的事务才能看到可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。串行化,顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。读也加锁啊写也加锁,读写锁冲突,后面的事务等前面事务执行完成再执行,一串隔离得越严实,效率就会越低。需要找一个平衡点。

February 27, 2019 · 1 min · jiezi

MySql 简易安装指南

本文为[原创]文章,转载请标明出处。原文链接:https://weyunx.com/2019/01/31…原文出自微云的技术博客在 Centos7 系统下使用yum命令安装 MySql ,首先先在官网这里查看资源包。# 根据实际情况替换 mysql80-community-release-el7-2.noarch.rpm 为最新版本wget http://repo.mysql.com/mysql80-community-release-el7-2.noarch.rpm# 安装rpm -ivh mysql*.rpmyum updateyum install mysql-server# 设置权限chmod 777 mysqld.logchown mysql:mysql -R /var/lib/mysql# 初始化mysqld –initialize# 启动systemctl start mysqld# 查看初始密码cat /var/log/mysqld.log | grep password例:2019-01-30T12:53:55.670725Z 5 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: 22g!lpwRac%t记录密码22g!lpwRac%t进行登录:# 登录 MySql,并输入密码mysql -u root -p# 修改密码为123mysql> set password for root@localhost = ‘123’;未完待续…

February 27, 2019 · 1 min · jiezi

Spring Data JPA 必须掌握的 20+ 个查询关键字

微信公众号:一个优秀的废人如有问题或建议,请后台留言,我会尽力解决你的问题。前言又是小师弟的投稿,确是一个喜欢技术的朋友。以下为原文:今天闲的无聊看 Spring Data JPA 官方文档的时候,发现并没有完整的 Jpa 关键字语义翻译。所以今天写了一篇中文文档,如果有错误,望大家轻喷。以下为官方图片以及示例代码和注释 :首先参照官方文档创建指定数据库CREATE TABLE demo_jpa ( id int(11) NOT NULL AUTO_INCREMENT, first_name varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, last_name varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, sex varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, email varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, age int(12) NOT NULL, PRIMARY KEY (id) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;示例代码及注释<参照以上顺序>/** * @Author: EvilSay * @Date: 2019/2/25 16:15 /public interface DemoJpaRepositories extends JpaRepository<DemoJpa,Integer> { //根据firstName与LastName查找(两者必须在数据库有) DemoJpa findByFirstNameAndLastName(String firstName, String lastName); //根据firstName或LastName查找(两者其一有就行) DemoJpa findByLastNameOrFirstName(String lastName,String firstName); //根据firstName查找它是否存在数据库里<类似与以下关键字> //DemoJpa findByFirstName(String firstName); DemoJpa findByFirstNameIs(String firstName); //在Age数值age到age2之间的数据 List<DemoJpa> findByAgeBetween(Integer age, Integer age2); //小于指定age数值之间的数据 List<DemoJpa> findByAgeLessThan(Integer age); //小于等于指定age数值的数据 List<DemoJpa> findByAgeLessThanEqual(Integer age); //大于指定age数值之间的数据 List<DemoJpa> findByAgeGreaterThan(Integer age); //大于或等于指定age数值之间的数据 List<DemoJpa> findByAgeGreaterThanEqual(Integer age); //在指定age数值之前的数据类似关键字<LessThan> List<DemoJpa> findByAgeAfter(Integer age); //在指定age数值之后的数据类似关键字<GreaterThan> List<DemoJpa> findByAgeBefore(Integer age); //返回age字段为空的数据 List<DemoJpa> findByAgeIsNull(); //返回age字段不为空的数据 List<DemoJpa> findByAgeNotNull(); /* * 该关键字我一度以为是类似数据库的模糊查询, * 但是我去官方文档看到它里面并没有通配符。 * 所以我觉得它类似 * DemoJpa findByFirstName(String firstName); * @see https://docs.spring.io/spring-data/jpa/docs/2.1.5.RELEASE/reference/html/#jpa.repositories */ DemoJpa findByFirstNameLike(String firstName); //同上 List<DemoJpa> findByFirstNameNotLike(String firstName); //查找数据库中指定类似的名字(如:输入一个名字"M" Jpa会返回多个包含M开头的名字的数据源)<类似数据库模糊查询> List<DemoJpa> findByFirstNameStartingWith(String firstName); //查找数据库中指定不类似的名字(同上) List<DemoJpa> findByFirstNameEndingWith(String firstName); //查找包含的指定数据源(这个与以上两个字段不同的地方在与它必须输入完整的数据才可以查询) List<DemoJpa> findByFirstNameContaining(String firstName); //根据age选取所有的数据源并按照LastName进行升序排序 List<DemoJpa> findByAgeOrderByLastName(Integer age); //返回不是指定age的所有数据 List<DemoJpa> findByAgeNot(Integer age); //查找包含多个指定age返回的数据 List<DemoJpa> findByAgeIn(List<Integer> age);}单元测试<已经全部通过>@SpringBootTest@RunWith(SpringRunner.class)@Slf4jpublic class DemoJpaRepositoriesTest { @Autowired private DemoJpaRepositories repositories; @Test public void findByFirstNameAndLastName() { DemoJpa demoJpa = repositories.findByFirstNameAndLastName(“May”, “Eden”); Assert.assertEquals(demoJpa.getFirstName(),“May”); } @Test public void findByLastNameOrFirstName() { DemoJpa demoJpa = repositories.findByLastNameOrFirstName(“Geordie”, “Eden”); Assert.assertNotEquals(demoJpa.getLastName(),“Eden”); } @Test public void findByFirstNameIs() { DemoJpa demoJpa = repositories.findByFirstNameIs(“amy”); Assert.assertNull(demoJpa); } @Test public void findByAgeBetween() { List<DemoJpa> demoJpaList = repositories.findByAgeBetween(15, 17); Assert.assertEquals(3,demoJpaList.size()); } @Test public void findByAgeLessThan() { List<DemoJpa> demoJpaList = repositories.findByAgeLessThan(17); Assert.assertEquals(2,demoJpaList.size()); } @Test public void findByAgeLessThanEqual() { List<DemoJpa> demoJpaList = repositories.findByAgeLessThanEqual(17); Assert.assertEquals(3,demoJpaList.size()); } @Test public void findByAgeGreaterThan() { List<DemoJpa> demoJpaList = repositories.findByAgeGreaterThan(17); Assert.assertEquals(2,demoJpaList.size()); } @Test public void findByAgeGreaterThanEqual() { List<DemoJpa> demoJpaList = repositories.findByAgeGreaterThanEqual(17); Assert.assertEquals(3,demoJpaList.size()); } @Test public void findByAgeAfter() { List<DemoJpa> demoJpaList = repositories.findByAgeAfter(17); Assert.assertEquals(2,demoJpaList.size()); } @Test public void findByAgeBefore() { List<DemoJpa> demoJpaList = repositories.findByAgeBefore(17); Assert.assertEquals(2,demoJpaList.size()); } @Test public void findByAgeIsNull() { List<DemoJpa> demoJpaList = repositories.findByAgeIsNull(); Assert.assertEquals(0,demoJpaList.size()); } @Test public void findByAgeNotNull() { List<DemoJpa> demoJpaList = repositories.findByAgeNotNull(); Assert.assertEquals(5,demoJpaList.size()); } @Test public void findByFirstNameLike() { DemoJpa demoJpa = repositories.findByFirstNameLike(“May”); Assert.assertNotNull(demoJpa); } @Test public void findByFirstNameNotLike() { } @Test public void findByFirstNameStartingWith() { List<DemoJpa> demoJpaList = repositories.findByFirstNameStartingWith(“May”); Assert.assertEquals(2,demoJpaList.size()); } @Test public void findByFirstNameEndingWith() { List<DemoJpa> demoJpaList = repositories.findByFirstNameEndingWith(“Evil”); Assert.assertEquals(0,demoJpaList.size()); } @Test public void findByFirstNameContaining() { List<DemoJpa> demoJpaList = repositories.findByFirstNameContaining(“hack”); Assert.assertEquals(0,demoJpaList.size()); } @Test public void findByAgeOrderByLastName() { List<DemoJpa> demoJpaList = repositories.findByAgeOrderByLastName(18); for (DemoJpa demoJpaL : demoJpaList){ log.info(“数据结果”+demoJpaL.toString()); } } @Test public void findByAgeNot() { List<DemoJpa> demoJpaList = repositories.findByAgeNot(20); Assert.assertEquals(5,demoJpaList.size()); } @Test public void findByAgeIn() { List<DemoJpa> demoJpaList = repositories.findByAgeIn(Arrays.asList(15, 16)); Assert.assertEquals(2,demoJpaList.size()); }}后语如果本文对你哪怕有一丁点帮助,请帮忙点好看。你的好看是我坚持写作的动力。另外,关注之后在发送 1024 可领取免费学习资料。资料内容详情请看这篇旧文:Python、C++、Java、Linux、Go、前端、算法资料分享 ...

February 27, 2019 · 2 min · jiezi