hapi框架搭建记录三Joi数据校验和Sequelize数据迁移填充数据

hapi框架,用官网的简介来说就是:Hapi是构建应用程序和服务的丰富框架,它使开发人员能够专注于编写可重用的应用程序逻辑,而不是花时间构建基础设施。用自己的话简单来说,就是个类似express,koa之类的node服务基础框架。此篇博客是在阅读过掘金小册的《基于 hapi 的 Node.js 小程序后端开发实践指南》并实践操作后,以此记录实践过程和踩过的坑。感兴趣读者可支持阅读掘金小册原版的内容。Joi数据校验1.安装joi更多校验规则参考文档:https://www.npmjs.com/package...npm i @hapi/joi2.配合swagger只需要在路由的config配置校验信息,如./routes/test.js 新增多一条测试接口{ method: "GET", path: `/${GROUP_NAME}/get`, handler: (request, h) => { return { data: request.query }; }, config: { tags: ["api", GROUP_NAME], description: "测试get提交", notes: "配置Implementation说明文", validate: { query: { num: Joi.number() .integer() .required() .description("数字") .error(new Error("num参数错误")) } } } },3.swagger接口文档 Sequence的使用1.安装此案例链接mysql数据库,所以安装mysq2npm i sequelize-cli -Dnpm i sequelizenpm i mysql22.使用到的目录和文件├── config # 项目配置目录| ├── config.js # 数据库连接的配置(区分开发/生产环境)| ├── index.js # 暴露部分配置信息给app.js使用├── models # 数据库 model| ├── index.js # 数据库连接的样板代码├── migrations # 数据迁移的目录├── seeders # 数据填充的目录├── .env # 配置3.env配置数据库信息注意需要在本地mysql中创建对应的数据库, 如:hapi_db# 域名配置信息HOST = 127.0.0.1PORT = 3303# MySQL 数据库连接配置信息MYSQL_HOST = 127.0.0.1MYSQL_PORT = 3306MYSQL_DB_NAME = hapi_dbMYSQL_USERNAME = rootMYSQL_PASSWORD = 1234564. 暴露给入口文件使用的数据信息新建./config/index.jsconst { env } = process;module.exports = { host: env.HOST, port: env.PORT};5. 模式model使用新建./config/config.js给后台将要创建的数据库模型model使用的数据信息// 根据环境动态加载数据库配置信息// 本页面内容主要给数据库连接使用(../models/index.js)if (process.env.NODE_ENV == "production") { require("env2")("./.env.prod");} else { require("env2")("./.env");}const { env } = process;module.exports = { development: { username: env.MYSQL_USERNAME, password: env.MYSQL_PASSWORD, database: env.MYSQL_DB_NAME, host: env.MYSQL_HOST, port: env.MYSQL_PORT, dialect: "mysql", operatorsAliases: false }, production: { username: env.MYSQL_USERNAME, password: env.MYSQL_PASSWORD, database: env.MYSQL_DB_NAME, host: env.MYSQL_HOST, port: env.MYSQL_PORT, dialect: "mysql", operatorsAliases: false }};使用sequelize创建表1.数据库的创建sequelize提供创建数据库的命令,但最后还需要自己动手修改数据库格式为utf-8,所以还是手动直接创建方便2.migration创建表创建一个商品表 shops根据以下命令,将会自动创建./migration/2019XXXXXXXXX-create-shops-table.js其实是可以直接在mysql创建表并填写数据,但这里使用migration来创建主要为了留下对表的创建修改等记录,未来有查询依据。就类比记录创建日志。./node_modules/.bin/sequelize migration:create --name create-shops-tablexxxx-crate.shops.table.js"use strict";module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.createTable("shops", { id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true }, name: { type: Sequelize.STRING, allowNull: false }, thumb_url: Sequelize.STRING, created_at: Sequelize.DATE, updated_at: Sequelize.DATE }); }, down: (queryInterface, Sequelize) => {face.dropTable("shops"); }};3. 向mysql数据库中创建表在项目根目录下执行命令./node_modules/.bin/sequelize db:migrate成功创建示例 ...

April 28, 2019 · 2 min · jiezi

百度云虚拟机ubuntu164安装wordpress

基本环境的安装1.安装Apache apt-get install apache2 2.查看状态 service apache2 restart/status/start/stop 3.关闭防火墙80端口限制 ufw allow 804.安装MySQL apt-get install mysql-server mysql-client 5.查看状态 service mysql retart/status/start/stop6.关闭防火墙3306端口限制 ufw allow 33067.安装PHP apt-get install php7.0 apt-get install libapache2-mod-php7.0 apt-get install php7.0-mysql8.重启服务 service apache2 restart service mysql restart9.安装phpMyAdmin sudo apt-get install phpmyadmin 安装时:空格选择apache2,enter确定,下一步配置数据库,输入密码。10.创建phpMyAdmin快捷方式 sudo ln -s /usr/share/phpmyadmin /var/www/html 11.启用Apache mod_rewrite模块,后面修改wordpress链接时会用 sudo a2enmod rewrite 12.重启服务 service php7.0-fpm restart 提示服务没找到?不去管它service apache2 restart13.配置Apache vim /etc/apache2/apache2.conf14.添加以下信息: AddType application/x-httpd-php .php .htm .html AddDefaultCharset UTF-815.重启Apache服务 ...

April 28, 2019 · 1 min · jiezi

hapi框架搭建记录二路由改造和生成接口文档

hapi框架,用官网的简介来说就是:Hapi是构建应用程序和服务的丰富框架,它使开发人员能够专注于编写可重用的应用程序逻辑,而不是花时间构建基础设施。用自己的话简单来说,就是个类似express,koa之类的node服务基础框架。此篇博客是在阅读过掘金小册的《基于 hapi 的 Node.js 小程序后端开发实践指南》并实践操作后,以此记录实践过程和踩过的坑。感兴趣读者可支持阅读掘金小册原版的内容。路由汇总1. 在./routes目录下新建index.js作为路由的汇总,这样以后只管在./routes下新建文件即可"use strict";const fs = require("fs");const path = require("path");const basename = path.basename(__filename); // 当前文件名let routeArr = [];// 同步读取当前目录,并过滤除了当前文件的文件名数组fs.readdirSync(__dirname) .filter(file => { // 过滤掉隐藏文件、当前文件、非js文件, 返回当前目录下文件名称数组 return ( file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".js" ); }) .forEach(file => { // 引入路由模块 let arr = require(path.join(__dirname, file)); // 汇总 routeArr.push(...arr); });module.exports = routeArr;测试路由1.新建test.js文件作为测试const GROUP_NAME = "test";module.exports = [ // 纯测试返回 接口 { method: "GET", path: `/${GROUP_NAME}`, handler: (request, h) => { const data = { message: "test" }; // 响应数据方式: // return h.response(data).code(200); return data; } },]2.修改app.js ...

April 28, 2019 · 1 min · jiezi

hapi框架搭建记录一初始化项目

hapi框架,用官网的简介来说就是:Hapi是构建应用程序和服务的丰富框架,它使开发人员能够专注于编写可重用的应用程序逻辑,而不是花时间构建基础设施。用自己的话简单来说,就是个类似express,koa之类的node服务基础框架。此篇博客是在阅读过掘金小册的《基于 hapi 的 Node.js 小程序后端开发实践指南》并实践操作后,以此记录实践过程和踩过的坑。感兴趣读者可支持阅读掘金小册原版的内容。第一个helloworld1.在新建项目目录中初始化配置文件npm init2.安装hapinpm i hapi3.全局安装热部署工具supervisor(类似nodemon)npm install supervisor -g4.新建app.js,可从官网案例中抄示例代码'use strict';const Hapi = require('hapi');const init = async () => { const server = Hapi.server({ port: 3000, host: 'localhost' }); server.route({ method: 'GET', path:'/', handler: (request, h) => { return 'Hello World!'; } }); await server.start(); console.log('Server running on %ss', server.info.uri);};process.on('unhandledRejection', (err) => { console.log(err); process.exit(1);});init();5.终端运行项目 6.浏览器测试效果 目录结构划分 (PS: 根据需要可以将业务逻辑代码再划分到controllers层)├── config # 项目配置目录├── logs # 输出日志├── migrations # 创建数据库文件├── models # 数据库 model├── node_modules # node.js 的依赖目录├── plugins # 插件目录├── routes # 路由目录├── seeders # 初始化表数据文件├── test # 测试类├── utils # 工具类相关目录├── .env # 配置文件├── app.js # 项目入口文件├── package.json # JS 项目工程依赖库├── readme.md # 项目工程如何被使用的说明手册配置全局环境信息1. env配置示例配置env的意义在于,不同的环境下域名数据库等信息不一定一致,通过配置手段加载读取更灵活。同时全局环境直接获取。# 域名配置信息HOST = 127.0.0.1PORT = 3303# MySQL 数据库连接配置信息MYSQL_HOST = 127.0.0.1MYSQL_PORT = 3306MYSQL_DB_NAME = database_nameMYSQL_USERNAME = database_usernameMYSQL_PASSWORD = database_password# JWT 自定义secretJWT_SECRET = your_secret# 微信小程序配置WX_APPID = your-app-id # 微信小程序appidWX_SECRET = your-secret # 微信小程序密码WX_MCHID = your-mchid # 支付商户号WX_PAY_API_KEY = your-pay-api-key # 微信支付的 api key2.安装env2通过此依赖可在js代码中直接读取.env配置的参数信息(如:读取端口号 process.env.PORT)npm i env23.示例获取 ...

April 28, 2019 · 1 min · jiezi

MySQL-批量修改数据表编码及字符集

当需要修改数据库编码和字符集时,通常需要对其下属的所有表及表中所有字段进行修改。以下备注批量修改方案( 以修改为 utf8mb4_bin 为例,注意将 db_name 换为真实的数据库名 )。1. 修改数据库编码及字符集这一步比较简单,直接执行即可: ALTER DATABASE db_name CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin;2. 修改数据表与表中字段的编码及字符集这里需要两步。 首先,需要利用语句,生成所有实际执行的语句: SELECT CONCAT("ALTER TABLE `", TABLE_NAME,"` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;") AS target_tablesFROM INFORMATION_SCHEMA.TABLESWHERE TABLE_SCHEMA="db_name"AND TABLE_TYPE="BASE TABLE"此语句会基于 MySQL 的元数据表,得到一组可直接执行的 SQL 列表,如下: ALTER TABLE `table1` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;ALTER TABLE `table2` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;ALTER TABLE `table3` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;ALTER TABLE `table4` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;ALTER TABLE `table5` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;ALTER TABLE `table6` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;其中,table1 到 table6 即为数据库中的所有数据表。 ...

April 27, 2019 · 1 min · jiezi

数据库MySQL锁机制热备分表

欢迎关注公众号:【爱编码】如果有需要后台回复2019赠送1T的学习资料哦!! 注:本文大都来自互联网,文字较多,基本是概念,若想深入了解,还需各位自己找文章研究。 表锁和行锁机制表锁(MyISAM和InnoDB)表锁的优势:开销小;加锁快;无死锁表锁的劣势:锁粒度大,发生锁冲突的概率高,并发处理能力低加锁的方式:自动加锁。查询操作(SELECT),会自动给涉及的所有表加读锁,更新操作(UPDATE、DELETE、INSERT),会自动给涉及的表加写锁。也可以显示加锁: 共享读锁:lock table tableName read;独占写锁:lock table tableName write;批量解锁:unlock tables;什么场景下用表锁InnoDB默认采用行锁,在未使用索引字段查询时升级为表锁。即便你在条件中使用了索引字段,MySQL会根据自身的执行计划,考虑是否使用索引(所以explain命令中会有possible_key 和 key)。如果MySQL认为全表扫描效率更高,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。 第一种情况:全表更新。事务需要更新大部分或全部数据,且表又比较大。若使用行锁,会导致事务执行效率低,从而可能造成其他事务长时间锁等待和更多的锁冲突。 第二种情况:多表查询。事务涉及多个表,比较复杂的关联查询,很可能引起死锁,造成大量事务回滚。这种情况若能一次性锁定事务涉及的表,从而可以避免死锁、减少数据库因事务回滚带来的开销。 行锁(InnoDB的行锁)行锁的劣势:开销大;加锁慢;会出现死锁行锁的优势:锁的粒度小,发生锁冲突的概率低;处理并发的能力强加锁的方式:自动加锁。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁;对于普通SELECT语句,InnoDB不会加任何锁;当然我们也可以显示的加锁: 共享锁:select * from tableName where … + lock in share more排他锁:select * from tableName where … + for update行锁优化1 尽可能让所有数据检索都通过索引来完成,避免无索引行或索引失效导致行锁升级为表锁。2 尽可能避免间隙锁带来的性能下降,减少或使用合理的检索范围。3 尽可能减少事务的粒度,比如控制事务大小,而从减少锁定资源量和时间长度,从而减少锁的竞争等,提供性能。4 尽可能低级别事务隔离,隔离级别越高,并发的处理能力越低。 InnoDB和MyISAM的最大不同点有两个:一,InnoDB支持事务(transaction);二,默认采用行级锁。加锁可以保证事务的一致性,可谓是有人(锁)的地方,就有江湖(事务);我们先简单了解一下事务知识。 MySQL 事务事务是由一组SQL语句组成的逻辑处理单元,事务具有ACID属性。原子性(Atomicity):事务是一个原子操作单元。在当时原子是不可分割的最小元素,其对数据的修改,要么全部成功,要么全部都不成功。一致性(Consistent):事务开始到结束的时间段内,数据都必须保持一致状态。隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的”独立”环境执行。持久性(Durable):事务完成后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。 事务隔离级别脏读,不可重复读,幻读,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。 更多精彩文章https://www.cnblogs.com/zyy16...https://www.cnblogs.com/itdra... 双机热备概念双机热备双机热备特指基于高可用系统中的两台服务器的热备(或高可用),因两机高可用在国内使用较多,故得名双机热备。从广义上讲,就是对于重要的服务,使用两台服务器,互相备份,共同执行同一服务。当一台服务器出现故障时,可以由另一台服务器承担服务任务,从而在不需要人工干预的情况下,自动保证系统能持续提供服务。 双机热备和备份的区别热备份指的是:High Available(HA)即高可用,而备份指的是Backup,即数据备份的一种,这是两种不同的概念,应对的产品也是两种功能上完全不同的产品。热备份主要保障业务的连续性,实现的方法是故障点的转移。而备份,主要目的是为了防止数据丢失,而做的一份拷贝,所以备份强调的是数据恢复而不是应用的故障转移。 双机热备分类按工作中的切换方式分为:主-备方式(Active-Standby方式)和双主机方式(Active-Active方式)。 主-备方式即指的是一台服务器处于某种业务的激活状态(即Active状态),另一台服务器处于该业务的备用状态(即Standby状态)。双主机方式即指两种不同业务分别在两台服务器上互为主备状态(即Active-Standby和Standby-Active状态)。mysql 双机热备工作原理简单的说就是把 一个服务器上执行过的sql语句在别的服务器上也重复执行一遍, 这样只要两个数据库的初态是一样的,那么它们就能一直同步。当然这种复制和重复都是mysql自动实现的,我们只需要配置即可。我们进一步详细介绍原理的细节, 这有一张图: 上图中有两个服务器, 演示了从一个主服务器(master) 把数据同步到从服务器(slave)的过程。这是一个主-从复制的例子。 主-主互相复制只是把上面的例子反过来再做一遍。 所以我们以这个例子介绍原理。对于一个mysql服务器, 一般有两个线程来负责复制和被复制。当开启复制之后。 1. 作为主服务器Master,  会把自己的每一次改动都记录到 二进制日志 Binarylog 中。 (从服务器会负责来读取这个log, 然后在自己那里再执行一遍。)2. 作为从服务器Slave, 会用master上的账号登陆到 master上, 读取master的Binarylog,  写入到自己的中继日志 Relaylog, 然后自己的sql线程会负责读取这个中继日志,并执行一遍。  到这里主服务器上的更改就同步到从服务器上了。在mysql上可以查看当前服务器的主,从状态。 其实就是当前服务器的 Binary(作为主服务器角色)状态和位置。 以及其RelayLog(作为从服务器)的复制进度。 ...

April 26, 2019 · 1 min · jiezi

不止LNMP的后端开发环境搭建

前言本次目标是在新安装在虚拟机中的CentOS7.5系统中安装一系列后端开发通常用到的程序服务(这里不介绍CentOS的安装),并以phpMyAdmin为例子部署一个网站. Nginx(1.16)MySQL(8.0,开机启动)PHP(7.3)Redis(1.5)memcached(1.4)Composer(1.8)phpMyAdmin(4.8)nodejs(10.15)yarn(1.15)重要提示:如果觉得一个个安装和配置太麻烦那推荐使用宝塔可视化管理工具进行操作,基本实现一键操作极大提高效率,简言之一个字,爽!(当然也不是完美的,如当版本该工具不支持在ubuntu安装MySQL8.0) 约定虚拟机ip是192.168.8.15,管理员用户名sorgo,服务程序执行用户名www,网站根除目录/data/wwwroot,安装和配置时除特别说明则root用户操作,执行编辑文件命令后,下一行开始向右缩进4个空格为本次编辑的内容 CentOSvmware虚拟机安装不到20分钟可以完成(基础网页服务器1810版本,选中文环境,yum源默认是163.com) 用户设置useradd www -s /usr/sbin/nologinuseradd -G www sorgopasswd sorgo#赋予sorgo用户root权限,且切换root时不用输密码, `-l`查看已有权限visudo #编辑内容: #添加一条,表示sorgo可使用`sudo`执行root权限 sorgo ALL=(ALL) NOPASSWD: ALL全局命令别名设置vim /etc/bashrc # 添加内容: alias s-start='systemctl start' alias s-stop='systemctl stop' alias s-restart='systemctl restart' alias s-enable='systemctl enable' alias s-disable='systemctl disablep' alias s-status='systemctl status' alias vi='vim' alias ll='ls -laph' alias ..='cd ..' alias nrd='npm run dev' alias nrh='npm run hot' alias nrp='npm run production' alias nrw='npm run watch' alias nrww='npm run watch-poll' alias yrd='yarn run dev' alias yrh='yarn run hot' alias yrp='yarn run production' alias yrw='yarn run watch' alias yrwp='yarn run watch-poll' alias pa='php artisan' alias phpspec='vendor/bin/phpspec' alias phpunit='vendor/bin/phpunit'#使修改立即生效source /etc/bashrc网站目录及权限#项目目录mkdir -p /data/wwwroot/#日志目录mkdir -p /data/wwwlogs/nginx/#网站基础应用的安装目录mkdir /data/server/#赋权chown -R sorgo:www /data/chmod -Rf g+s /data/网卡配置#查看网卡名,左侧一栏那个就是ifconfig#编辑网卡配置,使nat模式的ip固定下来vi /etc/sysconfig/network-scripts/ifcfg-ens33 #编辑内容: #配置文件关键内容,仅供参考 NAME=ens33 DEVICE=ens33 DEFROUTE=yes ONBOOT=yes #设置为static时要带`IPADDR` BOOTPROTO=static IPADDR=192.168.8.15 NETMASK=255.255.255.0 GATEWAY=192.168.8.2 DNS1=192.168.8.2yum源配置阿里源 ...

April 26, 2019 · 4 min · jiezi

马蜂窝火车票系统服务化改造初探

交通方式是用户旅行前要考虑的核心要素之一。为了帮助用户更好地完成消费决策闭环,马蜂窝上线了大交通业务。现在,用户在马蜂窝也可以完成购买机票、火车票等操作。 与大多数业务系统相同,我们一样经历着从无到有,再到快速发展的过程。本文将以火车票业务系统为例,主要从技术的角度,和大家分享在一个新兴业务发展的不同阶段背后,系统建设与架构演变方面的一些经验。 第一阶段:从无到有在这个阶段,快速支撑起业务,填补业务空白是第一目标。基于这样的考虑,当时的火车票业务从模式上选择的是供应商代购;从技术的角度需要优先实现用户在马蜂窝 App 查询车次余票信息、购票、支付,以及取消、退票退款等核心功能的开发。 图1-核心功能与流程 技术架构综合考虑项目目标、时间、成本、人力等因素,当时网站服务架构我们选择的是 LNMP(Linux 系统下 Nginx+MySQL+PHP)。整个系统从物理层划分为访问层 ( App,H5,PC 运营后台),接入层 (Nginx),应用层 (PHP 程序),中间件层 (MQ,ElasticSearch),存储层 (MySQL,Redis)。 对外部系统依赖主要包括公司内部支付、对账、订单中心等二方系统,和外部供应商系统。 图 2-火车票系统 V1.0 技术架构 如图所示,对外展现功能主要分为两大块,一块是 C 端 App 和 H5,另外是运营后台。二者分别经过外网 Nginx 和内网 Nginx 统一打到 php train 应用上。程序内部主要有四个入口,分别是: 供其他二方系统调用的 facade 模块运营后台调用的 admin 模块处理 App 和 H5 请求的 train 核心模块处理外部供应商回调模块四个入口会依赖于下层 modules 模块实现各自功能。对外调用上分两种情况,一种是调用二方系统的 facade 模块满足其公司内部依赖;一种是调用外部供应商。基础设施上依赖于搜索、消息中间件、数据库、缓存等。 这是典型的单体架构模式,部署简单,分层结构清晰,容易快速实现,可以满足初期产品快速迭代的要求,而且建立在公司已经比较成熟的 PHP 技术基础之上,不必过多担心稳定性和可靠性的问题。 该架构支撑火车票业务发展了将近一年的时间,简单、易维护的架构为火车票业务的快速发展做出了很大的贡献。然而随着业务的推进,单体架构的缺陷逐渐暴露出来: 所有功能聚合在一起,代码修改和重构成本增大研发团队规模逐渐扩大,一个系统多人开发协作难度增加交付效率低,变动范围难以评估。在自动化测试机制不完善的情况下,易导致「修复越多,缺陷越多」的恶性循环伸缩性差,只能横向扩展,无法按模块垂直扩展可靠性差,一个 Bug 可能引起系统崩溃阻碍技术创新,升级困难,牵一发而动全身为了解决单体架构所带来的一系列问题,我们开始尝试向微服务架构演进,并将其作为后续系统建设的方向。 第二阶段:架构转变及服务化初探从 2018 年开始,整个大交通业务开始从 LNMP 架构向服务化演变。 架构转变——从单体应用到服务化「工欲善其事,必先利其器」,首先简单介绍一下大交通在实施服务化过程中积累的一些核心设施和组件。 ...

April 26, 2019 · 2 min · jiezi

mySql入门一

mySql入门-(一)学了很多乱七杂八的东西,但是依然停留在前端,在工作中一直和后端交流,但是不太了解数据库是怎么回事,为了加强学习,准备学习一些关于数据库相关的东西。 说起数据库可能会有很多很多,SQLServer、Oracle、Sybase等等等,还有就是要学习MySql,说了这么多数据库这些都是关系型数据库。既然有关系型数据库自然也就会有非关系型数据,比如Neo4j、MongodDB、Cloudant等等等这些也就都属于非关系型数据库,非关系型数据库又被称为nosql。 非关系型数据库与关系型数据库对比什么是关系型数据库,什么是非关系型数据库两者之间又有什么区别呢? 非关系型数据库性能NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。可扩展性同样shouce也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。 优势nosql数据库简单易部署,基本都是开源软件,不需要像使用oracle那样花费大量成本购买使用,相比关系型数据库价格便宜。nosql数据库将数据存储于缓存之中,关系型数据库将数据存储在硬盘中,自然查询速度远不及nosql数据库。nosql的存储格式是key,value形式、文档形式、图片形式等等,所以可以存储基础类型以及对象或者是集合等各种格式,而数据库则只支持基础类型。关系型数据库有类似join这样的多表查询机制的限制导致扩展很艰难。劣势维护的工具和资料有限,因为nosql是属于新的技术,不能和关系型数据库10几年的技术同日而语。不提供对sql的支持,如果不支持sql这样的工业标准,将产生一定用户的学习和使用成本。不提供关系型数据库对事物的处理。关系型数据库关系型数据库对于复杂查询可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。事务支持使得对于安全性能很高的数据访问要求得以实现。 优势保持数据的一致性(事务处理)由于以标准化为前提,数据更新的开销很小(相同的字段基本上都只有一处)可以进行Join等复杂查询劣势大量数据的写入处理为有数据更新的表做索引或表结构(schema)变更字段不固定时应用对简单查询需要快速返回结果的处理对于这两类数据库,对方的优势就是自己的弱势,反过来也是如此。 MySql安装在Windows上安装Mysql还是比较简单的,去Mysql官网去现在zip包就好了。下载完包之后把包解压到想要存放的存储盘内。 Mysql有提供安装版本,下载好之后安装就能使用了,如果下载zip包解压后需要进行配置,比较麻烦,网上也有很多教程。 然后打开刚刚解压的文件夹,在文件夹下面有一个my.ini配置文件。 MySql的配置与我们平时学习的语言不太一样,MySql在配置过程中有两点需要注意: 配置中的-与_是等价的分段配置[]隔离段[mysql]# 设置mysql客户端默认字符集default-character-set=utf8 [mysqld]# 设置3306端口port = 3306# 设置mysql的安装目录basedir=C:\\web\\mysql-8.0.11# 设置 mysql数据库的数据的存放目录,MySQL 8+ 不需要以下配置,系统自己生成即可,否则有可能报错# datadir=C:\\web\\sqldata# 允许最大连接数max_connections=20# 服务端使用的字符集默认为8比特编码的latin1字符集character-set-server=utf8# 创建新表时将使用的默认存储引擎default-storage-engine=INNODB根据以上内容更改配置就好了,并添加环境变量。这里安装就不详细介绍了。 MySql服务管理MySQL在服务中提供了一些对MySQL管理的一些命令工具。 设置管理员密码./bin/mysqladmin -u root password "root123";mysqladmin是执行很多关于MySQL的一些管理的工作,同样也是在命令行里面去执行的。执行上面的命令,就会为root用户创建一个密码。 启动服务./bin/mysqld_safe &# ./bin/mysqld &启动服务的时候是使用mysqld_safe这个命令的,实际上mysqld_safe是一个脚本,他会管理mysqld进程进行管理,一旦mysqld因为异常导致程序崩溃的话mysqld_safe会重启一下MySQL服务。 停止服务./bin/mysqladmin shutdown# kill -9 $mysqld_pid // 重启服务# kill $mysqld_pid使用kill $mysqld_pid命名会停止mysql服务,包括mysqld_safe也会停止。 还有很多关于MySql服务管理的命令,这里就不一一赘述了,单独出一篇文章对其进一步介绍。 MySql可视化工具安装完数据库之后需要安装可视化工具,方便查看数据库内容,以及方便创建数据库和删除数据数据库等操作。 想要可视化MySQL需要借助Nacicat,网上有很多破解版可以随便下载一个。Nacicat使用手册。 下载安装好之后可以与数据库做连接,使用方法也很简单。 MySql语法规范MySql数据库属于关联型数据库,需要使用Sql语句对表数据进行查询。一个数据库通常包含一个或多个表。每个表由一个名字标识。表包含带有数据的记录(行)。Sql语句对大小写不敏感,但是还是建议所有的Sql语句使用大写,查询条件与表名用小写,这样方便区分哪些是Sql语句哪些是查询条件。 在MySql系统要求在每条 SQL 命令的末端使用分号。分号是在数据库系统中分隔每条Sql语句的标准方法,这样就可以在对服务器的相同请求中执行一条以上的语句。 MySql数据类型数值类型 整数型 类型大小范围(有符号)范围(无符号)用途TINYINT1 字节(-128,127)(0,255)小整数值SMALLINT2 字节(-32 768,32 767)(0,65 535)大整数值MEDIUMINT3 字节(-8 388 608,8 388 607)(0,16 777 215)大整数值INT或INTEGER4 字节(-2 147 483 648,2 147 483 647)(0,4 294 967 295)大整数值BIGINT8 字节(-9,223,372,036,854,775,808,9 223 372 036 854 775 807)(0,18 446 744 073 709 551 615)极大整数值浮点型 ...

April 25, 2019 · 2 min · jiezi

宜信开源数据库审核软件Themis的规则解析

导语Themis是宜信公司DBA团队开发的一款数据库审核产品,可帮助DBA、开发人员快速发现数据库质量问题,提升工作效率。 此平台可实现对Oracle、MySQL数据库进行多维度(对象结构、SQL文本、执行计划及执行特征)的审核,用以评估对象结构设计质量及SQL运行效率。可帮助DBA及开发人员,快速发现定位问题;并提供部分辅助诊断能力,提升优化工作效率。全部操作均可通过WEB界面进行,简单便捷。此外,为了更好满足个性化需求,平台还提供了扩展能力,用户可根据需要自行扩展。 开源地址: https://github.com/CreditEaseDBA 点击查看Themis的部署攻略 一、规则解析规则解析分为四块:对象类规则解析、文本类规则解析、执行计划类规则解析、统计信息类规则解析。每个模块都可以使用手动或自动的方式进行。 1.1 对象类规则解析手动解析oracle对象类信息配置data/analysis_o_obj.json文件 { "module": "analysis", "type": "OBJ", "db_server": "127.0.0.1", "db_port": 1521, "username": "schema", "db_type": "O", "rule_type": "OBJ", "rule_status": "ON", "create_user": "system", "task_ip": "127.0.0.1", "task_port": 1521}配置db_server、db_port、username、create_user、task_ip选项,其他的保持默认即可,username是需要审核的目标对象的名字。 python command.py -m analysis_o_obj -c data/analysis_o_obj.json使用上面的命令开始采集obj数据 手动解析mysql对象类数据配置data/analysis_m_obj.json文件 { "module": "mysql", "type": "OBJ", "db_server": "127.0.0.1", "db_port": 3306, "username": "schema", "db_type": "mysql", "rule_type": "OBJ", "rule_status": "ON", "create_user": "mysqluser", "task_ip": "127.0.0.1", "task_port": 3306}配置db_server、db_port、username、create_user、task_ip、db_port选项,其他的保持默认即可。 运行命令: python command.py -m analysis_m_obj -c data/analysis_m_obj.jsonoracle和mysql对象类规则是不需要依赖于采集的数据的,它是直接连接到数据库里进行查询的,由于有的库较大可能时间会比较久,建议在业务低峰期进行。 ...

April 25, 2019 · 2 min · jiezi

宜信开源数据库审核软件Themis部署攻略

一、介绍Themis是宜信公司DBA团队开发的一款数据库审核产品,可帮助DBA、开发人员快速发现数据库质量问题,提升工作效率。其名称源自希腊神话中的正义与法律女神。项目取此名称,寓意此平台对数据库质量公平判断,明察秋毫。 此平台可实现对Oracle、MySQL数据库进行多维度(对象结构、SQL文本、执行计划及执行特征)的审核,用以评估对象结构设计质量及SQL运行效率。可帮助DBA及开发人员,快速发现定位问题;并提供部分辅助诊断能力,提升优化工作效率。全部操作均可通过WEB界面进行,简单便捷。此外,为了更好满足个性化需求,平台还提供了扩展能力,用户可根据需要自行扩展。 开源地址: https://github.com/CreditEaseDBA 1.1 功能概述事后审核,自主优化部分放在二期实现。亦可在项目设计阶段引入,起到一部分事前审核的作用。通过WEB界面完成全部工作,主要使用者是DBA和有一定数据库基础的研发人员。可针对某个用户审核,可审核包括数据结构、SQL文本、SQL执行特征、SQL执行计划等多个维度。审核结果通过WEB页面或导出文件的形式提供。平台支持主流的Oracle、MySQL数据库,其他数据库放在二期实现。尽量提供灵活定制的能力,便于日后扩展功能。1.2 支持的数据库MySQL(5.6及以上)Oracle(10g及以上)1.3 审核维度数据库结果(对象)=》指数据库对象,常见的表、分区、索引、视图、触发器等。SQL文本(语句)=》指SQL语句文本本身。SQL执行计划=》指数据库中SQL的执行计划。SQL执行特征=》指语句在数据库上的真实执行情况。1.4 实现原理 整个平台的基本实现原理很简单,就是将我们的审核对象(目前支持四种),通过规则集进行筛选。符合规则的审核对象,都是疑似有问题的。平台会将这些问题及关联信息提供出来,供人工甄别使用。由此可见,平台的功能强大与否,主要取决于规则集的丰富程度。平台也提供了部分扩展能力,方便扩展规则集。 1.5 平台架构 图中的方框部分,为平台的主要模块。底色不同的模块,表示当前的进度状态不同。虚线代表数据流,实线代表控制流。其核心为这几个模块: 数据采集模块。它是负责从数据源抓取审核需要的基础数据。目前支持从Oracle、MySQL抓取。OBJ/SQL存储库。这是系统的共同存储部分,采集的数据和处理过程中的中间数据、结果数据都保存在这里。其核心数据分为对象类和SQL类。物理是采用的MongoDB。核心管理模块。图中右侧虚线部分包含的两个模块:SQL管理和OBJ管理就是这部分。它主要是完成对象的全生命周期管理。目前只做了简单的对象过滤功能,因此还是白色底色,核心的功能尚未完成。审核规则和审核引擎模块。这部分是平台一期的核心组件。审核规则模块是完成规则的定义、配置工作。审核引擎模块是完成具体规则的审核执行部分。优化规则和优化引擎模块。这部分是平台二期的核心组件。目前尚未开发,因此为白色底色。系统管理模块。这部分是完成平台基础功能,例如任务调度、空间管理、审核报告生成、导出等功能。1.6 操作流程 二、环境搭建本项目中会使用到mysql,mongo和redis,python支持2.6、2.7,暂不支持python3。 mysql用来存储pt-query-digest抓取的mysql的慢查询,mongo存储我们的规则、oracle的采集结果、执行job,解析结果集等,redis作为任务调度celery的队列。 在mysql的数据采集部分我们使用的是pt-query-digest工具。 2.1 依赖安装新建用户为了减少后面对supervisord.conf配置文件的修改,我们建议使用统一的用户进行安装 adduser themis-test su - themis-test后面的操作除了virtualenv安装需要切换到root用户,其他的都默认在themis-test用户下安装 安装cx_Oracle依赖由于在审核过程中需要连接oracle数据库,因此需要先安装cx_Oracle的依赖,参考:http://www.jianshu.com/p/pKz5K7 安装python依赖首先安装virtualenv,参考链接:https://pypi.python.org/simpl...,建议安装13.0.3或更新版本 如果联网不方便,或者在公司内网,可以从https://pan.baidu.com/s/1o7AI...,提取码:3sy3 压缩包里包括所有需要用到的依赖包 安装virtualenvtar -zxvf virtualenv-13.0.3.tar.gzcd virtualenv-13.1.0python setup.py install关于virtualenv的使用请参考:https://virtualenv.pypa.io/en... 安装其他依赖首先初始化虚拟环境 virtualenv python-project --python=python2.7source /home/themis-test/python-project/bin/activate解释一下上面的命令:virtualenv的第二个参数python-project是我们建立的虚拟环境的名称,这个名称我们虽然可以随便定义,但是后面supervisor的配置中使用了此名称,建议使用默认的,大家如果对python比较熟悉,可以随意定义。后面我们指定了python的版本,--python可以不加,默认会使用系统自带的python版本构建虚拟环境,当有多个版本的python时,可以使用此命令指定版本。 下面使用source初始化虚拟环境,以后安装的包依赖等都会被装到/home/themis-test/python-project/home/themis-test/python2.7/lib/python2.7/site-packages这里。 如果可以联网,进入源代码目录使用如下命令 pip install -r requirement.txt单独安装Pyh,下载地址:https://github.com/hanxiaomax... unzip pyh-master.zipcd pyh-masterpython setup.py install如果在局域网环境不方便联网请利用的上面网盘里提供的压缩包 pip install --no-index -f file:///home/themis-test/software -r requirement.txtfile:///home/themis-test/software是压缩包解压的位置 2.2 配置文件介绍下面以配置文件settings.py为例子说明需要的一些依赖 # # set oracle ipaddress, port, sid, account, password# ipaddres : port -> keyORACLE_ACCOUNT = { # oracle "127.0.0.1:1521": ["cedb", "system", "password"]}# set mysql ipaddress, port, account, passwordMYSQL_ACCOUNT = { "127.0.0.1:3307": ["mysql", "user", "password"]}# pt-query save data for mysql account, passwordPT_QUERY_USER = "user"PT_QUERY_PORT = 3306PT_QUERY_SERVER = "127.0.0.1"PT_QUERY_PASSWD = "password"PT_QUERY_DB = "slow_query_log"# celery settingREDIS_BROKER = 'redis://:password@127.0.0.1:6379/0'# REDIS_BROKER = 'redis://:@127.0.0.1:6379/0'REDIS_BACKEND = 'redis://:password@127.0.0.1:6379/0'# REDIS_BACKEND = 'redis://:@127.0.0.1:6379/0'CELERY_CONF = { "CELERYD_POOL_RESTARTS": True}# mongo server settingsMONGO_SERVER = "127.0.0.1"MONGO_PORT = 27017# MONGO_USER = "sqlreview"MONGO_USER = "sqlreview"# MONGO_PASSWORD = ""MONGO_PASSWORD = "sqlreview"MONGO_DB = "sqlreview"# server port settingSERVER_PORT = 7000# capture time settingCAPTURE_OBJ_HOUR = "18"CAPTURE_OBJ_MINUTE = 15CAPTURE_OTHER_HOUR = "18"CAPTURE_OTHER_MINUTE = 30ORACLE_ACCOUNT和MYSQL_ACCOUNT是我们需要审核的目标机器的帐号和密码,主要是在数据采集部分和对象类审核以及mysql的执行计划类审核部分会用到,因此该帐号应该具有较高的权限,为了安全在生产环境应该设置专有的帐号并设置专有的权限,或者加上一些ip的限制等。 ...

April 25, 2019 · 2 min · jiezi

开启MySQL远程访问权限-允许远程连接

环境Ubuntu 18.04MySQL5.7登陆mysql数据库mysql -uroot -p增加远程连接权限use mysql;grant all privileges on *.* to root@'%' identified by "password";flush privileges;修改MySQL配置文件vim /etc/mysql/mysql.conf.d/mysqld.cnf注释掉bind-address这行重启mysql服务service mysql restart

April 25, 2019 · 1 min · jiezi

关于mysql数据库迁移datetime遇到的坑

工作中经常会遇到迁移数据库。如果用phpmyadmin导入,数据库小还能解决,如果太大就麻烦了,不是超时,就是文件太大,不允许导入。所以我就选择使用Navicat For Mysql进行数据库的复制 在这个过程中,我经常会遇到一个问题:ERROR 1292 (22007): Incorrect datetime value: '1970-01-01 08:00:00' for column 'time' at row 1经过查询资料,发现原因是在MySQL中,timestamp类型的合法区间是1970-01-01 00:00:01 - 2038-01-19 03:14:07 UTC,而在存储是,会先将你插入的数据转换为UTC时间,然后存储起来,读取的时候,再转换为你的本地时间。由于我的时区为东八区,因此转换后就变为了1970-01-01 00:00:00 UTC,成为了非法时间。(摘自MySQL时间类型和模式) MySql严格模式mysql> mysql> show variables like "%sql_mode%";+---------------+-------------------------------------------------------------------------------------------------------------------------------------------+| Variable_name | Value |+---------------+-------------------------------------------------------------------------------------------------------------------------------------------+| sql_mode | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION |+---------------+-------------------------------------------------------------------------------------------------------------------------------------------+1 row in set (0.00 sec)其中NO_ZERO_IN_DATE,NO_ZERO_DATE两个模式,限制datetime的时间格式必须合法,所以 mysql> set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTOO_CREATE_USER,NO_ENGINE_SUBSTITUTION';Query OK, 0 rows affected, 1 warning (0.00 sec)mysql> show variables like "%sql_mode%"; +---------------+--------------------------------------------------------------------------------------------------------------+| Variable_name | Value |+---------------+--------------------------------------------------------------------------------------------------------------+| sql_mode | ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION |+---------------+--------------------------------------------------------------------------------------------------------------+1 row in set (0.01 sec)重新设置模式,将NO_ZERO_IN_DATE,NO_ZERO_DATE去掉 ...

April 24, 2019 · 1 min · jiezi

如何优雅的备份账号相关信息

前言: 最近遇到实例迁移的问题,数据迁完后还需要将数据库用户及权限迁移过去。进行逻辑备份时,我一般习惯将MySQL系统库排除掉,这样备份里面就不包含数据库用户相关信息了。这时候如果想迁移用户相关信息 可以采用以下三种方案,类似的 我们也可以采用以下三种方案来备份数据库账号相关信息。(本文方案针对MySQL5.7版本,其他版本稍有不同) 1.mysqldump逻辑导出用户相关信息我们知道,数据库用户密码及权限相关信息保存在系统库mysql 里面。采用mysqldump可以将相关表数据导出来 如果有迁移用户的需求 我们可以按照需求在另外的实例中插入这些数据。下面我们来演示下: #只导出mysql库中的user,db,tables_priv表数据 #如果你有针队column的赋权 可以再导出columns_priv表数据#若数据库开启了GTID 导出时最好加上 --set-gtid-purged=OFFmysqldump -uroot -proot mysql user db tables_priv -t --skip-extended-insert > /tmp/user_info.sql#导出的具体信息---- Dumping data for table `user`--LOCK TABLES `user` WRITE;/*!40000 ALTER TABLE `user` DISABLE KEYS */;INSERT INTO `user` VALUES ('%','root','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','Y','',_binary '',_binary '',_binary '',0,0,0,0,'mysql_native_password','*81F5E21E35407D884A6CD4A731AEBFB6AF209E1B','N','2019-03-06 03:03:15',NULL,'N');INSERT INTO `user` VALUES ('localhost','mysql.session','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','Y','N','N','N','N','N','N','N','N','N','N','N','N','N','',_binary '',_binary '',_binary '',0,0,0,0,'mysql_native_password','*THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE','N','2019-03-06 02:57:40',NULL,'Y');INSERT INTO `user` VALUES ('localhost','mysql.sys','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','',_binary '',_binary '',_binary '',0,0,0,0,'mysql_native_password','*THISISNOTAVALIDPASSWORDTHATCANBEUSEDHERE','N','2019-03-06 02:57:40',NULL,'Y');INSERT INTO `user` VALUES ('%','test','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','',_binary '',_binary '',_binary '',0,0,0,0,'mysql_native_password','*94BDCEBE19083CE2A1F959FD02F964C7AF4CFC29','N','2019-04-19 06:24:54',NULL,'N');INSERT INTO `user` VALUES ('%','read','Y','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','',_binary '',_binary '',_binary '',0,0,0,0,'mysql_native_password','*2158DEFBE7B6FC24585930DF63794A2A44F22736','N','2019-04-19 06:27:45',NULL,'N');INSERT INTO `user` VALUES ('%','test_user','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','',_binary '',_binary '',_binary '',0,0,0,0,'mysql_native_password','*8A447777509932F0ED07ADB033562027D95A0F17','N','2019-04-19 06:29:38',NULL,'N');/*!40000 ALTER TABLE `user` ENABLE KEYS */;UNLOCK TABLES;---- Dumping data for table `db`--LOCK TABLES `db` WRITE;/*!40000 ALTER TABLE `db` DISABLE KEYS */;INSERT INTO `db` VALUES ('localhost','performance_schema','mysql.session','Y','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N');INSERT INTO `db` VALUES ('localhost','sys','mysql.sys','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','N','Y');INSERT INTO `db` VALUES ('%','test_db','test','Y','Y','Y','Y','Y','Y','N','N','N','Y','N','N','Y','Y','N','N','Y','N','N');/*!40000 ALTER TABLE `db` ENABLE KEYS */;UNLOCK TABLES;---- Dumping data for table `tables_priv`--LOCK TABLES `tables_priv` WRITE;/*!40000 ALTER TABLE `tables_priv` DISABLE KEYS */;INSERT INTO `tables_priv` VALUES ('localhost','mysql','mysql.session','user','boot@connecting host','0000-00-00 00:00:00','Select','');INSERT INTO `tables_priv` VALUES ('localhost','sys','mysql.sys','sys_config','root@localhost','2019-03-06 02:57:40','Select','');INSERT INTO `tables_priv` VALUES ('%','test_db','test_user','t1','root@localhost','0000-00-00 00:00:00','Select,Insert,Update,Delete','');/*!40000 ALTER TABLE `tables_priv` ENABLE KEYS */;UNLOCK TABLES;#在新的实例插入所需数据 就可以创建出相同的用户及权限了 2.自定义脚本导出首先拼接出创建用户的语句: ...

April 24, 2019 · 3 min · jiezi

MySQL-扩展性负载均衡眼花缭乱迷人眼

负载均衡的基本思路很简单: 在一个服务器集群中尽可能地的平均负载量。基于这个思路,我们通常的做法是在服务器前端设置一个负载均衡器。负载均衡器的作用是将请求的连接路由到最空闲的可用服务器上。如图 1,显示了一个大型网站负载均衡设置。其中一个负责 HTTP 流量,另一个用于 MySQL 访问。 负载均衡有五个常见目的: 可扩展性。负载均衡对某些扩展很有帮助,比如读写分离时从备库读数据。高效性。负载均衡因为能够控制请求被路由到何处,因此有助于更有效的使用资源。可用性。灵活的负载均衡方案能够大幅提高服务的可用性。透明性。客户端无需知道是否存在负载均衡器,也不需要关系在负载均衡器的背后有多少机器。呈现给客户端看到的就是一个透明的服务器。一致性。如果应用是有状态的(数据库事务、网站会话等),那么负载均衡器就可以将相关的查询指向同一个服务器,以防止状态丢失。而对于负载均衡的实现,一般有两种方式:直接连接和引入中间件。 1 直接连接有些人认为负载均衡就是配置在应用和 MySQL 服务器直接东西,但实际上这并不是唯一的负载均衡方法。接下来我们就讨论一下常见的应用直连的方法,及其相关注意事项。 1.1 复制的读写分离此种方式下,容易出现一个最大的问题:脏数据。一个典型的例子是,当用户评论了一篇博文,然后重新加载页面,却没有看到新增的评论。 当然,我们也不能因为脏数据的问题,就将读写分离弃之不用。实际上,对于很多应用,可能对脏数据的容忍度比较高,此时就可以大胆的引入此种方式。 那么对于脏数据的容忍度比较低的应用,如何进行读写分离呢?接下来,我们对读写分离再进一步区分,相信你总能找到适合自己的一款策略。 1) 基于查询分离 如果应用只有少数数据不能容忍脏数据,我们可以将所有不能容忍脏数据的读和写都分配到 master 上。其它的读查询分配的 slave 上。该策略很容易实现,但如果容忍脏数据的查询比较少,很可能会出现不能有效使用备库的情况。 2) 基于脏数据分离 这是对基于查询分离策略的小改进。需要做一些额外的工作,比如让应用检查复制延迟,以确定备库数据是否最新。许多报表类应用都可以使用这个策略:只需要晚上加载的数据复制到备库接口,并不关心是不是完全跟上了主库。 3) 基于会话分离 这个策略比脏数据分离策略更深入 一些。它是判断用户是否修改了数据,用户不需要看到其他用户的最新数据,只需要看到自己的更新。 具体可以在会话层设置一个标记位,表明用户是否做了更新,用户一旦做了更新,就将该用户的查询在一段时间内指向主库。 这种策略在简单和有效性之间做了很好的妥协,是一种较为推荐的策略。 当然,如果你的想法够多,可以把基于会话的分离策略和复制延迟监控策略结合起来。如果用户在 10 秒前更新了数据,而所有备库延迟在 5 秒内,就可以大胆的从备库中读取数据。要注意的是,记得为整个会话选择同个备库,否则一旦多个备库的延迟不一致,就会给用户造成困扰。 4) 基于全局版本 / 会话分离 通过记录主库日志坐标和备库已复制的坐标对比,确认备库是否更新数据。当应用指向写操作时,在提交事务后,执行一次 SHOW MASTER STATUS 操作,然后将主库日志坐标存储在缓存中,作为被修改对象或者会话的版本号。当应用连接到备库时,执行 SHOW SLAVE STATUS,并将备库上的坐标和缓存中的版本号对比。如果备库比主库记录点更新,就表明备库已更新对应数据,可放心的使用。 实际上,很多读写分离策略都需要监控复制延迟来决定读查询的分配。不过要注意的是,SHOW SLAVE STATUS 得到的 Seconds_behind_master 列的值并不能精确的表示延迟。我们可以使用 Percona Toolkit 中的 pt-heartbeat 工具更好的监控延迟。 1.2 修改 DNS 名对于一些比较简单的应用,可以为不同目的创建 DNS。最简单的方法是只读服务器拥有一个 DNS 名(read.mysql-db.com),给负责写操作的服务器起另外一个 DNS 名(write.mysql-db.com)。如果备库能够跟得上主库,就把只读 DNS 名指向到备库,否则,就指向到主库。 ...

April 24, 2019 · 1 min · jiezi

mysql安装操作总结

一、MySQL安装最近接了一个需求,需要写SQL语句查库,从头做起,从安装MySQL开始。安装没什么好说的,直接官网上下载适合的版本下载一步步安装就可以了,我是用的Mac安装,安装完成后会在系统偏好设置出现MySQL的小标标,如下图, <img src="/img/bVbrINL" width = "100" height = "100" div align=right />点击图标进入样子是下面这样 点击Start MySQL Server就可以成功启动MySQL啦。此时我们在命令行输入mysql -uroot -p命令会提示没有commod not found,我们还需要将mysql加入系统环境变量。步骤1.进入/usr/local/mysql/bin,查看此目录下是否有mysql;2.执行vim ~/.bash_profile,在该文件中添加mysql/bin的目录:PATH=$PATH:/usr/local/mysql/bin,添加完成后,按esc,然后输入:wq保存.最后在命令行输入source ~/.bash_profile; 现在输入mysql -uroot -p就可以登录mysql了,然后输入密码就是这个样子;接下来就可以输入MySQL语句查询啦; 二、常用语句总结首先查看有哪些数据库: show databases;(不要忘记这个分号。。。)用哪个数据库进行查询: use databasename;查看此数据库里都有哪些表:show tables;然后就可以进行数据查询啦。下面举个例子 select DATE_FORMAT(time,'%Y-%m-%d'),avg(cpa) cpaavgfrom Customswhere t="time"group by DATE_FORMAT(time,'%Y-%m-%d');这个语句是从Customs表中查询了日期和平均的cpa值,t为time的,最后按日期分组,查询结果如下图: 三、总结MySQL语句功能非常强大,方法也非常多,入门可以参考http://www.runoob.com/sql/sql...,在此记录一下,以后有其他内容再继续更新~~

April 23, 2019 · 1 min · jiezi

MySQL时间类型和模式

当我在MySQL数据库中尝试插入一条带有时间戳的数据时报错: mysql> insert into alarm_service values (6, '1970-01-01 08:00:00'); ERROR 1292 (22007): Incorrect datetime value: '1970-01-01 08:00:00' for column 'time' at row 1# 查看表结构mysql> show create table alarm_service;+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| Table | Create Table |+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| alarm_service | CREATE TABLE `alarm_service` ( `id` int(11) NOT NULL AUTO_INCREMENT, `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 |+---------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+1 row in set (0.00 sec)我们可以发现错误信息提示是时间值错误,但是我们这明显是一个合法的时间点啊。 ...

April 23, 2019 · 2 min · jiezi

如果有人问你数据库的原理叫他看这篇文章转载

一提到关系型数据库,我禁不住想:有些东西被忽视了。关系型数据库无处不在,而且种类繁多,从小巧实用的 SQLite 到强大的 Teradata 。但很少有文章讲解数据库是如何工作的。你可以自己谷歌/百度一下『关系型数据库原理』,看看结果多么的稀少【译者注:百度为您找到相关结果约1,850,000个…】 ,而且找到的那些文章都很短。现在如果你查找最近时髦的技术(大数据、NoSQL或JavaScript),你能找到更多深入探讨它们如何工作的文章。 难道关系型数据库已经太古老太无趣,除了大学教材、研究文献和书籍以外,没人愿意讲了吗? 作为一个开发人员,我不喜欢用我不明白的东西。而且,数据库已经使用了40年之久,一定有理由的。多年以来,我花了成百上千个小时来真正领会这些我每天都在用的、古怪的黑盒子。关系型数据库非常有趣,因为它们是基于实用而且可复用的概念。如果你对了解一个数据库感兴趣,但是从未有时间或意愿来刻苦钻研这个内容广泛的课题,你应该喜欢这篇文章。 虽然本文标题很明确,但我的目的并不是讲如何使用数据库。因此,你应该已经掌握怎么写一个简单的 join query(联接查询)和CRUD操作(创建读取更新删除),否则你可能无法理解本文。这是唯一需要你了解的,其他的由我来讲解。 我会从一些计算机科学方面的知识谈起,比如时间复杂度。我知道有些人讨厌这个概念,但是没有它你就不能理解数据库内部的巧妙之处。由于这是个很大的话题,我将集中探讨我认为必要的内容:数据库处理SQL查询的方式。我仅仅介绍数据库背后的基本概念,以便在读完本文后你会对底层到底发生了什么有个很好的了解。 【译者注:关于时间复杂度。计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。如果不了解这个概念建议先看看维基或百度百科,对于理解文章下面的内容很有帮助】 由于本文是个长篇技术文章,涉及到很多算法和数据结构知识,你尽可以慢慢读。有些概念比较难懂,你可以跳过,不影响理解整体内容。 这篇文章大约分为3个部分: 底层和上层数据库组件概况查询优化过程概况事务和缓冲池管理概况回到基础很久很久以前(在一个遥远而又遥远的星系……),开发者必须确切地知道他们的代码需要多少次运算。他们把算法和数据结构牢记于心,因为他们的计算机运行缓慢,无法承受对CPU和内存的浪费。 在这一部分,我将提醒大家一些这类的概念,因为它们对理解数据库至关重要。我还会介绍数据库索引的概念。 O(1) vs O(n^2)现今很多开发者不关心时间复杂度……他们是对的。 但是当你应对大量的数据(我说的可不只是成千上万哈)或者你要争取毫秒级操作,那么理解这个概念就很关键了。而且你猜怎么着,数据库要同时处理这两种情景!我不会占用你太长时间,只要你能明白这一点就够了。这个概念在下文会帮助我们理解什么是基于成本的优化。 概念时间复杂度用来检验某个算法处理一定量的数据要花多长时间。为了描述这个复杂度,计算机科学家使用数学上的『简明解释算法中的大O符号』。这个表示法用一个函数来描述算法处理给定的数据需要多少次运算。 比如,当我说『这个算法是适用 O(某函数())』,我的意思是对于某些数据,这个算法需要 某函数(数据量) 次运算来完成。 重要的不是数据量,而是当数据量增加时运算如何增加。时间复杂度不会给出确切的运算次数,但是给出的是一种理念。 图中可以看到不同类型的复杂度的演变过程,我用了对数尺来建这个图。具体点儿说,数据量以很快的速度从1条增长到10亿条。我们可得到如下结论: 绿:O(1)或者叫常数阶复杂度,保持为常数(要不人家就不会叫常数阶复杂度了)。红:O(log(n))对数阶复杂度,即使在十亿级数据量时也很低。粉:最糟糕的复杂度是 O(n^2),平方阶复杂度,运算数快速膨胀。黑和蓝:另外两种复杂度(的运算数也是)快速增长。例子数据量低时,O(1) 和 O(n^2)的区别可以忽略不计。比如,你有个算法要处理2000条元素。 O(1) 算法会消耗 1 次运算O(log(n)) 算法会消耗 7 次运算O(n) 算法会消耗 2000 次运算O(n*log(n)) 算法会消耗 14,000 次运算O(n^2) 算法会消耗 4,000,000 次运算O(1) 和 O(n^2) 的区别似乎很大(4百万),但你最多损失 2 毫秒,只是一眨眼的功夫。确实,当今处理器每秒可处理上亿次的运算。这就是为什么性能和优化在很多IT项目中不是问题。 我说过,面临海量数据的时候,了解这个概念依然很重要。如果这一次算法需要处理 1,000,000 条元素(这对数据库来说也不算大)。 O(1) 算法会消耗 1 次运算O(log(n)) 算法会消耗 14 次运算O(n) 算法会消耗 1,000,000 次运算O(n*log(n)) 算法会消耗 14,000,000 次运算O(n^2) 算法会消耗 1,000,000,000,000 次运算我没有具体算过,但我要说,用O(n^2) 算法的话你有时间喝杯咖啡(甚至再续一杯!)。如果在数据量后面加个0,那你就可以去睡大觉了。 ...

April 23, 2019 · 10 min · jiezi

ScalikeJDBC连接MySQL

ScalikeJDBC是一个Scala的JDBC框架,官网说easy-to-use and very flexible,易用又灵活~ 1、添加依赖 <dependency> <groupId>org.scalikejdbc</groupId> <artifactId>scalikejdbc_2.11</artifactId> <version>3.3.2</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.197</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.15</version> </dependency>2、上代码 import scalikejdbc._object MySQLDAO { def main(args: Array[String]): Unit = { // initialize JDBC driver & connection pool Class.forName("org.h2.Driver") ConnectionPool.singleton("jdbc:mysql:///film?characterEncoding=utf8", "root", "root") val id = 1 val name = "复联%" val value = sql"""select * from movie where name LIKE $name ORDER BY id DESC """ println(value) // simple example val lasts: List[Map[String, Any]] = DB.readOnly { implicit session => sql"""select * from movie where name LIKE $name ORDER BY id DESC """.map(_.toMap()).list.apply() } println(lasts) }}3、排错1)版本 ...

April 23, 2019 · 1 min · jiezi

MySQL - 扩展策略:氪金氪脑任君选

如果将应用的所有数据简单地放在一台 MySQL 服务器实例上,就不用谈什么扩展性了。但是业务能稳定持续的增长,那么应用肯定会碰到性能瓶颈。 对于很多类型的应用而言,购买更高性能的机器能解决一大部分性能问题,这也是我们常说的 “垂直扩展” 或者 “向上扩展”。 另一个与之相反的方法是将任务分配的多台机器上,这通常被称为 “水平扩展” 或者 “向外扩展”。 接下来,我们将讨论如何联合使用向上扩展和向外扩展,以及如何使用集群方案来进行扩展。 最后,大部分应用还会有一些很少或者从不需要的数据,这些数据可以被清理或归档,我们可以称这种方案为 “向内扩展”。 1 向上扩展向上扩展(也叫垂直扩展)意味着购买更多性能强悍的机器。这种策略有较多优点: 更容易维护和开发,显著节约开销;单台服务器备份和恢复较为简单,无需关心一致性;因此,从复杂性的成本来说,大多时候,向上扩展比向外扩展更简单。 另外,不要觉得向上扩展很快就走到“尽头”,要相信科技的进步速度。现在,拥有 0.5TB 内存、32 核(或者更多)CPU 以及更强悍 I/O 性能的商用服务器很容易获得。优秀的应用和数据库设计,再加上很好的性能优化技能,已经可以满足绝大多数商业应用。 不过遗憾的,虽然高性能服务器比较容易获得,但是 MySQL 并不能扩展到对应的规模。为了更好地在大型服务器上运行 MySQL,一定要尽量选择最新的版本。即使如此,当前合理的 “收益递减点” 的机器配置大约是: 256G RAM32 核 CPUPCIe flash 驱动器如果继续提升硬件配置,MySQL 性能虽然还能有所提升,但性价比就会降低。 因此,我们建议,如果系统确实有可能碰到可规划性的天花板,并且会导致严重的业务问题,那就不要无限制的做向上扩展的规划。对于庞大的应用,可以短期内购买更优的服务器,但最终还是需要向外扩展的。 2 向外扩展向外扩展(也叫横向扩展或水平扩展)策略通常分为三个部分:复制、拆分和数据分片。 最常见的向外扩展就是读写分离。通过复制将数据分发到多个服务器上,然后将备库用于读查询。这种技术对于以读为主的应用很有效。 另一个比较常见的向外扩展方法是将工作负载分布到多个 “节点”。接下来我们要了解的主要是这种扩展方法。 在此之前,我们先明确下节点的概念。在 MySQL 架构中,一个节点就是一个功能部件。一般的,我们会将一台服务器作为一个几点。但如果我们考虑到节点的高可用性,那么一个节点通常可能是下面的几种: 一个主 - 主 复制双机结构,拥有一个主动服务器和被动服务器。一个主库和多个备库。一个主动服务器,并使用分布式复制块设备(DRBD)作为备用服务器。一个基于存储区域网络(SAN)的 “集群”。2.1 按功能拆分按功能拆分,或者说按职责拆分,意味着不同的节点执行不同的任务。 例如,如果有一个网站,各个部分无需共享数据,那么可以按照网站的功能区域进行划分。像我们常见的门户网站,一般都是把不同栏目放在一起,但实际上可以将网站新闻、论坛、寻求支持等功能放到专用的 MySQL 服务器。如图 2-1 2.2 数据分片在目前用于扩展大型 MySQL 应用的方案中,数据分片是最通用且最成功的方法。它把数据分割成一小片,或者说一块,然后存储到不同的节点中。 在使用分片前,要牢记一个通用原则:如非必要,尽量不分片。 除此之前,对于分片,我们只会对需要的数据做分片。这里 “需要的数据” 通常是那些增长非常庞大的数据。而像对于用户信息这些全局数据,一般是存储在单个节点上,通常保存在类似 redis 这样的缓存中。 ...

April 22, 2019 · 1 min · jiezi

【数据库】MySQL查询优化

欢迎关注公众号:【爱编程】如果有需要后台回复2019赠送1T的学习资料哦!! 背景在这个快速发展的时代,时间变得越来越重要,也流逝得非常得快,有些人长大了,有些人却变老了。稍不留神,2019已经过完了三分之一。回首这四个月收获什么,懂得了什么?欢迎留言分享给我哟。 **言归正传:MySQL的查询怎么才能更快,更合理?除了加索引还有什么可以学习的呢?** 原理要想更好地学习某样东西,从其原理和运作方式入手更容易掌握。道理你们都懂,我就不废话了。 MySQL发送查询请求,到底做了什么工作?下图是MySQL查询执行流程图: 客户端发送一条查询给服务器。服务器先检查查询缓存,如果命中了缓存,则立刻返回查询在缓存中的结果。否则会进入下一个阶段。3.服务端进行SQL解析、预处理、再由优化器生成对应的执行计划。4.MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询。5.将结果返回给客户端。 是什么导致MySQL查询变慢了?对于MySQL,最简单的衡量查询开销的三个指标如下: 响应时间扫描的行数返回的行数没有哪个指标能够完美地衡量查询的开销,但它们大致反映了MySQL在内部执行查询时需要访问多少数据,并可以大概推算出查询运行的时间。 查询慢的原因基本都是:我们的不合理操作导致查询的多余数据太多了。常见原因有以下: 1.查询不需要的记录。2.多表关联时返回全部列3.总是取出全部列常用优化技巧1.用索引最简单且见效最快的方式就是给你的条件加索引(主键索引,普通索引,唯一索引等)。注:索引是要另开辟一块空间存储的,所以不能不要钱滴都加索引。 2.关联子查询MySQL的子查询实现是非常糟糕的。比如下面的 SELECT * FROM book WHERE book_id IN (SELECT book_id FROM author WHERE author_id = 1)MySQL对IN()列表中的选项有专门的优化策略,一般会认为MySQL会先执行子查询返回所有包含author_id 为1的book_id。 或许你想MySQL的运行时这样子的: SELECT GROUP_CONCAT(book_id) FROM author WHERE author_id = 1SELECT * FROM book WHERE book_id IN (1,21,3,45,656,766,213,123)但是,MySQL会将相关的外层表压到子查询中的,就是下面的样子: SELECT * FROM book WHERE EXISTS (SELECT * FROM author WHERE author_id = 1 AND book.book_id = author.book_id)原因:因为子查询需要book_id ,所以MySQL认为无法先执行这个子查询,而是先对book 进行全表扫描,然后再根据book_id进行子查询。具体可以EXPLAIN该SQL进行分析。 建议:1.使用左外连接(LEFT OUTER JOIN)代替子查询。 ...

April 21, 2019 · 2 min · jiezi

linux 安装mysql8.x

MySQLMySQL简介MySQL原本是一个开放源代码的关系数据库管理系统,原开发者为瑞典的MySQL AB公司,该公司于2008年被昇阳微系统(Sun Microsystems)收购。2009年,甲骨文公司(Oracle)收购昇阳微系统公司,MySQL成为Oracle旗下产品。 MySQL在过去由于性能高、成本低、可靠性好,已经成为最流行的开源数据库,因此被广泛地应用在Internet上的中小型网站中。随着MySQL的不断成熟,它也逐渐用于更多大规模网站和应用,比如维基百科、Google和Facebook等网站。非常流行的开源软件组合LAMP中的“M”指的就是MySQL。 但被甲骨文公司收购后,Oracle大幅调涨MySQL商业版的售价,且甲骨文公司不再支持另一个自由软件项目OpenSolaris的发展,因此导致自由软件社群们对于Oracle是否还会持续支持MySQL社群版(MySQL之中唯一的免费版本)有所隐忧,MySQL的创始人麦克尔·维德纽斯以MySQL为基础,成立分支计划MariaDB。而原先一些使用MySQL的开源软件逐渐转向MariaDB或其它的数据库。例如维基百科已于2013年正式宣布将从MySQL迁移到MariaDB数据库[6]。 关系型数据库关系数据库(英语:Relational database),是创建在关系模型基础上的数据库,借助于集合代数等数学概念和方法来处理数据库中的数据。现实世界中的各种实体以及实体之间的各种联系均用关系模型来表示。关系模型是由埃德加·科德于1970年首先提出的,并配合“科德十二定律”。现如今虽然对此模型有一些批评意见,但它还是数据存储的传统标准。标准数据查询语言SQL就是一种基于关系数据库的语言,这种语言执行对关系数据库中数据的检索和操作。 关系模型由关系数据结构、关系操作集合、关系完整性约束三部分组成。 MySQL特性MySQL是一种使用广泛的数据库,特性如下: 使用C和C++编写,并使用了多种编译器进行测试,保证源代码的可移植性 支持AIX、FreeBSD、HP-UX、Linux、Mac OS、Novell Netware、OpenBSD、OS/2Wrap、Solaris、Windows等多种操作系统。 为多种编程语言提供了API。编程语言包括C、C++、Python、Java、Perl、PHP、Eiffel、Ruby和Tcl等。 支持多线程,充分利用CPU资源 优化的SQL查询算法,有效地提高查询速度既能够作为一个单独的应用程序应用在客户端服务器网络环境中,也能够作为一个库而嵌入到其他的软件中提供多语言支持,常见的编码如中文的GB 2312、BIG5,日文的Shift_JIS等都可以用作数据表名和数据列名 提供TCP/IP、ODBC和JDBC等多种数据库连接途径提供用于管理、检查、优化数据库操作的管理工具可以处理拥有上千万条记录的大型数据库MySQL应用与大型数据库例如Oracle、DB2、SQL Server等相比,MySQL自有它的不足之处,如规模小、功能有限(MySQL Cluster的功能和效率都相对比较差)等,但是这丝毫也没有减少它受欢迎的程度。对于一般的个人使用者和中小型企业来说,MySQL提供的功能已经绰绰有余,而且由于MySQL是开放源码软件,因此可以大大降低总体拥有成本。 目前Internet上流行的网站构架方式是LAMP(Linux+Apache+MySQL+PHP),即使用Linux作为操作系统,Apache作为Web服务器,MySQL作为数据库,PHP作为服务器端脚本解释器。由于Linux+Apache+MySQL+PHP都是自由或开放源码软件(FLOSS),因此使用LAMP不用花一分钱就可以建立起一个稳定、免费的网站系统。 MySLQ存储引擎MySQL存储引擎简介插件式存储引擎是MySQL数据库最重要的特性之一,用户可以根据应用的需要选择如何存储和索引数据库,是否使用事务等。mySQL默认支持多种存储引擎,以适应不同领域的数据库应用需要。用户可以通过选择使用不同的存储引擎提高应用的效率,提供灵活的存储,用户设置可以按照自己的需要定制和使用自己的存储引擎,以实现最大程度的可定制性。MySQL常用的存储引擎为MyISAM、InnoDB、MEMORY、MERGE,其中InnoDB提供事务安全表,其他存储引擎都是非事务安全表。MyISAM是MySQL的默认存储引擎。MyISAM不支持事务、也不支持外键,但其访问速度快,对事务完整性没有要求。InnoDB存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。但是比起MyISAM存储引擎,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。MySQL支持外键存储引擎只有InnoDB,在创建外键的时候,要求附表必须有对应的索引,子表在创建外键的时候也会自动创建对应的索引。 MySQL存储引擎特性主要体现在性能、事务、并发控制、参照完整性、缓存、故障恢复,备份及回存等几个方面目前比较普及的存储引擎是MyISAM和InnoDB,而MyISAM又是绝大部分Web应用的首选。MyISAM与InnoDB的主要的不同点在于性能和事务控制上。MyISAM是早期ISAM(Indexed Sequential Access Method)的扩展实现,ISAM被设计为适合处理读频率远大于写频率的情况,因此ISAM以及后来的MyISAM都没有考虑对事物的支持,不需要事务记录,ISAM的查询效率相当可观,而且内存占用很少。MyISAM在继承了ISAM优点的同时,与时俱进的提供了大量实用的新特性和相关工具。例如考虑到并发控制,提供了表级锁。而且由于MyISAM是每张表使用各自独立的存储文件(MYD数据文件和MYI索引文件),使得备份及恢复十分方便(拷贝覆盖即可),而且还支持在线恢复。所以如果应用不需要事务,不支持外键,处理的只是基本的CRUD(增删改查)操作,那么MyISAM是不二选择。 linux (CentOS7.5_x86_64)下安装mysql8.0# 下载mysql $ wget http://mirrors.163.com/mysql/Downloads/MySQL-8.0/mysql-8.0.13-el7-x86_64.tar.gz# 解压$ mysql tar -zxvf mysql-8.0.4-rc-linux-glibc2.12-x86_64.tar.gz -C /usr/local# 修改文件夹名称 $ mv mysql-8.0.4-rc-linux-glibc2.12-x86_64/ mysql添加默认配置文件$ vim/etc/my.cnf[client]port=3306socket=/tmp/mysql.sock[mysqld]port=3306user=mysqlsocket=/tmp/mysql.sockbasedir=/usr/local/mysqldatadir=/usr/local/mysql/data# 创建mysql组$ groupadd mysql# 创建mysql用户$ useradd -g mysql mysql# 创建mysql数据目录$ mkdir $MYSQL_HOME/data# 初始化mysql $ /usr/local/mysql/bin/mysqld --initialize --user=mysql --basedir=/usr/local/mysql/ --datadir=/usr/local/mysql/data/# 初始化报错bin/mysqld: error while loading shared libraries: libaio.so.1: cannot open shared object file: No such file or directory# 解决方法yum install -y libaio# 初始化报错2018-07-08T02:53:24.542370Z 0 [System] [MY-010116] /usr/local/mysql/bin/mysqld (mysqld 8.0.4-rc) starting as process 17745 ...mysqld: Can't create/write to file '/tmp/mysql/data/ibd35qXQ' (Errcode: 13 - Permission denied)2018-07-08T02:53:24.554816Z 1 [ERROR] [MY-011066] InnoDB: Unable to create temporary file; errno: 132018-07-08T02:53:24.554856Z 1 [ERROR] [MY-011066] InnoDB: InnoDB Database creation was aborted with error Generic error. You may need to delete the ibdata1 file before trying to start up again.2018-07-08T02:53:24.555000Z 0 [ERROR] [MY-010020] Data Dictionary initialization failed.2018-07-08T02:53:24.555033Z 0 [ERROR] [MY-010119] Aborting2018-07-08T02:53:24.555919Z 0 [System] [MY-010910] /usr/local/mysql/bin/mysqld: Shutdown complete.# 解决办法:修改/tmp/mysql的目录权限$ chown -R mysql:mysql /tmp/mysql# 初始化成功> 如果无异常情况日志如下可以看到mysql默认会生成root账号和密码root@localhost: /TI(mjVAs1Ta[root@localhost mysql]# bin/mysqld --initialize --user=mysql --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data2019-01-29T10:19:34.023997Z 0 [System] [MY-013169] [Server] /usr/local/mysql/bin/mysqld (mysqld 8.0.13) initializing of server in progress as process 42402019-01-29T10:19:39.764895Z 5 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: /TI(mjVAs1Ta2019-01-29T10:19:43.041419Z 0 [System] [MY-013170] [Server] /usr/local/mysql/bin/mysqld (mysqld 8.0.13) initializing of server has completed# 拷贝mysql启动文件到系统初始化目录$ cp /usr/local/mysql/support-files/mysql.server /etc/init.d/mysqld# 启动mysql服务器$ service mysqld startmysql 基本操作# 使用mysql客户端连接mysql$ /usr/local/mysql/bin/mysql -u root -p password修改mysql的默认初始化密码> alter user 'root'@'localhost' identified by 'root';# 创建用户 CREATE USER '用户名称'@'主机名称' INDENTIFIED BY '用户密码'> create user 'jack'@'localhost' identified by 'jack';# 授予权限 grant 权限 on 数据库.表 to '用户名'@'登录主机' [INDENTIFIED BY '用户密码'];> grant replication slave on *.* to 'jack'@'localhost';# 刷新# $ flush privileges; # 修改root用户可以远程连接> update mysql.user set host='%' where user='root';# 查看mysql所用用户> select user,host from mysql.user;# docker 修改mysql的最大连接数apt-get updateapt-get install vimvim /etc/mysql/mysql.conf.d/mysqld.cnfmax_connections=1000> alter user 'root'@'%' identified with mysql_native_password by 'root';mysql 集群主从复制准备两台安装好的mysql服务器192.168.79.15 (master)192.168.79.16 (slave) ...

April 21, 2019 · 3 min · jiezi

select * 和 select 所有字段的区别

阅读本文大概需要 1 分钟。之前发过的文章中,关于 select * 和 select 所有字段的知识,有描述不恰当,这次重新纠正下,加深下理解。MySQL 5.1.37表记录数 41,547,002,即 4000+w 行。使用远程客户端取 1000 条数据,统计时间:SELECT * FROM dmsp.dmsp_dimension_content LIMIT 0, 1000;时间 2.218s,网络消耗 0.547s 。SELECT id, appid, aop, t, uid, sid, pid, pname, bid, bname, ptype, sm, sv, bt, national, area, ov FROM dmsp.dmsp_dimension_content LIMIT 0, 1000;取出所有字段,时间 2.250s,网络消耗 0.578s 。多次查询(改变 limit 条件避免缓存),时间变化不大。结论:两者差别几乎可忽略。所以查询所有字段(或者大多数字段)的时候,大可 select * 来操作。如果某些不需要的字段数据量特别大,还是写清楚字段比较好,因为这样可以减少网络传输。(1)减少数据的负担。SELECT *,需要数据库先 Query Table Metadata For Columns,一定程度上为数据库增加了负担(影响网络传输的性能),但是实际上,两者效率差别不大。(2)考虑到今后的扩展性。因为程序里面你需要使用到的列毕竟是确定的, SELECT * 只是减少了一句 SQL String 的长度,并不能减少其他地方的代码。(3)索引问题select abc from table; 和 select * from table;在 abc 字段有索引的情况下,mysql 是可以不用读 data,直接使用 index 里面的值就返回结果的。但是一旦用了 select ,就会有其他列需要读取,这时在读完 index 以后还需要去读 data 才会返回结果,这样就造成了额外的性能开销。综上:除平时练习使用,其他情况都不推荐使用 SELECT * FROM XXX 。·END·程序员的成长之路路虽远,行则必至本文原发于 同名微信公众号「程序员的成长之路」,回复「1024」你懂得,给个赞呗。往期精彩回顾程序员接私活的7大平台利器select count() 底层究竟做了什么? 删库后,除了跑路还能怎么办? MySQL索引优化看这篇文章就够了! 选择 25k 的 996 还是 18k 的 965一个完整的 Web 请求到底发生了什么 会写代码是你创业路上的包袱吗? 支付宝架构师眼中的高并发架构 最近话题火爆的四件事你知道不? ...

April 20, 2019 · 1 min · jiezi

好书共读 | 国内外互联网技术大牛们都写了哪些书籍?

出自 GitHub 开源组织 Doocs源地址:https://github.com/doocs/tech…后面将会在 GitHub 陆续更新书籍清单,有兴趣的朋友可以一起来维护。书籍是人们获取知识的主要途径。然而,如今的社会太浮躁,不少人不愿意花时间静下心来仔细读书,很多开发人员也是如此。殊不知,书籍沉淀了前人的经验和思考。本项目主要收集国内外各大互联网公司技术大牛们出版的值得一看的书籍,欢迎推荐书籍、完善内容和排版。如果你有比较好的 idea,也欢迎在 issues 上交流。注意,本项目所有书籍资源仅供交流和学习使用,请勿商用。若喜欢且有条件,请购买正版书籍,以便更好地支持原书作者。Common#TitleAuthor(s)AbstractResource1编码:隐匿在计算机软硬件背后的语言[美]Charles Petzold 著左飞 薛佟佟 译讲述计算机工作原理,却并不晦涩难懂。不管你是计算机高手,还是对这个神奇的机器充满敬畏之心的菜鸟,读一读大师的经典作品,必然有所收获。1f7s2码农翻身:用故事给技术加点料刘欣用故事方式讲述软件编程的若干重要领域,侧重于基础性、原理性的知识。2d9sBackend#TitleAuthor(s)AbstractResource1Effective Java 原书第 3 版[美]Joshua Bloch 著俞黎敏 译Java 经典书籍,内容涵盖 Java 9 及以下版本。w2f22码出高效:Java 开发手册杨冠宝(花名:孤尽)高海慧(花名:鸣莎)结合阿里巴巴实践经验与故障案例,与 Java 底层源码解析融会贯通。9yyp3互联网轻量级SSM框架解密:Spring、Spring MVC、MyBatis源码深度剖析李艳鹏 等SSM 框架源码深度剖析。39i14Java 8 In Action 中文版[英]Raoul-Gabriel Urma [意]Mario Fusco [英]Alan Mycroft 著陆明刚 劳佳 译全面介绍 Java8 这个里程碑版本的新特性,包括 Lambdas、流和函数式编程6063Database#TitleAuthor(s)AbstractResource1Redis 设计与实现黄健宏基于 Redis 3.0,内容通俗易懂,可以深入了解 Redis 底层。q2ky2高性能 MySQL 第三版[美] Baron Schwartz 等著 宁海元 等译MySQL 领域极佳之作。0ijqFrontend#TitleAuthor(s)AbstractResourceArchitecture#TitleAuthor(s)AbstractResource1企业 IT 架构转型之道:阿里巴巴中台战略思想与架构实战钟华(花名:古谦)分享阿里巴巴建设共享服务体系的经验和实践。euzo2大型网站系统与 Java 中间件实践曾宪杰(花名:华黎)围绕大型网站和支撑大型网站架构的 Java 中间件的实践展开介绍。hrkh3大型网站技术架构:核心原理与案例分析李智慧梳理大型网站技术发展历程,剖析大型网站技术架构模式,深入讲述大型互联网架构设计的核心原理。8epr4亿级流量网站架构核心技术:跟开涛学搭建高可用高并发系统张开涛总结并梳理了亿级流量网站高可用和高并发原则,通过实例详细介绍了如何落地这些原则。2v7a5逆流而上:阿里巴巴技术成长之路阿里巴巴集团成长集编委会总结阿里巴巴技术团队在基础架构、中间件、数据库、业务开发等领域的经典实践以及对未来的思考。coi5Big Data#TitleAuthor(s)AbstractResource1HBase 不睡觉书杨曦一本让读者看了不会睡着的 HBase 技术书。sjcw

April 19, 2019 · 1 min · jiezi

处理高并发的一般思路

前言今天看见有人聊目前系统有2亿的PV,该如何优化?当我看到这个话题的时候,突然在想自己工作中也遇到了不少高并发的场景了,所以即兴发挥,在这里简单总结和分享下,欢迎指正和补充。正文读操作关于读,我们一般遵循如下优先级:优先级技术方案说明示例最高尽可能静态化对实时性要去不高的数据,尽可能全走CDN例如获取基础商品信息高就近使用内存优先级服务器内存、远程内存服务例如秒杀、抢购库存(优先分配库存到服务器内存,其次远程内存服务<又涉及额外网络IO>)极低数据库(能不读就不要读)连接池、sql优化常见业务写操作关于写,我们一般会按照数据的一致性要求级别来看:数据一致性要求技术方案不高先写内存(优先级从服务器内存到远程内存服务) 再异步储存高同步完成最关键的任务 异步保证其他任务最终成功削峰限流从简单到复杂:简单程度技术方案最简单百分比流量拒绝(随机、没有先到先得不够公平)简单原子操作限流(优先级使用服务器内存、其次远程内存服务)稍麻烦队列限流(先到先得,公平)服务稳定性在高并发的场景,有时候为了保证核心业务的正常进行,我们需要对一些次要的业务进行服务降级。简单的降级方案如下:配置开关降级:手动进行配置开关降级定时开关降级:自动定时降级系统架构关于系统架构,不用想的太复杂,简单的拆离此业务即可。运维架构部署层面,尽可能的把此类服务单独部署。武器"工欲善其事,必先利其器",处理高并发我们当然少不了好的武器。以下是高并发“三剑客”:技术名词说明异步异步回调,层层回调似灾难(Promise也是很臃肿的链式代码)epollIO多路复用,nginx/redis方案协程轻量,用户态调度高并发能力

April 19, 2019 · 1 min · jiezi

nodes 操作MySQL 踩坑记录

使用nodejs链接MySQL先安装,用淘宝镜像npm i mysql –registry=https://registry.npm.taobao.org然后代码中const mysql = require(‘mysql’)// 创建连接对象const con = mysql.createConnection({ host:’localhost’, user:‘root’, password:‘password’, port:3306, database:‘myblog’, insecureAuth : true})// 开始连接con.connect()// 执行sql语句const sql = “select * from users;“con.query(sql, (err, result)=>{ if(err){ console.log(err) return } console.log(result)})// 关闭连接con.end()然后控制台执行node index.js报错了。。Client does not support authentication protocol requested by server; consider upgrading MySQL client这个问题的原因应该没有权限,解决方法呢,是修改加密规则进入的MySQL中操作Mac怎么进入MySQL呢?,执行以下/usr/local/mysql/bin/mysql -u root -p回车输入数据库root账户的密码进入到MySQL模式然后执行下面的操作,如图ALTER USER ‘root’@’localhost’ IDENTIFIED BY ‘password’ PASSWORD EXPIRE NEVER;ALTER USER ‘root’@’localhost’ IDENTIFIED WITH mysql_native_password BY ‘password’;FLUSH PRIVILEGES;以为这回可以正常使用了,结果又报错了。Access denied for user ‘root’@’localhost’ (using password: YES)浏览器搜索了一下为什么会这样大家的解释如下: 1)mysql的服务器停止2)用户的端口号或者IP导致 3)mysql的配置文件错误—-my.ini等文件4)root用户的密码错误然后去反思自己曾经的操作。。。发现:ALTER USER ‘root’@’localhost’ IDENTIFIED BY ‘新密码’;这一步操作,我把自己的密码更改了/(ㄒoㄒ)/~~隧使用新密码,修改后,执行,成功! ...

April 18, 2019 · 1 min · jiezi

使用idea创建SpringBoot项目

首先我们打开idea。这里我是用的版本是2019.1.这边项目名选默认的就可以了,项目地址个人建议用一个专门的文件夹进行存放。

April 18, 2019 · 1 min · jiezi

如何给列表降维?sum()函数的妙用

上个月,学习群里的 S 同学问了个题目,大意可理解为列表降维 ,例子如下:oldlist = [[1, 2, 3], [4, 5]]# 想得到结果:newlist = [1, 2, 3, 4, 5]原始数据是一个二维列表,目的是获取该列表中所有元素的具体值。从抽象一点的角度来理解,也可看作是列表解压或者列表降维。这个问题并不难,但是,怎么写才比较优雅呢?# 方法一,粗暴拼接法:newlist = oldlist[0] + oldlist[1]这种方法简单粗暴,需要拼接什么内容,就取出来直接拼接。然而,如果原列表有很多子列表,则这个方法就会变得繁琐了。我们把原问题升级一下:一个二维列表包含 n 个一维列表元素,如何优雅地把这些子列表拼成一个新的一维列表? 方法一的做法需要写 n 个对象,以及 n - 1 次拼接操作。当然不可行。下面看看方法二:# 方法二,列表推导式:newlist = [i for j in range(len(oldlist)) for i in oldlist[j]]这个表达式中出现了两个 for 语句,在第一个 for 语句中,我们先取出原列表的长度,然后构造 range 对象,此时 j 的取值范围是 [0, n-1] 的闭区间。在第二个 for 语句中,oldlist[j] 指的正是原列表的第 j 个子列表,for i in oldlist[j] 则会遍历取出 j 子列表的元素,由于 j 取值的区间正对应于原列表的全部索引值,所以,最终达到解题目的。这种方法足够优雅了,而且理解也并不难。然而,我们是否就能满足于此了呢?有没有其它奇技淫巧,哦不,是其它高级方法呢?F 同学贡献了一个思路:# 方法三,巧用sum:newlist = sum(oldlist,[])说实话,这个方法令我大感意外!sum() 函数不是用于求和的么?怎么竟然有此用法?这个写法利用了什么原理呢?由于我开始时不知道 sum() 函数可以接收两个参数,不清楚它们是怎么用于计算的,所以一度很困惑。但是,当我知道 sum() 的完整用法时,我恍然大悟。接下来也不卖关子了,直接揭晓吧。语法: sum(iterable[, start]) ,sum() 函数的第一个参数是可迭代对象,如列表、元组或集合等,第二个参数是起始值,默认为 0 。其用途是以 start 值为基础,再与可迭代对象的所有元素相“加”。在上例中,执行效果是 oldlist 中的子列表逐一与第二个参数相加,而列表的加法相当于 extend 操作,所以最终结果是由 [] 扩充成的列表。这里有两个关键点:sum() 函数允许带两个参数,且第二个参数才是起点。 可能 sum() 函数用于数值求和比较多,然而用于作列表的求和,就有奇效。它比列表推导式更加优雅简洁!至此,前面的升级版问题就得到了很好的回答。简单回顾一下,s 同学最初的问题可以用三种方法实现,第一种方法中规中矩,第二种方法正道进阶,而第三种方法旁门左道(没有贬义,只是说它出人意料,却效果奇佳)。这道并不算难的问题,在众人的讨论与分享后,竟还引出了很有价值的学习内容。前不久,同样是群内的一个问题,也产生了同样的学习效果,详见《Python进阶:如何将字符串常量转为变量?》。我从中得到了一个启示:应该多角度地思考问题,设法寻求更优解,同时,基础知识应掌握牢固,并灵活贯通起来。 学无止境,这里我还想再开拓一下思路,看看能发现些什么。1、如果原列表的元素除了列表,还有其它类型的元素,怎么把同类的元素归并在一起呢?2、如果是一个三维或更高维的列表,怎么更好地把它们压缩成一维列表呢?3、sum() 函数还有什么知识要点呢?前两个问题增加了复杂度,解决起来似乎没有“灵丹妙药”了,只能用笨方法分别拆解,逐一解压。第三个思考题是关于 sum() 函数本身的用法,我们看看官方文档是怎么说的:The iterable’s items are normally numbers, and the start value is not allowed to be a string.For some use cases, there are good alternatives to sum(). The preferred, fast way to concatenate a sequence of strings is by calling ‘’.join(sequence). To add floating point values with extended precision, see math.fsum(). To concatenate a series of iterables, consider using itertools.chain().sum() 的第二个参数不允许是字符串。如果用了,会报错:TypeError: sum() can’t sum strings [use ‘’.join(seq) instead]为什么不建议使用 sum() 来拼接字符串呢?哈哈,文档中建议使用 join() 方法,因为它更快。为了不给我们使用慢的方法,它竟特别限定不允许 sum() 的第二个参数是字符串。文档还建议,在某些使用场景时,不要用 sum() ,例如当以扩展精度对浮点数求和时,推荐使用 math.fsum() ;当要拼接一系列的可迭代对象时,应考虑使用 itertools.chain() 。浮点数的计算是个难题,我曾转载过一篇《如何在 Python 里面精确四舍五入?》,对此有精彩分析。而itertools.chain() 可以将不同类型的可迭代对象串联成一个更大的迭代器,这在旧文《Python进阶:设计模式之迭代器模式》中也有论及。不经意间,sum() 函数的注意事项,竟把 Python 其它的进阶内容都联系起来了。小小的函数,竟成为学习之路上的一个枢纽。前段时间,我还写过 range() 、locals() 和 eval() 等内置函数,也是通过一个问题点,而关联出多个知识点, 获益良多。这些内置函数/类的魔力可真不小啊。本文到此结束,希望对你有所帮助。友情提示: 本公众号内有限时抽奖活动,送出 Python 书籍 15 本,书目有《Python数据分析与挖掘实战》、《Python语言程序设计》《自学Python编程基础、科学计算及数据分析》《实用机器学习》、《Python程序员面试算法宝典》,活动结束时间为本月 20 日 18 点 ,赶快来抽奖啦!详情请戳:抽奖送书公众号【Python猫】, 专注Python技术、数据科学和深度学习,力图创造一个有趣又有用的学习分享平台。本号连载优质的系列文章,有喵星哲学猫系列、Python进阶系列、好书推荐系列、优质英文推荐与翻译等等,欢迎关注哦。PS:后台回复“爱学习”,免费获得一份学习大礼包。 ...

April 18, 2019 · 1 min · jiezi

mysql中order by 使用索引问题

问题:order by 排序字段会不会使用索引?创建表结构DROP TABLE IF EXISTS test.test;CREATE TABLE test.test(id int(10) not null auto_increment,a int(10) not null,b int(10) not null,c int(10) not null,PRIMARY key (id))ENGINE INNODB DEFAULT CHARSET utf8 COMMENT ‘测试表’;批量插入1000万条mysql> explain select * from test order by b desc limit 10;+—-+————-+——-+————+——+—————+——+———+——+———+———-+—————-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——+—————+——+———+——+———+———-+—————-+| 1 | SIMPLE | test | NULL | ALL | NULL | NULL | NULL | NULL | 9979716 | 100.00 | Using filesort |+—-+————-+——-+————+——+—————+——+———+——+———+———-+—————-+1 row in set, 1 warning (0.00 sec)现象:执行计划是全盘扫描增加索引mysql> ALTER TABLE test ADD INDEX idx_b (b);Query OK, 0 rows affected (12.57 sec)再次查询mysql> explain select * from test order by b desc limit 10;+—-+————-+——-+————+——-+—————+——-+———+——+——+———-+——-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+——-+———+——+——+———-+——-+| 1 | SIMPLE | test | NULL | index | NULL | idx_b | 4 | NULL | 10 | 100.00 | NULL |+—-+————-+——-+————+——-+—————+——-+———+——+——+———-+——-+1 row in set, 1 warning (0.00 sec)现象:命中索引,执行计划只扫描10行结论:order by后面的的字段需要创建索引。 ...

April 17, 2019 · 1 min · jiezi

MySQL - 扩展性 1 概述:人多未必力量大

我们应该接触过或者听说过数据库的性能瓶颈问题。对于一个单机应用而言,提升数据库性能的最快路径就是氪金 - 买更高性能的数据库服务器,只要钱到位,性能不是问题。但是当系统性能增加到一定地步时,你会发现,原先花 3000 块提升了 50% 的性能,现在花 30000 块,才提升了不到 10%。也就是说,我们花了钱,但没有得到等价的性能提升,这个时候,我们就要考虑数据库的可扩展性了。要讨论 MySQL 的可扩展性,就要先明确可扩展性的定义。在此之前,我们先抛开 MySQL,专注于扩展性,搞清楚什么是扩展性,才能更有针对性的去提升数据库的扩展性。1 什么是可扩展性我们常常把“可扩展性”、“高可用性”以及“性能”用作同义词,但事实上它们是完全不同的。简单来说,性能是响应时间,可用性是宕机时间,而扩展性表明了当需要增加资源以执行更多工作时,系统能够获得等价的性能提升的能力。换种说法,可扩展性就是我们能够尽可能的花费相同的资源提升等价的性能。而缺乏扩展能力的系统在达到收益递减的转折点后,将无法进一步增长。容量是一个和可扩展性相关的概念。系统容量表示在一定时间内能够完成的工作量。容量和可扩展性并不依赖于性能。以高速公路上的汽车来类比的话:性能是汽车的时速。容量是车道乘以最大安全时速。可扩展性就是在不减慢交通的情况下,能增加更多车和车道的程度。在上面这个类比中,可扩展性依赖多个条件:换道设计是否合理、路上有多少车抛锚或发生事故、汽车行驶速度不同以及是否频繁变换车道。但一般来说,和汽车的引擎是否强大无关。这并不是说性能不重要,性能确实重要,只是要注意的是,即使系统性能不是很高的系统也可以具备可扩展性。从较高层次看,可扩展性就是能够通过增加资源来提升容量的能力。对于容量,我们可以简单的认为是处理负载的能力,而从不同的角度考虑负载对我们优化扩展性很有帮助。数据量应用所能累计的数据量是可扩展性最普遍的挑战,特别是对于现在的互联网应用而言,因为从不删除数据。用户量首先,即使每个用户只有少量的数据,但在累计到一定数量的用户后,数据量也会开始不成比例的增长,且速度快过用户数增长。其次,更多的用户意味着要处理更多的事务,并且事务数可能和用户数不成比例。最后,大量用户也意味着更多复杂的查询。用户活跃度不是所有的用户活跃度都相同,并且用户活跃度也不总是不变的。如果用户突然变得活跃,例如 github 给小团队免费开放了私有化仓库,那么其对应的负载可能会明显提升。要注意的是,用户活跃度不仅仅指页面浏览数(PV),即使同样的 PV,如果网站的某个需要执行大量查询工作的功能变得更受欢迎,也可能导致更多的工作。相关数据集的大小如果用户间存在关系,应用可能需要在整个相关联用户群体上执行查询和计算,这比处理一个个的用户和用户数据要复杂的多。说了这么多,只是为了让我们更好的理解可扩展性的让我们用下面图表来更明确的表达可扩展性。假设有一个只有一台服务器的系统,并且能够测量它的最大容量,如图 1 所示:假设我们现在增加一台服务器,系统的能力加倍,如图 2 所示:图 2 就是线性扩展。我们增加了一倍的服务器,增加了一倍的容量。然而,理想是美好的,现实是骨感的。大部分系统并不是线性扩展的,而是如图 3 所示的扩展方式:大部分系统都只能以比线性扩展略低的扩展系数进行扩展。这就导致,多数系统最终会达到一个最大吞吐量临界点,超过这个点后增加投入可能反而会降低系统的吞吐量。到这一步,大家对扩展性应该已经有一个较为清晰的概念了。在此基础上,让我们再深入一步:Amdahl 扩展 和 USL 扩展。简而言之,USL 说的是线下扩展的偏差可通过两个因素来建立模型:无法并发执行的一部分工作;需要交互的另外一部分工作。在对第一个因素继续建模后,就有了著名的(听过这个著名吗?)阿姆达尔定律(Amdahl)。第一个因素最终会导致吞吐量趋于平缓。如果部分任务无法并行,那么不管你如果分而治之,该任务至少需要串行部分的时间。这句话很重要,让我们用一个栗子再简单阐述下:假设大家都做过韭菜煎蛋这道菜,我们做这道菜时,有几个必要步骤:切韭菜,耗时 t1;打蛋液,耗时 t2;开煎,耗时 t3;就上面 3 个步骤而言,你可以在切韭菜的时候,让你女票帮你打蛋液,也就是说 1、2 是可以并行的,但是我们能边切菜边煎吗?或者边打蛋液边煎吗?显示是不行的。因此,步骤 3 和 1、2 是串行的。这时候,我们就会发现,做韭菜煎蛋这个任务需要的时间 t 为:t = MAX(t1, t2) + t3;对第二个因素,需要交互的工作而言,交互就意味着内部节点间或者进程间的通信。这种通信的代价取决于通信信道的数量,而信道的数量将按照系统内工作者数量的二次方增长,所以最终开销比带来的收益增长的更快,这就是产生扩展性倒退的原因。由此和 Amdahl 定律,就得出了 USL。图 4 阐明了目前讨论的三个概念:线性扩展、Amdahl 扩展以及 USL 扩展。而大多数真实系统看起来更像 USL 曲线。至此,关于扩展性的概念描述告一段落。接下来,我们回到正题,看看 MySQL 的扩展性如何规划。2 规划可扩展性什么情况下需要扩展?,这是个值得我们牢记的问题。当我们提到系统的可扩展性时,一般只有两种情况:刚开始规划一个应用;当前应用无法满足增加的负载;上述两种情况,大多数情况下我们碰到的应该都是后者。具体表现为:CPU 密集型变成 I/O 密集型;并发查询竞争;不断增大的延迟;如果是可扩展的应用,可以简单地增加更多的服务器来分担负载。但如果是可扩展性比较差的,你就会发现 - 只剩下提高可扩展性这一条路可走。只有一条路,那就且行且 996 吧!走上了提升扩展性这条路,接下来的问题就是,如何提高可扩展性?这里比较困难的部分是估算应用承担的负载到底有多少?这个值不一定非常精确,但必须在一定的数量级范围内。什么?你问为什么要在一定范围内?不清楚敌人的火力,咱们是准备用高射炮打蚊子还是用大刀对机枪呢?除此之外,为了能帮助我们更好的规划可扩展性,咱们最好还能想清楚下面这个问题:应用的核心功能完成了多少?很多可扩展性方案可能会导致某些功能实现起来更加复杂。在核心功能没完成前,问问自己,真的要走提升扩展性这条路吗?换个说法,准备好迎接 996 了吗?3 为扩展赢得时间程序员们理想的开发环境应该是:计划先行、有足够能够一起战斗的同伴、有花不完的预算等等。但现实是:boss:诶,小九啊,咱们系统提升下性能要多久啊?三天应该差不多了吧,最多不能超过一周,上次提升性能,小六一天就搞定了的。小九:。。。卒正常情况下,提升系统的扩展性的难度可能要比重构的难度还要大。因此,在你没有完全把系统摸熟悉,或对扩展性还模糊的时候,千万别给老板说要提升系统的扩展性。在老板要求提升性能时,你要想尽一切办法满足他提升性能的需求,同时,要多想下如何提高系统的扩展性,为将来提升扩展性赢得时间。可以通过以下工作先提升系统性能:优化性能。很多时候可以通过一个简单的改动来获得明显的性能提升。例如为表建立正确的索引,或从 MyISAM 切换到 InnoDB。再进一步,可以通过慢日志来分析。购买性能更强的硬件。在应用早期,升级或增加服务器可以显著的提升系统性能,并且还能快速的完成。就像我们把服务器从 1 台增加到 3 台,可能就能让性能提升 100%,但是当我们的服务器已经到达 100 台时,再从 100 增加到 300,这时候的复杂度和成本可能已经让你心甘情愿走上提升系统扩展性的道路上了。总结扩展性是当需要增加资源以执行更多工作时,系统能够获得等价的性能提升的能力。不准确评估应用负载的扩展,都是耍流氓。 ...

April 17, 2019 · 1 min · jiezi

面试必备的数据库悲观锁与乐观锁

前言在上一个章节5分钟带你读懂事务隔离性与隔离级别 的最后,其实我们已经提到了锁的概念。本章节接下来将主要介绍以下数据库悲观锁与乐观锁的相关知识。如有错误还请大家及时指出本文已同步至 GitHub/Gitee/公众号,感兴趣的同学帮忙点波关注问题:为什么需要锁?什么是悲观锁?什么是乐观锁?悲观锁与乐观锁区别与联系?悲观锁与乐观锁的使用场景?为什么需要锁?在并发环境下,如果多个客户端访问同一条数据,此时就会产生数据不一致的问题,如何解决,通过加锁的机制,常见的有两种锁,乐观锁和悲观锁,可以在一定程度上解决并发访问。1. 悲观锁(Pessimistic Lock)1.1 定义百度百科: 悲观锁,正如其名,具有强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。其他知识点悲观锁主要是共享锁或排他锁共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。1.2 案例分析使用场景举例:以MySQL InnoDB为例作为演示,我们继续使用之前的数据库表:product表productIdproductNameproductPriceproductCount1小米19991002魅族1999100首先我们需要set autocommit=0,即不允许自动提交有看过上一篇文章5分钟带你读懂事务隔离性与隔离级别 的同学,可以看到最后我们使用事务隔离级别时,所引申出来的根本问题就是可以通过锁机制解决。问题在并发情况下回导致数据一致性的问题:如果有A、B两个用户需要抢productId =1的小米手机,A、B用户都查询小米手机数量是100,A购买后修改商品的数量为99,B购买后修改数量为99。用法每次获取小米手机时,对该商品加排他锁。也就是在用户A获取获取 id=1 的小米手机信息时对该行记录加锁,期间其他用户阻塞等待访问该记录。代码如下:start transaction; select p.productCount from product p where p.productId = 1 for update; update product p set p.productCount=p.productCount-1 where p.productId=1 ; commit;操作下面同时打开两个窗口模拟2个用户并发访问数据库时间轴事务A事务BT1start transaction; T2select p.productCount from product p where p.productId = 1 for update; T3 start transaction;T4 select p.productCount from product p where p.productId = 1 for update;(等待中…)流程说明用户A start transaction开启一个事物。前一步我们关闭了mysql的autocommit,所以需要手动控制事务的提交。在获得小米手机信息(productId = 1 )时,进行数据加锁操作(for update)。与普通查询方式不同,我们使用了select…for update的方式,这样就通过数据库实现了悲观锁。在这个update事务提交之前其他外界是不能修改这条数据的,但是这种处理方式效率比较低,一般不推荐使用。用户B start transaction开启一个事物。用户B 也进行查询操作,此时处于等待中(阻塞状态)。ps:需要等待用户A事务提交后,才会执行。注意:在事务中,只有select…for update(排他锁) 或lock in share mode(共享锁) 操作同一个数据时才会等待其它事务结束后才执行,一般select… 则不受此影响。例如在 T3中执行select p.productCount from product p where p.productId = 1;则能正常查询出数据,不会受第一个事务的影响。2. 乐观锁(Optimistic Lock)2.1 定义百度百科: 乐观锁机制采取了更加宽松的加锁机制。乐观锁是相对悲观锁而言,也是为了避免数据库幻读、业务处理时间过长等原因引起数据处理错误的一种机制,但乐观锁不会刻意使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性。其他知识点实现乐观锁一般来说有以下2种方式:使用版本号使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。使用时间戳乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。2.2 案例分析使用场景举例:以MySQL InnoDB为例作为演示,我们继续使用之前的数据库表:product表productIdproductNameproductPriceproductCountversion1小米199910012魅族19991002我们以版本号实现的方式进行说明。操作查询当前事务隔离级别:SELECT @@tx_isolation;结果:REPEATABLE-READ下面同时打开两个窗口模拟2个用户并发访问数据库第一种测试时间轴用户A用户BT1start transaction; T2select * from product p where p.productId = 1;(productCount=100) T3update product p set p.productCount = 99,version=version+1 where p.productId = 1 and version = 1;(受影响的行: 1) T4 start transaction;T5 select * from product p where p.productId = 1;(productCount=100)T6 update product p set p.productCount = 99,version=version+1 where p.productId = 1 and version = 1;(等待中…)T7commit; T8 T6执行(受影响的行: 0)T9 commit;流程说明事务A开启事务。事务A查询当前小米手机数量为100。事务A购买小米手机,小米手机数量更新为99。(此时并未提交事务)。事务B开启事务。事务B查询当前小米手机数量为100。事务B购买小米手机,小米手机数量更新为99。注意:此时处于阻塞状态。事务A提交事务。此时第六步执行完毕,但并未成功(受影响的行: 0)。事务B提交事务。第二种测试时间轴用户A用户BT1select * from product p where p.productId = 1;(productCount=100) T2update product p set p.productCount = 99,version=version+1 where p.productId = 1 and version = 1;(受影响的行: 1) T3 select * from product p where p.productId = 1;(productCount=100)T4 update product p set p.productCount = 99,version=version+1 where p.productId = 1 and version = 1;(受影响的行: 0)乐观锁小结用户B修改数据的时候,受影响行数为0,对业务来说,及更新失败。这时候我们只需要告诉用户购买失败,重新查询一遍即可。对比第一种和第二种测试,我们会发现第一种测试,将update语句放入事务中会出现阻塞的情况,而第二种测试不会出现阻塞情况。这是为什么呢?update其实在不在事务中都无所谓,在内部是这样的:update是单线程的,及如果一个线程对一条数据进行update操作,会获得锁,其他线程如果要对同一条数据操作会阻塞,直到这个线程update成功后释放锁。乐观锁不需要数据库底层的支持!3. 适用场景悲观锁比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。乐观锁比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。文末本章节主要简单介绍了数据库中乐观锁与悲观锁的相关知识,后续我们将会继续介绍数据库中的其他锁以及相关知识。例如行锁、表锁、死锁、欢迎关注公众号:Coder编程获取最新原创技术文章和相关免费学习资料,随时随地学习技术知识!参考文章:https://chenzhou123520.iteye….https://chenzhou123520.iteye….推荐阅读带你了解数据库中JOIN的用法 带你了解数据库中事务的ACID特性 5分钟带你读懂事务隔离性与隔离级别 ...

April 16, 2019 · 1 min · jiezi

MySQL 乐观锁于悲观锁

MySQL5.5 版本之后默认采用innoDb 数据引擎.本文采用默认的存储引擎。乐观锁乐观锁实际上是一种逻辑思想,并不是mysql 数据库的特性。这个要区分清楚。实现数据版本有两种方式,第一种是使用版本号,第二种是使用时间戳。使用方式::/** 伪代码 number 库存 goods_id 商品ID version 版本号默认为0 **/$sql=“select number from goods where goods_id={$goods_id} and version= {$version} “;// 时间戳方式查询库存$sql=“select number from goods where goods_id={$goods_id} and time < time() “;$rs=mysqli_query($conn,$sql);$row = $rs->fetch_assoc();if($row[’number’]>0){//高并发下会导致超卖 if($row[’number’]<$number){ return insertLog(‘库存不够’,3,$username); }//库存减少 $sql=“update goods set number=number-{$number},version += 1 where goods_id={$goods_id} and number>0”;// 时间戳方式减少库存$sql=“update goods set number=number-{$number},time = time() where goods_id={$goods_id} and number>0”;$store_rs=mysqli_query($conn,$sql);if($store_rs){//生成订单,返回操作成功 echo json_encode(array(‘code’=>0,‘msg’=>‘库存减少成功’,‘data’=>$username));}else{ echo json_encode(array(‘code’=>1,‘msg’=>‘库存减少失败’,‘data’=>$username));}}else{ echo json_encode(array(‘code’=>3,‘msg’=>‘库存减少失败’,‘data’=>$username));} 注意:使用乐观锁需要注意啊,将库存字段number字段设为unsigned,当库存为0时,因为字段不能为负数,将会返回false悲观锁悲观锁指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。悲观锁利用了MySQL innoDB 存储引擎的支持行锁的特性。一行一行操作数据.使用方式:// 使用悲观锁锁住当前行$sql=“select number from goods where goods_id={$goods_id} for update “;// 减少库存操作$sql=“update goods set number=number-{$number} where goods_id={$goods_id} and number>0”; 注意:innoDB 行锁是基于索引来执行的,where 条件后必须有索引,不然走的就是全表扫描。优点与不足:悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数乐观并发控制相信事务之间的数据竞争(datarace)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。但如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题。建议: 在用户并发量不大的应用场景下,采用乐观锁的方式。在对数据一致性要求很高的情况下,可以牺牲一下性能,采用悲观锁。这个两种方式的前提是采用MySQL的解决方案。 ...

April 16, 2019 · 1 min · jiezi

Python3基础知识

Python3基础知识 | 基础语法 Python3基础知识 | 编程第一步 Python3基础知识 | 基本数据类型Python3基础知识 | 解释器 Python3基础知识 | 注释 Python3基础知识 | 运算符Python3基础知识 | 数字 Python3基础知识 | 字符串 Python3基础知识 | 列表Python3基础知识 | 元组 Python3基础知识 | 字典 Python3基础知识 | 条件控制Python3基础知识 | 循环 Python3基础知识 | 迭代器与生成器 Python3基础知识 | 函数Python3基础知识 | 数据结构 Python3基础知识 | 模块 Python3基础知识 | 输入和输出Python3基础知识 | File 方法 Python3基础知识 | OS 文件目录方法 Python3基础知识 | 错误和异常Python3基础知识 | 标准库概览 Python3基础知识 | 日期和时间 Python3基础知识 | 正则表达式Python3基础知识 | XML解析 Python3基础知识 | JSON 数据解析 Python3基础知识 | MySQL 数据库连接Python3基础知识 | 多线程 Python3基础知识 | 面向对象 ...

April 16, 2019 · 1 min · jiezi

阿里云中间件培训

April 15, 2019 · 0 min · jiezi

MySQL常用SQL时间查询语句

1.查询当天SELECT * FROM article WHERE to_days(add_time) = to_days(now());2.查询昨天SELECT * FROM article WHERE to_days(now()) – to_days(add_time) = 1;3.查询最近7天SELECT * FROM article WHERE date_sub(curdate(), INTERVAL 7 DAY) <= DATE(add_time);//ORSELECT * FROM article WHERE curdate()- INTERVAL 7 DAY <= DATE(add_time);4.查询最近30天SELECT * FROM article WHERE date_sub(curdate(), INTERVAL 30 DAY) <= DATE(add_time);//ORSELECT * FROM article WHERE curdate()-INTERVAL 30 DAY <= DATE(add_time);5.查询截止到当前本周SELECT * FROM article WHERE YEARWEEK(date_format(add_time,’%Y-%m-%d’)) = YEARWEEK(now());#默认从周日开始到周六SELECT * FROM article WHERE YEARWEEK(date_format(add_time,’%Y-%m-%d’),1) = YEARWEEK(now(),1);#设置为从周一开始到周日6.查询上周的数据SELECT * FROM article WHERE YEARWEEK(date_format(add_time,’%Y-%m-%d’)) = YEARWEEK(now())-1;7.查询截止到当前本月SELECT * FROM article WHERE date_format(add_time, ‘%Y%m’) = date_format(curdate() , ‘%Y%m’);8.查询上一月SELECT * FROM article WHERE period_diff(date_format(now() , ‘%Y%m’) , date_format(add_time, ‘%Y%m’)) =1;SELECT * FROM ke_order_list WHERE add_time BETWEEN ‘2019-03-01’ AND ‘2019-04-01’;SELECT * FROM ke_order_list WHERE add_time LIKE ‘2019-03%’ ...

April 15, 2019 · 1 min · jiezi

vue中修改Modal的重置功能怎么写?(重置不单独调后台接口)可利用深拷贝

工作中遇到弹出模态框形式的修改功能,模态框里面是Form表单,Form表单中的内容是从后台获取的,这时候用户修改完没有提交,而是想重置然后重新修改,怎么办呢?①重新调一遍后台的接口,将原始数据再一次放入Form中,这个方法可能会浪费资源,占用带宽②可以利用缓存③可以利用深拷贝简单解释一下深拷贝和浅拷贝a复制b,修改b,a也发生改变,说明拷贝不彻底,此为浅拷贝,a复制b,修改b,a不变,此为深拷贝深拷贝一般复杂数据类型才会发生,原因是基本数据类型和复杂数据类型的存储方式不同。关于深拷贝和浅拷贝的文章有很多,想多了解的同学可以自行搜索下面直接说方法我们可以封装一个深拷贝函数,在随便一个js文件里面(根据自己的项目) /** * 深度拷贝 * @param {*} arr */ export const copyArray = (arr) => { return JSON.parse(JSON.stringify(arr)) } 然后在你用到修改功能的vue文件中,直接引入使用就可以le import { copyArray } from ‘@/utils/util.js’ //修改的时候 this.formInline = copyArray(data)这是深拷贝的json方式,深拷贝的方式有很多,可自行了解说一下这种方式的原理吧上面说过基本数据类型没有深拷贝,json.stringify()将数组数据类型转换成字符串数据类型字符串属于基本数据类型,基本数据类型是按值传递的 var b = 1; var a = b; b++; console.log(a,b)//1,2这时候进行拷贝,然后在将字符串转换成对象,就实现了深拷贝加油!每天进步一点点!

April 15, 2019 · 1 min · jiezi

MySQL有哪些索引类型

从数据结构角度1、B+树索引(O(log(n))):关于B+树索引,可以参考 MySQL索引背后的数据结构及算法原理2、hash索引:a 仅仅能满足"=",“IN"和”<=>“查询,不能使用范围查询b 其检索效率非常高,索引的检索可以一次定位,不像B-Tree 索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问,所以 Hash 索引的查询效率要远高于 B-Tree 索引c 只有Memory存储引擎显示支持hash索引3、FULLTEXT索引(现在MyISAM和InnoDB引擎都支持了)4、R-Tree索引(用于对GIS数据类型创建SPATIAL索引)从物理存储角度1、聚集索引(clustered index)2、非聚集索引(non-clustered index)从逻辑角度1、主键索引:主键索引是一种特殊的唯一索引,不允许有空值2、普通索引或者单列索引3、多列索引(复合索引):复合索引指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用复合索引时遵循最左前缀集合4、唯一索引或者非唯一索引5、空间索引:空间索引是对空间数据类型的字段建立的索引,MYSQL中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON。MYSQL使用SPATIAL关键字进行扩展,使得能够用于创建正规索引类型的语法创建空间索引。创建空间索引的列,必须将其声明为NOT NULL,空间索引只能在存储引擎为MYISAM的表中创建CREATE TABLE table_name[col_name data type][unique|fulltext|spatial][index|key]index_name[asc|desc]1、unique|fulltext|spatial为可选参数,分别表示唯一索引、全文索引和空间索引;2、index和key为同义词,两者作用相同,用来指定创建索引3、col_name为需要创建索引的字段列,该列必须从数据表中该定义的多个列中选择;4、index_name指定索引的名称,为可选参数,如果不指定,MYSQL默认col_name为索引值;5、length为可选参数,表示索引的长度,只有字符串类型的字段才能指定索引长度;6、asc或desc指定升序或降序的索引值存储

April 15, 2019 · 1 min · jiezi

活动:送两本《PHP 程序员面试笔试真题库》

你好,是我琉忆。由于近期工作忙碌,没有时间去更新PHP面试专栏。现在特此搞个抽奖活动送2本书给大家。给各位面试助力,尽快找到一份满意的PHP工作。《 PHP 程序员面试笔试真题库》历时一年,由机械工业出版社出版,在 2019 年 4 月问世。本书的适用群体:刚接触 PHP,自学一段时间 PHP 后打算去找 PHP 相关的 PHP 面试工作的群体。本书主要抽取了各大互联网公司往年 PHP 的面试笔试真题,然后抽出整理成书,为你的面试笔试助力。现免费送出 2 本《 PHP 程序员面试笔试真题库 》,具体参与规则如下:1、在本文下留言即可参与,并且一个用户的多次留言只算作一次。2、活动截止日期为北京时间 2019 年 4 月 19 日 19:59:59(本周五),以评论时间为准。(2019年4月 19 日晚上 21 点开奖)3、在活动结束后随机抽取 2 名幸运网友。4、获赠名单在本帖后会公布,并 私信该网友。5、幸运网友把收件地址、姓名和手机号发送给我安排邮寄。6、全国包邮(不含新疆、西藏、港澳台)7、保留最终解释权。PS:该书已在天猫、京东、当当等电商平台销售。获取更多PHP程序员面试笔试资料可以关注:琉忆编程库

April 15, 2019 · 1 min · jiezi

MySQL 复制 - 性能与扩展性的基石 4:主备切换

一旦使用 MySQL 的复制功能,就很大可能会碰到主备切换的情况。也许是为了迭代升级服务器,或者是主库出现问题时,将一台备库转换成主库,或者只是希望重新分配容量。不过出于什么原因,都需要将新主库的信息告诉其它备库。对于主备切换,如果是计划内的操作,较为容易(至少比紧急情况下容易)。只需在备库简单的使用 CHANGE MASTER TO 命令,并指定合适的值即可。而且大多数的值是可选的,只要指定需要改变的配置项接口。备库将抛弃之前的配置和中继日志,并从新的主库开始复制。同时,新的参数会被更新到 master.info 文件中,这样就算重启,备库配置信息也不会丢失。整个过程中最难的是获取新主库上合适的二进制日志位置。这样备库才可以从老主库相同的逻辑位置开始复制。把备库提升为主库要较为麻烦,我们把备库提升主库分为计划内切换和计划外切换两种场景。1 计划内切换备库提升为主库,简单来说,有以下步骤:停止向老主库写入。让备库追赶上主库(可选,可以简化后续的步骤)。将一台备库配置为新主库。将备库和写操作指向新主库,然后开启主库写入。但上面的过程中还因此着很多细节。一些场景可能依赖于复制的拓扑结构。更深入一点,下面是大多数配置需要的步骤:停止当前主库上的所有写操作。如果可以,最好能将所有的客户端程序关闭(除了复制连接)。通过 FLUSH TABLE WITH READ LOCK 命令在主库上停止所有活跃的写入。也可以在主库上设置 read_only 选项。意味着从这一刻起,禁止向老主库做任何写入操作。因为一旦切换的新主库,老主库的写入就意味着数据丢失。要注意的是,即使设置了 read_only 也不会阻止当前已存在的事务继续提交。因此,可以 kill 所有打开的事务,真正的结束所有写入。选择一个备库作为新的主库,并确保它已经完全跟上主库(例如,让它执行完所有从主库获得的中继日志)。确保新主库和老主库数据一致。在新主库上执行 STOP SLAVE。在新主库上执行 CHANGE MASTER TO MASTER_HOST=’’,然后再执行 RESET SLAVE,使其断开与老主库的连接,并丢弃 master.info 里记录的信息(如果连接信息记录在 my.cnf 里,会无法正常工作,因此我们建议不要把复制连接信息写到配置文件里)。执行 SHOW MASTER STATUS 记录新主库的二进制日志坐标。确保其它备库已经追赶上老主库。关闭老主库。将客户端连接到新主库。在每台备库上执行 CHANGE MASTER TO 语句,使用之前获得的二进制日志坐标,指向新的主库。2 计划外切换当主库崩溃时,需要将一台备库提升为主库。这个过程就比较麻烦。如果只有一台备库,可以直接使用这台备库。但如果有超过一台的备库,就需要做一些额外的工作。另外,还有潜在的丢失复制事件的问题。可能有主库上已发生的修改还没有更新到它任何一台备库上的情况。甚至可能一条语句在主库上执行了回滚,但在备库上没有回滚,这样备库可能就超过主库的逻辑复制位置。如果能在某一点恢复主库的数据,也许就可以取得丢失语句,并手动执行他们。在以下描述中,需要确保在服务器中使用 Master_Log_File 和 Read_Master_Log_Pos 的值。2.1 主备结构之备库提升确定哪台备库的数据最新。检查每台备库上 SHOW_SLAVE_STATUS 命令的输出,选择其中 Master_Log_File 和 Read_Master_Log_Pos 的值最新的那个。让所有备库执行完所有从老主库崩溃前获得的中继日志。在新主库上执行 STOP SLAVE。在新主库上执行 CHANGE MASTER TO MASTER_HOST=’’,然后再执行 RESET SLAVE,使其断开与老主库的连接,并丢弃 master.info 里记录的信息。执行 SHOW MASTER STATUS 记录新主库的二进制日志坐标。比较每台备库和新主库上的 Master_Log_File 和 Read_Master_Log_Pos 的值。将客户端连接到新主库。在每台备库上执行 CHANGE MASTER TO 语句,使用之前获得的二进制日志坐标,指向新的主库。如果已经在所有备库上开启了 log_bin 和 log_slave_updates,就可以将所有备库恢复到一个一致的时间点,如果没有开启这两个选项,则很难做到这一点。上面过程中比较重要的一点是确定日志位置。接下来,我们就来看看如何却。3 确定日志位置如果有备库和新主库的位置不相同,则需要找到该备库最后一条执行的事件在新主库的二进制日志中对应的位置,然后再执行 CHANGE MASTER TO。可以通过 mysqlbinlog 工具来找到备库执行的最后一条查询,然后再主库上找到同样的查询,进行简单的计算即可得到。为了便于描述,假设每个日志事件都有一个自增数字 ID。新主库在老主库崩溃时获得了编号为 100 的事件,另外两条备库:R2 和 R3。R2 已结获取了 99 号事件,R3 获取了 98 号事件。如果把 R2 和 R3 都指向新主库的同一个二进制日志位置,它们将从 101 号事件开始复制,从而导致数据不同步。但只要新主库的二进制日志已结通过 log_slave_updates 打开,就可以在新主库的二进制日志中找到 99 号 和 100 号事件,从而将备库恢复到一致的状态。由于服务器重启,不同的配置,日志轮转或者 FLUSH LOGS 命令,同一个事件在不同的服务器上可能有不同的偏移量。我们可以通过 mysqlbinlog 从二进制日志或中继日志中解析出每台备库上执行的最后一个事件,并还有该命令解析新主库上的二进制文件,找到相同的查询,mysqlbinlog 会打印出该事件的偏移量,在 CHANGE MASTER TO 命令中使用这个值。更快的方法是把新主库和停止的备库上的字节偏移量相减,它显示了字节位置的差异。然后把这个值和新主库当前二进制日志的位置相减,就可以得到期望的查询位置。一起来看个栗子。假设 s1 是 s2 和 s3 的主库。其中 s1 已经崩溃。根据 SHOW SLAVE STATUS 获得 Master_Log_File 和 Read_Master_Log_Pos 的值,s2 已结执行完了 s1 上所有的二进制日志,但 s3 还没有。如图 1:我们可以肯定 s2 已经执行完了主库上的所有二进制日志,因为 Master_log_File 和 Read_Master_Log_Pos 的值和 s1 上最后的日志位置相吻合。因此,我们可以将 s2 提升为新主库,并将 s3 设置为 s2 的备库。应该在 s3 上为需要执行的 CHANGE MASTER TO 语句赋予什么参数呢?这里需要做一点计算。s3 在偏移量 1493 处停止,比 s2 执行的最后一条语句的偏移量 1582 要小 89 字节。s2 正在向偏移量为 8167 的二进制日志写入,因此,理论上我们应该将 s3 指向 s2 日志的偏移量为 8167-89=8078 的位置。最后在 s2 日志中的 8078 位置,确定该位置上是否是正确的日志事件。如果验证没问题,可以通过下面命令将 s3 切换为 s2 的备库:CHANGE MASTER TO MASTER_HOST=“s2 host”, MASTER_LOG_FILE=“mysql-bin.000009”, MASTER_LOG_POS=8078;如果服务器在它崩溃时已经执行完成并记录了一个事件 a。因为 s2 仅仅读取并执行到了 1582,因此可能会失去事件 a。但是如果老主库的磁盘没有损坏,仍然可以通过 mysqlbinlog 或者从日志服务器的二进制日志中找到丢失的事件。总结备库提升区分计划内和计划外场景。备库提升,找到新主库准确的二进制日志位置是关键。 ...

April 15, 2019 · 1 min · jiezi

MYSQL统计行数时到底应该怎么COUNT

相信每个人在写代码时都有遇到过要获取MYSQL表里数据行数的情况,多数人获取数据表行数时都用COUNT(),但同时也流传了不少其他版本,比如说COUNT(1)、COUNT(主键)、COUNT(字段)。到底那个版本MYSQL执行起来更快也是众说纷纭,其实之前我也不知道到底哪个执行起来快,到底谁说的对(笑哭)。好在最近在认真学习极客时间的MySQL专栏,其中专门有一节是对这个问题的讨论,看完后也是解除了长久以来的疑惑。文章中都是针对MySQL的InnoDB引擎展开讨论的,MyISAM引擎是把一个表的总行数记录在了磁盘里,查询时效率很高(如果加了where条件也不能直接从磁盘返回)。而InnoDB由于多版本并发控制(MVCC)的原因,即使时同一时刻的查询InnoDB表应该"返回多少行"也是不确定的,比如假设表t中有10000行数据:时刻会话A会话B会话CT1begin(默认可重复读隔离级别拿到一致性视图); T2select count() from tbegin(默认可重复读隔离级别拿到一致性视图); T3 insert into t (插入一行);T4select count() from tbegin拿到一致性视图); T5 insert into t (插入一行);insert into t (插入一行);T6select count() from t; (返回10000)select count() from t; (返回10000)begin;select count() from t; (返回10002)会话A在T1开启事务拿到一致性视图,可重复读级别下在事务中任何时刻读到数据都一样,其他事务的更新对会话A没影响所以count()的结果是10000,会话B在T4开启事务拿到一致性视图,T4之前会话C已经新插入了一条语句并提交(单独执行一条更新语句,InnoDB会自己启动一个事务,语句执行完马上提交)。会话B在T5插入一条新数据,在T6查询时count()的结果是10002(T4 begin时会话C insert语句已经提交,所以在会话B的事务中能看到这个更新)。由于会话B在T6时事务还没有提交,会话C看不到会话B的更新,所以会话C在T6时count()的结果是10001。COUNT是一个聚合函数,它的功能是对返回的结果集中每一行进行判断,如果COUNT函数的参数不是NULL则累加1,否则不累加,最后返回累计值。接下来看一下每个COUNT版本的执行效率:COUNT(主键ID) InnoDB遍历全表,把每一行的主键值都取出来返回给MySQL的Server层,因为主键不可能为NULL,Server层直接按行累加最后返回累计值给客户端。COUNT(1) 遍历全表但不取值,Server层对返回的每一行放个数字"1"进去,按行累加。COUNT(1)比COUNT(主键)快,因为不需要取值,减少了数据传输。COUNT(字段) 遍历全表,一行行从记录中读出字段值给Server层,Server层判断值不为NULL了再累加。COUNT() MySQL专门做了优化,会找到表中最小的索引树,InnoDB普通索引树比主键索引小很多,对于COUNT()遍历哪个树是一样的,count()时MySQL不取记录值,count()也肯定不为NULL,Server层中直接按行累加。所以这个版本COUNT的从低到高分别为:COUNT(字段) < COUNT(主键) < COUNT(1) ≈ COUNT()所以建议你尽量使用count(*)来获取记录行数。另外要注意,很多人为了销量会把表的行数记录到Redis中,但这样不能保证Redis里的计数和MySQL表里的数据保持精确一致,这是两个不同的存储系统不支持分布式事务所以就无法拿到精确的一致性视图,如果为了效率把表行数单独存储那么最好存放在一个单独的MySQL表里,这样无法拿到一致性视图的问题就能解决了。关于MySQL更详细的分析,推荐关注MySQL实战45讲

April 14, 2019 · 1 min · jiezi

工具集核心教程 | 第四篇: Velocity模板引擎入门到进阶

Velocity是一个基于java的模板引擎(template engine)。它允许任何人仅仅简单的使用模板语言(template language)来引用由java代码定义的对象。 当Velocity应用于web开发时,界面设计人员可以和java程序开发人员同步开发一个遵循MVC架构的web站点,也就是说,页面设计人员可以只 关注页面的显示效果,而由java程序开发人员关注业务逻辑编码。Velocity将java代码从web页面中分离出来,这样为web站点的长期维护提 供了便利,同时也为我们在JSP和PHP之外又提供了一种可选的方案。Velocity脚本语法摘要1. 变量(1)变量的定义:#set($name = “hello”) 说明:velocity中变量是弱类型的。当使用#set 指令时,括在双引号中的字面字符串将解析和重新解释,如下所示:#set($directoryRoot = “www” )#set($templateName = “index.vm” )#set($template = “$directoryRoot/$templateName” )$template输出将会是:www/index.vm注:在velocity中使用$2.5这样的货币标识是没有问题得的,因为velocity中的变量总是以一个大写或者小写的字母开始的。(2)变量规范的写法${name} ,也可以写成:$name。提倡用前面的写法。例如:你希望通过一个变量$vice来动态的组织一个字符串。 Jack is a $vicemaniac.本来变量是$vice现在却变成了$vicemaniac,这样Veloctiy就不知道您到底要什么了。所以,应该使用规范的格式书写 : Jack is a ${vice}maniac现在Velocity知道变量是$vice而不是$vicemaniac。注意:当引用属性的时候不能加{}(3)变量的赋值: $name=“hello"赋值的左边必须是一个变量或者是属性引用。右边可以是下面六种类型之一: 变量引用,字面字符串,属性引用,方法引用,字面数字,数组列表。下面的例子演示了上述的每种类型:#set( $monkey = $bill ) ## variable reference#set( $monkey.Friend = “monica” ) ## string#set( $monkey.Blame = $whitehouse.Leak ) ## property reference#set( $monkey.Plan = $spindoctor.weave($web) ) ## method reference#set( $monkey.Number = 123 ) ##number#set( $monkey.Say = [“Not”, $my, “fault”] ) ## ArrayList注意:①如果上述例子中的右值是null, 则左值不会被赋值,也就是说会保留以前的值。②velocity模板中未被定义的变量将被认为是一个字符串。例如:#set($foo = “gibbous”)$moon = $foo输出结果为:$moon = gibbous③velocity模板中不会将reference解释为对象的实例变量。例如:$foo.Name将被解释为Foo对象的getName()方法,而不是Foo对象的Name实例变量。例如:$foo.getBar() 等同于$foo.Bar ;$data.getUser(“jon”) 等同于$data.User(“jon”) ;data.getRequest().getServerName() 等同于$data.Request.ServerName等同于${data.Request.ServerName}2. 循环#foreach ($element in $list) This is $element. $velocityCount#end例子:#set( $list = [“pine”, “oak”, “maple”])#foreach ($element in $list)$velocityCountThis is $element.#end输出的结果为:1 This is pine.2 This is oak.3 This is maple.每次循环$list中的一个值都会赋给$element变量。$list可以是一个Vector、Hashtable或者Array。分配给$element的值是一个java对象,并且可以通过变量被引用。例如:如果$element t是一个java的Product类,并且这个产品的名字可以通过调用他的getName()方法得到。#foreach ( $key in $list.keySet())Key: $key -> Value: $list.get($key) <br>#end提示:velocity中大小写敏感。Velocity还特别提供了得到循环次数的方法,$velocityCount变量的名字是Velocity默认的名字。 例子:First example:#foreach ( $foo in [1..5] ) $foo #endSecond example:#foreach ( $bar in [2..-2] ) $bar #endThird example:#set ( $arr = [0..1] ) #foreach ( $i in $arr ) $i #end上面三个例子的输出结果为: First example: 1 2 3 4 5Second example: 2 1 0 -1 -2Third example: 0 13. 条件语句#if (condition)#elseif (condition)#else #end4. 语句的嵌套#foreach ($element in $list) ## inner foreach 内循环#foreach ($element in $list)This is $element. $velocityCount <br>inner<br>#end ## inner foreach 内循环结束 ## outer foreachThis is $element.$velocityCount <br>outer<br>#end语句中也可以嵌套其他的语句,如#if…#else…#end等。5. 注释(1)单行注释: ## This is a single line comment.(2)多行注释:# Thus begins a multi-line comment. Online visitors won’t see this text because the Velocity Templating Engine will ignore it. #(3)文档格式:#** This is a VTL comment block and may be used to store such information as the document author and versioninginformation:@version 1.1@author xiao*#6. 关系和逻辑操作符Velocity 也具有逻辑AND, OR 和 NOT 操作符。如## example for AND#if($foo && $bar)<strong>This and that</strong>#end例子中#if() 指令仅在$foo 和$bar 都为真的时候才为真。如果$foo 为假,则表达式也为假;并且 $bar 将不被求值。如果 $foo 为真,Velocity 模板引擎将继续检查$bar的值,如果 $bar 为真,则整个表达式为真。并且输出This AND that 。如果 $bar 为假,将没有输出因为整个表达式为假。7.Velocity 中的宏Velocity中的宏我们可以理解为函数。①宏的定义#macro(宏的名称 $参数1 $参数2 …) 语句体(即函数体)#end②宏的调用#宏的名称($参数1 $参数2 …) 说明:参数之间用空格隔开。8.#stop 停止执行模板引擎并返回,把它应用于debug是很有帮助的。9.#include与#parse#include和#parse的作用都是引入本地文件, 为了安全的原因,被引入的本地文件只能在TEMPLATE_ROOT目录下。区别:(1) 与#include不同的是,#parse只能指定单个对象。而#include可以有多个如果您需要引入多个文件,可以用逗号分隔就行:#include (“one.gif”, “two.txt”, “three.htm” )在括号内可以是文件名,但是更多的时候是使用变量的:#include ( “greetings.txt”, $seasonalstock )(2) #include被引入文件的内容将不会通过模板引擎解析; 而#parse引入的文件内容Velocity将解析其中的velocity语法并移交给模板,意思就是说相当与把引入的文件copy到文件中。#parse是可以递归调用的,例如:如果dofoo.vm包含如下行:Count down.<br>#set ($count = 8)#parse (“parsefoo.vm”)<br>All done with dofoo.vm!那么在parsefoo.vm模板中,你可以包含如下VTL:$count#set($count = $count - 1)#if ( $count > 0 )<br>#parse( “parsefoo.vm” )#else<br>All done with parsefoo.vm!#end的显示结果为:Count down.876543210All done with parsefoo.vm!All done with dofoo.vm!注意:在vm中使用#parse来嵌套另外一个vm时的变量共享问题。如:->a.vm 里嵌套 b.vm;->a.vm 里定义了变量 $param;->b.vm 里可以直接使用$param,无任何限制。但需要特别注意的是,如果b.vm里同时定义有变量$param,则b.vm里将使用b.vm里定义的值。10.转义字符’‘的使用如果reference被定义,两个’\’意味着输出一个’\’,如果未被定义,刚按原样输出。如:#set($email = “foo” )$email$email\$email\$email输出:foo$email\foo$email如果$email 未定义$email$email\$email\$email输出:$email$email\$email\$email11.内置对象Velocity内置了一些对象,在vm模版里可以直接调用,列举如下:$request、$response、$session,另外,模板内还可以使用 $msg内的消息工具访问 Struts 的国际化资源,达到简便实现国际化的方法。12. 数组访问对数组的访问在Velocity中存在问题,因为Velocity只能访问对象的方法,而数组又是一个特殊的Array,所以虽然数组可以进行循环列举,但却不能定位访问特定位置的元素,如 strs[2],数组对固定位置元素的访问调用了Array的反射方法get(Object array, int index),而Velocity没能提供这样的访问,所以数组要么改成List等其他类容器的方式来包装,要么就通过公用Util类的方式来提供,传入数组对象和要访问的位置参数,从而达到返回所需值的目的。结束语Velocity 可以被应用在各种各样的情景下,本文介绍的只是它的一种用途而已,它还可以被用来做 MVC 结构中的view 层,或者动态内容静态化等。另外,Velocity 并不是唯一的模板框架,同样很优秀的 Freemarker、Thymeleaf 也获得了非常广泛的应用,有兴趣的读者可以去深入研究更多的功能和用途。附录及参考文献使用 Velocity 模板引擎快速生成代码写在最后欢迎关注、喜欢、和点赞后续将推出更多的工具集教程,敬请期待。欢迎关注我的微信公众号获取更多更全的学习资源,视频资料,技术干货!公众号回复“学习”,拉你进程序员技术讨论群,干货资源第一时间分享。公众号回复“视频”,领取800GJava视频学习资源。公众号回复“全栈”,领取1T前端,Java,产品经理,微信小程序,Python等资源合集大放送。公众号回复“慕课”,领取1T慕课实战学习资源。公众号回复“实战”,领取750G项目实战学习资源。公众号回复“面试”,领取8G面试实战学习资源。 ...

April 14, 2019 · 2 min · jiezi

工具集核心教程 | 第五篇: 利用Velocity模板引擎生成模板代码

前言不知道大家有没有这样的感觉,在平时开发中,经常有很多dao、service类中存着很多重复的代码,Velocity提供了模板生成工具,今天我教大家怎么和这些大量的重复代码说再见。参考项目:https://github.com/bigbeef/cppba-codeTemplate个人博客:http://www.zhangbox.cn注意大家可以写适合自己的模板,这里为了演示,就直接拿cppba-web的模板来示范,至于velocity的语法大家可以查看这篇文章:工具集核心教程 | 第四篇: Velocity模板引擎入门到大神maven配置 <!– velocity –><dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> <version>1.7</version></dependency>创建模板文件首先看下目录结构:这里演示我就只贴出ServiceImplTemplate.java,需要其他模板代码可以到我github里面下载#set ($domain = $!domainName.substring(0,1).toLowerCase()+$!domainName.substring(1))package $!{packageName}.service.impl;import $!{packageName}.core.bean.PageEntity;import $!{packageName}.dao.$!{domainName}Dao;import $!{packageName}.dto.$!{domainName}Dto;import $!{packageName}.dto.BaseDto;import $!{packageName}.entity.$!{domainName};import $!{packageName}.service.$!{domainName}Service;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Objects;/** * 开发者 * nickName:星缘 * email:1342541819@qq.com * github:https://github.com/bigbeef * velocity模板生成 cppba-codeTemplate /@Service@Transactionalpublic class $!{domainName}ServiceImpl implements $!{domainName}Service{ @Resource private $!{domainName}Dao $!{domain}Dao; @Override public void save($!{domainName} $!{domain}) { $!{domain}Dao.save($!{domain}); } @Override public void delete($!{domainName} $!{domain}) { $!{domain}Dao.delete($!{domain}); } @Override public void update($!{domainName} $!{domain}) { $!{domain}Dao.update($!{domain}); } @Override public $!{domainName} findById(int id) { return ($!{domainName}) $!{domain}Dao.get($!{domainName}.class, id); } @Override public PageEntity<$!{domainName}> query(BaseDto baseDto) { String hql = " select distinct $!{domain} from $!{domainName} $!{domain} where 1=1 “; Map params = new HashMap<String,Object>(); $!{domainName}Dto $!{domain}Dto = ($!{domainName}Dto)baseDto; $!{domainName} $!{domain} = $!{domain}Dto.get$!{domainName}(); int page = $!{domain}Dto.getPage(); int pageSize = $!{domain}Dto.getPageSize(); List list = $!{domain}Dao.query(hql,params,page,pageSize); long count = $!{domain}Dao.count(hql,params); PageEntity<$!{domainName}> pe = new PageEntity<$!{domainName}>(); pe.setCount(count); pe.setList(list); return pe; }}模板生成接下来是生成模板的主函数:package com.cppba.core;import org.apache.velocity.Template;import org.apache.velocity.VelocityContext;import org.apache.velocity.app.Velocity;import org.apache.velocity.app.VelocityEngine;import java.io.BufferedWriter;import java.io.File;import java.io.FileOutputStream;import java.io.OutputStreamWriter;import java.util.HashMap;import java.util.Map;import java.util.Properties;/* * 开发者 * nickName:星缘 * email:1342541819@qq.com * github:https://github.com/bigbeef /public class Main { static String domainName = “Articles”; //类名 static String packageName = “com.cppba”;//类包 static String templateDir = “\src\main\webapp\template\”; static String sourcePath = System.getProperty(“user.dir”)+templateDir; static String resultDir = “\out”; static String targetPath = System.getProperty(“user.dir”) + resultDir + “\” + packageName.replace(”.", “\”); public static void main(String []args) throws Exception{ Map<String,Object> map = new HashMap(); map.put(“DaoTemplate.java”,“dao/” + domainName + “Dao.java”); map.put(“ServiceTemplate.java”,“service/” + domainName + “Service.java”); map.put(“ServiceImplTemplate.java”,“service/impl/” + domainName + “ServiceImpl.java”); map.put(“DtoTemplate.java”,“dto/” + domainName + “Dto.java”); for(String templateFile:map.keySet()){ String targetFile = (String) map.get(templateFile); Properties pro = new Properties(); pro.setProperty(Velocity.OUTPUT_ENCODING, “UTF-8”); pro.setProperty(Velocity.INPUT_ENCODING, “UTF-8”); pro.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, sourcePath); VelocityEngine ve = new VelocityEngine(pro); VelocityContext context = new VelocityContext(); context.put(“domainName”,domainName); context.put(“packageName”,packageName); Template t = ve.getTemplate(templateFile, “UTF-8”); File file = new File(targetPath, targetFile); if (!file.getParentFile().exists()) file.getParentFile().mkdirs(); if (!file.exists()) file.createNewFile(); FileOutputStream outStream = new FileOutputStream(file); OutputStreamWriter writer = new OutputStreamWriter(outStream, “UTF-8”); BufferedWriter sw = new BufferedWriter(writer); t.merge(context, sw); sw.flush(); sw.close(); outStream.close(); System.out.println(“成功生成Java文件:” + (targetPath + targetFile).replaceAll("/", “\\”)); } }}生成java文件我们可以修改domainName和packageName来修改我们的包名和类名,我们运行下看:我们看到生成成功,我们打开ArticlesServiceImpl.java看下:package com.cppba.service.impl;import com.cppba.core.bean.PageEntity;import com.cppba.dao.ArticlesDao;import com.cppba.dto.ArticlesDto;import com.cppba.dto.BaseDto;import com.cppba.entity.Articles;import com.cppba.service.ArticlesService;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Objects;/* * 开发者 * nickName:星缘 * email:1342541819@qq.com * github:https://github.com/bigbeef * velocity模板生成 cppba-codeTemplate */@Service@Transactionalpublic class ArticlesServiceImpl implements ArticlesService{ @Resource private ArticlesDao articlesDao; @Override public void save(Articles articles) { articlesDao.save(articles); } @Override public void delete(Articles articles) { articlesDao.delete(articles); } @Override public void update(Articles articles) { articlesDao.update(articles); } @Override public Articles findById(int id) { return (Articles) articlesDao.get(Articles.class, id); } @Override public PageEntity<Articles> query(BaseDto baseDto) { String hql = " select distinct articles from Articles articles where 1=1 “; Map params = new HashMap<String,Object>(); ArticlesDto articlesDto = (ArticlesDto)baseDto; Articles articles = articlesDto.getArticles(); int page = articlesDto.getPage(); int pageSize = articlesDto.getPageSize(); List list = articlesDao.query(hql,params,page,pageSize); long count = articlesDao.count(hql,params); PageEntity<Articles> pe = new PageEntity<Articles>(); pe.setCount(count); pe.setList(list); return pe; }}生成成功,我们拷贝到cppba-web中可完美运行!写在最后欢迎关注、喜欢、和点赞后续将推出更多的工具集教程,敬请期待。欢迎关注我的微信公众号获取更多更全的学习资源,视频资料,技术干货!公众号回复“学习”,拉你进程序员技术讨论群,干货资源第一时间分享。公众号回复“视频”,领取800GJava视频学习资源。公众号回复“全栈”,领取1T前端,Java,产品经理,微信小程序,Python等资源合集大放送。公众号回复“慕课”,领取1T慕课实战学习资源。公众号回复“实战”,领取750G项目实战学习资源。公众号回复“面试”,领取8G面试实战学习资源。 ...

April 14, 2019 · 3 min · jiezi

工具集核心教程 | 第一篇: .md即markdown文件的基本常用编写语法(图文并茂)

序言:感觉只要是不写博客,人就很变得很懒,学的知识点感觉还是记不住,渐渐地让我明白,看的越多,懂的越少(你这话不是有毛病吗?应该是看的越多,懂的越多才对),此话怎讲,当你在茫茫的知识库里面东看看,西看看的时候,很快就被海量的知识给淹没了,根本就不知道哪些是对的,哪些是错的,感觉好像这个也懂了,那个也懂了,但是真正写起来,脑子又一片空白,又好像什么都不懂,这种状态时有发生,这就叫不懂装懂,最根本的原因就是看的太多,写的太少,所以为了改掉这样毛病,把被动学习变成主动学习,接下来的日子,多写写,即使是写一些学习工作中遇到的坑也是好的,没事翻出来看看,还可以加深印象,好了,废话到处!起因:因为现在前后端、测试交互很频繁,一个完整的项目或者教程,说明性文件必不可少!那就难免要写一些readme等等的说明性文件,但是这样的文件一般都是.md的文件,编写的语法自然跟其他格式的文件有所区别,置于为什么要用这种格式的文件,不要问我,我也不知道,大家都这么用,跟着用就对了,如果有大神知道的,不妨告知小弟,本文也是我学习写markdown文件的一个笔记吧,仅供参考!正文:1.标题的几种写法:第一种:前面带#号,后面带文字,分别表示h1-h6,上图可以看出,只到h6,而且h1下面会有一条横线,注意,#号后面有空格第二种:这种方式好像只能表示一级和二级标题,而且=和-的数量没有限制,只要大于一个就行第三种:这里的标题支持h1-h6,为了减少篇幅,我就偷个懒,只写前面二个,这个比较好理解,相当于标签闭合,注意,标题与#号要有空格那既然3种都可以使用,可不可以混合使用呢?我试了一下,是可以的,但是为了让页面标签的统一性,不建议混合使用,推荐使用第一种,比较简洁,全面为了搞清楚原理,我特意在网上搜一下在线编写markdown的工具,发现实际上是把这些标签最后转化为html标签,如图:在线地址请看这里: markdown在线编辑 (只是想看看背后的转换原理,没有广告之嫌)2.列表我们都知道,列表分为有序列表和无序列表,下面直接展示2种列表的写法:可以看到,无序列表可以用* , + , — 来创建,用在线编辑器看,实际上是转换成了ul>li ,所以使用哪个都可以,推荐使用吧有序列表就相对简单一点,只有这一种方式,注意,数字后面的点只能是英文的点,特别注意,有序列表的序号是根据第一行列表的数字顺序来的,比如说:第一组本来是3 2 1 倒序,但是现实3 4 5 ,后面一组 序号是乱的, 但是还是显示 3 4 5 ,这点必须注意了3.区块引用比如说,你想对某个部分做的内容做一些说明或者引用某某的话等,可以用这个语句无序列表下方的便是引用,可以有多种用途,看你的需求了,用法就是在语句前面加一个 > ,注意是英文的那个右尖括号,注意空格引用因为是一个区块,理论上是应该什么内容都可以放,比如说:标题,列表,引用等等,看看下图:将上面的代码稍微改一下,全部加上引用标签,就变成了一个大的引用,还有引用里面还有引用,那引用嵌套引用还没有别的写法呢?上图可以看出,想要在上一次引用中嵌套一层引用,只需多加一个>,理论上可以无限嵌套,我就不整那么多了,注意:多层嵌套的>是不需要连续在一起的,只要在一行就可以了,中间允许有空格,但是为了好看,还是把排版搞好吧4.华丽的分割线分割线可以由 - _(星号,减号,底线)这3个符号的至少3个符号表示,注意至少要3个,且不需要连续,有空格也可以应该看得懂吧,但是为了代码的排版好看,你们自己定规则吧,前面有用到星号,建议用减号5.链接支持2种链接方式:行内式和参数式,不管是哪一种,链接文字都是用 [方括号] 来标记。上图可知,行内式的链接格式是:链接的文字放在[]中,链接地址放在随后的()中,举一反三,经常出现的列表链接就应该这样写:链接还可以带title属性,好像也只能带title,带不了其他属性,注意,是链接地址后面空一格,然后用引号引起来这是行内式的写法,参数式的怎么写:这就好理解了,就是把链接当成参数,适合多出使用相同链接的场景,注意参数的对应关系,参数定义时,这3种写法都可以:还支持这种写法,如果你不想混淆的话:其实还有一种隐式链接的写法,但是我觉得那种写法不直观,所以就不写了,经常用的一般就上面2种,如果你想了解隐式链接,可以看我文章最后放出的参考地址6.图片图片也有2种方式:行内式和参数式,用法跟链接的基本一样,唯一的不同就是,图片前面要写一个!(这是必须的),没什么好说的7.代码框这个就比较重要了,很多时候都需要展示出一些代码如果代码量比较少,只有单行的话,可以用单反引号包起来,如下:要是多行这个就不行了,多行可以用这个,也就是一对`:多行用三个反引号,如果要写注释,可以在反引号后面写8.表格这个写的有点麻烦,注意看从这3种不同写法看,表格的格式不一定要对的非常起,但是为了好看,对齐肯定是最好的,第一种的分割线后面的冒号表示对齐方式,写在左边表示左对齐,右边为右对齐,两边都写表示居中,还是有点意思的,不过现实出来的结果是,表格外面并没有线框包起来,不知道别人的怎么弄的9.强调一个星号或者是一个下划线包起来,会转换为<em>倾斜,如果是2个,会转换为<strong>加粗10.转义就不一一列举了,基本上跟js转义是一样的11.删除线常用的基本上就这些了,如果还有一些常用的,可以跟我留言,我补充上去,我觉得图文并茂才是高效学习的正确姿势,但愿为你的学习带来帮助!最后推荐一款Window下markdown编辑器typora非常好用的一款本地MD文件编辑器 ,中文版下载地址:typora下载界面如下:参考文献:http://www.appinn.com/markdown/http://sspai.com/25137写在最后欢迎关注、喜欢、和点赞后续将推出更多的工具集教程,敬请期待。欢迎关注我的微信公众号获取更多更全的学习资源,视频资料,技术干货!公众号回复“学习”,拉你进程序员技术讨论群,干货资源第一时间分享。公众号回复“视频”,领取800GJava视频学习资源。公众号回复“全栈”,领取1T前端,Java,产品经理,微信小程序,Python等资源合集大放送。公众号回复“慕课”,领取1T慕课实战学习资源。公众号回复“实战”,领取750G项目实战学习资源。公众号回复“面试”,领取8G面试实战学习资源。

April 14, 2019 · 1 min · jiezi

工具集核心教程 | 第二篇: IDEA入门到进阶(图文并茂)

前言:IntelliJ IDEA如果说IntelliJ IDEA是一款现代化智能开发工具的话,Eclipse则称得上是石器时代的东西了。其实笔者也是一枚从Eclipse转IDEA的探索者,随着近期的不断开发实践和调试,逐步体会到这款智能IDE带来的巨大开发便利,在强大的插件功能支持下,诸如对Git和Maven的支持简直让人停不下来,各种代码提示,包括JS更是手到擒来,最终不得不被这款神奇的IDE所折服。为了让身边更多的小伙伴参与进来,决定写下这篇文章,与君共享。(^_^)高级传送门:IntelliJ IDEA 官网下载 - Ultimate 终极版激活方法: 安装完成后 选择 License 输入 http://intellij.mandroid.cn正文:IntelliJ IDEA 使用教程1. IDEA VS Eclipse 核心术语比较由下图可见:两者最大的转变就在于工作空间概念的转变,并且在IDEA当中,Project和 Module是作为两个不同的概念,对项目结构是重要意义的,这也恰恰是许多IDEA初学者觉得困扰的地方。 1.1 为什么要取消工作空间?答:简单来说,IDEA不需要设置工作空间,因为每一个Project都具备一个工作空间!!对于每一个IDEA的项目工程(Project)而言,它的每一个子模块(Module)都可以使用独立的JDK和MAVEN。这对于传统项目迈向新项目的重构添加了极大的便利性,这种多元化的灵活性正是Eclipse所缺失的,因为开始Eclipse在初次使用时已经绑死了工作空间。 1.2 此外,很多新手都会问,为什么IDEA里面的子工程要称为Module ?答:其实就是模块化的概念,作为聚合工程亦或普通的根目录,它称之为Project,而下面的子工程称为模块,每一个子模块之间可以相关联,也可以没有任何关联。2. 当前项目配置VS 默认配置 2.1 为什么有了当前项目配置,还需要默认配置呢?答:因为IDEA没有工作空间的概念,所以每个新项目(Project)都需要设置自己的JDK和MAVEN等相关配置,这样虽然提高了灵活性,但是却要为每个新项目都要重新配置,这显然不符合我们的预期。在这个背景下,默认配置给予当前项目配置提供了Default选项,问题自然就迎刃而解了。 2.2 初始化步骤 打开默认配置:顶部导航栏 -> File -> Other Settings -> Default Settings /ProjectStructs 打开当前配置:顶部导航栏 -> File -> Settings / ProjectStructs示例图:=============================================接下来,来看看IDEA如何快速搭建Java开发环境!!=============================================3. 全局JDK(默认配置) 具体步骤:顶部工具栏 File ->Other Settins -> Default Project Structure -> SDKs -> JDK 示例: 根据下图步骤设置JDK目录,最后点击OK保存。4. 全局Maven(默认配置) 具体步骤:顶部工具栏 File ->Other Settings -> Default Settings -> Build & Tools -> Maven 示例: 理论上只要配置了Maven主目录即可,实际开发推荐采用User Settins file .5. 版本控制Git/Svn (默认配置) 具体步骤:顶部工具栏 File ->Other Settings -> Default Settings -> Version Control -> Git 示例: IDEA默认集成了对Git/Svn的支持 直接设置执行程序,右边Test提示成功即可。 部分小伙伴反馈说无法找到svn.exe,解决方法:重装SVN,配置项重新选择command line client tools 即可。6. 自动导包和智能移除 (默认配置) 具体步骤:顶部工具栏 File ->Other Settings -> Default Settings -> Auto Import 说明: 在网上看到很多人在提问IDEA为什么不能优化导包而Eclipse可以, 所以特意抽出来跟大家分享IDEA如何优化导包。 7. Tomcat Server(当前项目配置) 很多小伙伴刚开始都找不到Tomcat的配置,其实很简单,Tomcat或者Jetty这些都是部署的容器,自然会联想到Deployment ,打开部署配置,可以看到应用服务器的配置。 配置Tomcat方法: File -> Settings -> Deployment -> Application Servers -> Tomcat Server 具体配置方法,如下图:IDEA 必备小技能 为了提升开发效率,撸主贴心为大家准备以下实用指数五颗星的小技巧:8. 自动编译 具体步骤:顶部工具栏 File ->Other Settings -> Default Settings -> Auto Import 说明:开启自动编译之后,结合Ctrl+Shift+F9 会有热更新效果。自动编译(Runtime) 具体步骤: 敲击 Ctrl + Shift + Alt + / 然后双击Shift搜索进入Registry 找到compiler.automake.allow.when.app.running ,然后勾选上。9. 取消大小写敏感 具体步骤:File | Settings | Editor | General | Code Completion Case | Sensitive Completion = None 取消大小敏感,在编写代码的时候,代码的自动提示将更加全面和丰富。11. 调整字体类型和字体大小默认的白色背景和细小的字体会影响大家的编码体验,这里特意提供了调整代码窗的快捷配置。打开配置,搜索Font,然后再Font可以调整字体类型,Size可以调整字体大小,如图:12. 将快捷键设置为跟Eclipse一样很多人可能并不习惯IDEA的快捷键,为了方便,这里我们将快捷键设置为跟 Eclipse一样。 具体步骤: File -> Settings -> Keymap - > 选择Eclipse .13. 打开常用工具栏 具体步骤:顶部导航栏 - View -> 勾选 Toolbar & Tool Buttons如下图所示:14. 打开Maven神器(强烈推荐!) 具体步骤:右侧直接点击 Maven Project 管理插件 ,记得先打开常用工具栏,详见8.3。 如下图所示: 还在Eclipse使用Update命令苦苦挣扎的童鞋,请火速尝试此款插件,能给你带来前所未有的愉快感!!15. 懒人必备快捷键1. 按【鼠标中键】快速打开智能提示,取代alt+enter 。 File->Settings-> Keymap-> 搜索 Show Intention Actions -> 添加快捷键为鼠标中键。2. 按【F2】快速修改文件名,告别双手操作。 File->Settings-> Keymap-> 搜索 Rename -> 将快捷键设置为F2 。3. 按【F3】直接打开文件所在目录,浏览一步到位。 File->Settings-> Keymap-> 搜索 Show In Explorer -> 将快捷键设置为F3 。4. 按【Ctrl+右键】直接打开实现类,方便开发查询。 File->Settings-> Keymap-> 搜索 implementation-> Add Mouse Shortcut 将快捷键设置为Ctrl+ 鼠标右键。16. 重度强迫症患者1.取消大小写敏感,让自动完成更齐全! File | Settings | Editor | General | Code Completion Case | Sensitive Completion = None。2.自动隐藏注释,让源码阅读更为清爽! File -> Settings -> Editor -> General -> Code Folding -> Documentation comments 勾选。如何想快速一键打开全部注释,则单击鼠标右键,选择Folding -> Expand Doc comments 。3. Maven自动下载源码包,告别反编译,直接上源码注释!! File | Settings | Build, Execution, Deployment | Build Tools | Maven | Importing 将Automatically Download 的 Source 勾上。17. IDEA十问十答 (1)如何打开本地工程/已存在的工程?答:点击File -> Open 打开 工程文件夹即可,注意先配置好JDK、Maven等基础配置。(2)IDEA如何删除项目工程?答:问这个问题的Coder真的好可爱啊哈哈,很肯定的回答你,不需要删,点击File-> Close Project 即可快速关闭当前项目; 示例:什么?你还是想要干掉整个目录?那也阔以,右键Show In Explorer ,删掉文件夹即可。不过笔者建议还是直接Close关掉就好啦,万一以后用得上呢,你说呢?(3)如何在单个窗口打开多个Maven工程啊?答:随便新建一个文件夹,然后将工程都扔进去,使用IDEA打开这个文件夹。(4)如何为当前项目工程添加多个模块啊?答:对着工程右键 -> 选择New -> Module -> 通常选择Spring Initializr ,如图:写在最后欢迎关注、喜欢、和点赞后续将推出更多的工具集教程,敬请期待。欢迎关注我的微信公众号获取更多更全的学习资源,视频资料,技术干货!公众号回复“学习”,拉你进程序员技术讨论群,干货资源第一时间分享。公众号回复“视频”,领取800GJava视频学习资源。公众号回复“全栈”,领取1T前端,Java,产品经理,微信小程序,Python等资源合集大放送。公众号回复“慕课”,领取1T慕课实战学习资源。公众号回复“实战”,领取750G项目实战学习资源。公众号回复“面试”,领取8G面试实战学习资源。 ...

April 14, 2019 · 2 min · jiezi

MySQL5.7应当注意的参数

简介: 本篇文章主要介绍 MySQL 初始化应当注意的参数,对于不同环境间实例迁移,这些参数同样应当注意。注: 本文介绍的参数都是在配置文件 [mysqld] 部分。server_id 和 log_bin ,binlog_format这几个系统变量通常成对出现,当我们想指定log_bin 选项时,必须也要指定server_id 。log_bin 是全局变量 不可动态修改 默认为OFF。当我们需要开启binlog时,可将该参数设为binlog名字或绝对路径加名字。binlog_format 指定binlog格式 5.7.7版本以上默认是ROW模式建议设置:#server_id 各个实例建议设置不同 log_bin不指定路径时默认在数据文件目录server_id = 213306log_bin = mysqlbinbinlog_format = row或者server_id = 213306log_bin = /data/mysql/logs/mysqlbinbinlog_format = rowsql_mode该参数控制 MySQL server 在不同的SQL模式下运行,对于客户端发送的请求不同的模式会有不同的应答。sql_mode 参数分为全局和会话级别 可以动态修改#sql_mode 默认为:sql_mode = ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION#关于修改和各个模式的作用 可参考官方文档: https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html该参数建议去掉ONLY_FULL_GROUP_BY,具体采用严格或非严格模式可以根据需求来修改。注意该参数在不同实例要保持一致,不然可能会出现一条sql在此环境下可以执行 在另外一个环境不能执行的情况。max_connections该参数指定 MySQL 的最大连接数,是全局变量 可动态修改 默认为151。建议设置大些 防止出现连接数用满的错误。character_set_server该参数指定 MySQL server端字符集,分为全局和会话级别 可以动态修改 5.7版本默认值为latin1。建议设置该参数为utf8或utf8mb4,不同实例间保持一致 特别是主从实例。lower_case_table_names该参数控制 MySQL 是否大小写敏感,主要影响库名及表名。Linux下该参数默认为0 即对大小写敏感,是全局变量 不可动态修改,建议设置为1。transaction_isolation该参数指定 MySQL server 采用哪种事务隔离级别,默认是REPEATABLE-READ 可动态修改。采用哪种隔离级别要根据应用要求来选择,一般可改为READ-COMMITTED,不同实例间建议保持一致。innodb_buffer_pool_size该参数控制InnoDB缓冲池大小,默认值为134217728字节(128MB)5.7.5版本以上可动态修改。缓冲池是缓存数据和索引的地方,尽可能大的缓存池将确保使用内存而不是磁盘来进行大多数读取操作。典型值为5-6GB(8GB RAM),20-25GB(32GB RAM),100-120GB(128GB RAM),在一个独立使用的数据库服务器上,你可以设置这个变量到服务器物理内存大小的80%。innodb_log_file_size该参数定义redo日志组中每个日志文件的大小(以字节为单位),是全局变量 不可动态修改 默认为48M。当MySQL server 读写比较频繁时,建议增大该参数 可与 innodb_log_files_in_group 参数配合使用。innodb_io_capacity 和 innodb_io_capacity_maxinnodb_io_capacity参数设置InnoDB后台任务每秒执行的IO操作数的上限,默认值为200 可动态修改。此参数应设置为系统每秒大约可执行的IO操作数 即系统的IOPS。该值取决于你的系统配置。当MySQL server 写操作特多 刷新脏页落后时 , innodb_io_capacity_max 参数是后台任务定义每秒执行的IO操作数的上限,innodb_io_capacity_max通常设置为innodb_io_capacity的2倍。如果MySQL服务器是SSD高速磁盘,我们可以设置 innodb_io_capacity_max= 6000 和 innodb_io_capacity = 3000 (最大值的50%)。当然 运行sysbench或任何其他基准测试工具来对磁盘吞吐量进行基准测试是个好主意。其他相关参数除了上面列举的参数 还有些其他参数需要注意 ,篇幅关系 我将其汇总如下:#禁用所有DNS解析 建议开启 唯一的限制是GRANT语句必须仅使用IP地址skip_name_resolve = 1#MySQL server关闭空闲连接等待的秒数 默认为28800interactive_timeout = ? wait_timeout = ?#日志记录时间与系统保持一致log_timestamps = SYSTEM#一些日志相关参数log_error = error.logslow_query_log = 1slow_query_log_file = slow.loglong_query_time = 3#binlog日志删除策略 单位为天 默认为0 及不自动清理expire_logs_days = 30 #允许master创建function并同步到slave,有潜在的数据安全问题log_bin_trust_function_creators = 1#导出文件安全目录 默认为空secure_file_priv = /tmp总结:本篇文章介绍了部分MySQL初始化应当注意的参数,给出了相关参数的默认值及是否可动态修改。对于不可动态修改的参数 建议启动前设置合理,这样可以减少后面维护重启次数。在大家修改参数之前 请记住以下几点:一次更改一个设置!这是估计变更是否有益的唯一方法。不允许在配置文件中进行重复设置。如果要跟踪更改,请使用版本控制。更改前应该在测试环境演练。确保参数位置正确,单位合理,不和其他参数冲突。不要做天真的数学运算,比如“我的新服务器有2x内存,我只需要将所有值设置为以前的2倍”。 ...

April 13, 2019 · 1 min · jiezi

透过 Crontab 排程备份 Mariadb (Mysql)使用 php

本教学使用环境介绍伺服器端:Ubuntu 18.04 LTS资料库:Mariadb 10.1.34(Mysql)语言版本:php 7.3本机端:MacOS High Sierra本教学将使用纯 php 去备份资料库并下载到目录底下$ crontab -e设定每天凌晨00:00 执行0 0 * * * php /var/www/backup.phpbackup.php 脚本记得开头一定要 「<?php」,即便你有启用缩写header(‘Content-Type: text/html; charset=utf-8’);function backup_mysql_database($options){$mtables = array(); $contents = “– Database: ".$options['db_to_backup']." –\n”;$mysqli = new mysqli($options[‘db_host’], $options[‘db_uname’], $options[‘db_password’], $options[‘db_to_backup’]);if ($mysqli->connect_error) { die(‘Error : (’. $mysqli->connect_errno .’) ‘. $mysqli->connect_error);}$mysqli->query(“SET NAMES utf8”);$mysqli->set_charset(“utf8mb4”);$results = $mysqli->query(“SHOW TABLES”);while ($row = $results->fetch_array()){ if (!in_array($row[0], $options[‘db_exclude_tables’])){ $mtables[] = $row[0]; }}foreach($mtables as $table){ $contents .= “– Table ".$table." –\n”; $results = $mysqli->query(“SHOW CREATE TABLE “.$table); while ($row = $results->fetch_array()){ $contents .= $row[1].”;\n\n”; } $results = $mysqli->query(“SELECT * FROM “.$table); $row_count = $results->num_rows; $fields = $results->fetch_fields(); $fields_count = count($fields); $insert_head = “INSERT INTO ".$table." (”; for($i=0; $i < $fields_count; $i++){ $insert_head .= “".$fields[$i]-&gt;name."”; if($i < $fields_count-1){ $insert_head .= ‘, ‘; } } $insert_head .= “)”; $insert_head .= " VALUES\n”; if($row_count>0){ $r = 0; while ($row = $results->fetch_array()){ if(($r % 400) == 0){ $contents .= $insert_head; } $contents .= “(”; for($i=0; $i < $fields_count; $i++){ $row_content = str_replace("\n”,"\n",$mysqli->real_escape_string($row[$i])); switch($fields[$i]->type){ case 8: case 3: $contents .= $row_content; break; default: $contents .= “’”. $row_content ."’"; } if($i < $fields_count-1){ $contents .= ‘, ‘; } } if(($r+1) == $row_count || ($r % 400) == 399){ $contents .= “);\n\n”; } else { $contents .= “),\n”; } $r++; } }}if (!is_dir ( $options[‘db_backup_path’] )) { mkdir ( $options[‘db_backup_path’], 0777, true ); }## 备份后的 sql 名称$backup_file_name = “dev-” . date( “Y-m-d H:i:s”).".sql";$fp = fopen($options[‘db_backup_path’] . ‘/’ . $backup_file_name ,‘w+’);if (($result = fwrite($fp, $contents))) { // echo “Backup file created ‘$backup_file_name’ ($result)”;}fclose($fp);return $backup_file_name;}## 资料库设定$options = array( ‘db_host’=> ’localhost’, ‘db_uname’ => ‘root’, // 资料库使用者帐号 ‘db_password’ => ‘password’, // 资料库密码 ‘db_to_backup’ => ‘db’, // 资料库名称 ‘db_backup_path’ => ‘/var/www/’, // 保存到哪个路径 ‘db_exclude_tables’ => array());$backup_file_name=backup_mysql_database($options);这样就可以透过 php 备份到该主机下,直接产生 sql 档了。Line ID:ianmacQQ:1258554508 ...

April 13, 2019 · 2 min · jiezi

Graphql实战系列(下)

前情介绍在《Graphql实战系列(上)》中我们已经完成技术选型,并将graphql桥接到凝胶gels项目中,并动态手写了schema,可以通过 http://localhost:5000/graphql 查看效果。这一节,我们根据数据库表来自动生成基本的查询与更新schema,并能方便的扩展schema,实现我们想来的业务逻辑。设计思路对象定义在apollo-server中是用字符串来做的,而Query与Mutation只能有一个,而我们的定义又会分散在多个文件中,因此只能先以一定的形式把它们存入数组中,在生成schema前一刻再组合。业务逻辑模块模板设计:const customDefs = { textDefs: type ReviseResult { id: Int affectedRows: Int status: Int message: String }, queryDefs: [], mutationDefs: []}const customResolvers = { Query: { }, Mutation: { } }export { customDefs, customResolvers }schema合并算法let typeDefs = [] let dirGraphql = requireDir('../../graphql') //从手写schema业务模块目录读入文件 G.L.each(dirGraphql, (item, name) =&gt; { if (item &amp;&amp; item.customDefs &amp;&amp; item.customResolvers) { typeDefs.push(item.customDefs.textDefs || '') //合并文本对象定义 typeDefObj.query = typeDefObj.query.concat(item.customDefs.queryDefs || []) //合并Query typeDefObj.mutation = typeDefObj.mutation.concat(item.customDefs.mutationDefs || []) //合并Matation let { Query, Mutation, ...Other } = item.customResolvers Object.assign(resolvers.Query, Query) //合并resolvers.Query Object.assign(resolvers.Mutation, Mutation) //合并resolvers.Mutation Object.assign(resolvers, Other) //合并其它resolvers } }) //将query与matation查询更新对象由自定义的数组转化成为文本形式 typeDefs.push(Object.entries(typeDefObj).reduce((total, cur) =&gt; { return total += type ${G.tools.bigCamelCase(cur[0])} { ${cur[1].join(’’)} } }, ''))从数据库表动态生成schema自动生成内容:一个表一个对象;每个表有两个Query,一是单条查询,二是列表查询;三个Mutation,一是新增,二是更新,三是删除;关联表以上篇中的Book与Author为例,Book中有author_id,会生成一个Author对象;而Author表中会生成一个对象列表[Book]mysql类型 =&gt; graphql 类型转化常量定义定义一类型转换,不在定义中的默认为String。const TYPEFROMMYSQLTOGRAPHQL = { int: 'Int', smallint: 'Int', tinyint: 'Int', bigint: 'Int', double: 'Float', float: 'Float', decimal: 'Float',}从数据库中读取数据表信息 let dao = new BaseDao() let tables = await dao.querySql('select TABLE_NAME,TABLE_COMMENT from information_schema.TABLES' + ' where TABLE_SCHEMA = ? and TABLE_TYPE = ? and substr(TABLE_NAME,1,2) &lt;&gt; ? order by ?', [G.CONFIGS.dbconfig.db_name, 'BASE TABLE', 't_', 'TABLE_NAME'])从数据库中读取表字段信息tables.data.forEach((table) =&gt; { columnRs.push(dao.querySql('SELECT COLUMNS.COLUMN_NAME,COLUMNS.COLUMN_TYPE,COLUMNS.IS_NULLABLE,' + 'COLUMNS.CHARACTER_SET_NAME,COLUMNS.COLUMN_DEFAULT,COLUMNS.EXTRA,' + 'COLUMNS.COLUMN_KEY,COLUMNS.COLUMN_COMMENT,STATISTICS.TABLE_NAME,' + 'STATISTICS.INDEX_NAME,STATISTICS.SEQ_IN_INDEX,STATISTICS.NON_UNIQUE,' + 'COLUMNS.COLLATION_NAME ' + 'FROM information_schema.COLUMNS ' + 'LEFT JOIN information_schema.STATISTICS ON ' + 'information_schema.COLUMNS.TABLE_NAME = STATISTICS.TABLE_NAME ' + 'AND information_schema.COLUMNS.COLUMN_NAME = information_schema.STATISTICS.COLUMN_NAME ' + 'AND information_schema.STATISTICS.table_schema = ? ' + 'where information_schema.COLUMNS.TABLE_NAME = ? and COLUMNS.table_schema = ?', [G.CONFIGS.dbconfig.db_name, table.TABLE_NAME, G.CONFIGS.dbconfig.db_name])) })几个工具函数取数据库表字段类型,去除圆括号与长度信息 getStartTillBracket(str: string) { return str.indexOf('(') &gt; -1 ? str.substr(0, str.indexOf('(')) : str }下划线分隔的表字段转化为big camel-case bigCamelCase(str: string) { return str.split('_').map((al) =&gt; { if (al.length &gt; 0) { return al.substr(0, 1).toUpperCase() + al.substr(1).toLowerCase() } return al }).join('') }下划线分隔的表字段转化为small camel-case smallCamelCase(str: string) { let strs = str.split('_') if (strs.length &lt; 2) { return str } else { let tail = strs.slice(1).map((al) =&gt; { if (al.length &gt; 0) { return al.substr(0, 1).toUpperCase() + al.substr(1).toLowerCase() } return al }).join('') return strs[0] + tail } }字段是否以_id结尾,是表关联的标志不以_id结尾,是正常字段,判断是否为null,处理必填typeDefObj[table].unshift(${col[‘COLUMN_NAME’]}: ${typeStr}${col[‘IS_NULLABLE’] === ‘NO’ ? ‘!’ : ‘’}\n)以_id结尾,则需要处理关联关系 //Book表以author_id关联单个Author实体 typeDefObj[table].unshift(“““关联的实体””” ${G.L.trimEnd(col[‘COLUMN_NAME’], ‘_id’)}: ${G.tools.bigCamelCase(G.L.trimEnd(col[‘COLUMN_NAME’], ‘id’))}) resolvers[G.tools.bigCamelCase(table)] = { [G.L.trimEnd(col['COLUMN_NAME'], '_id')]: async (element) =&gt; { let rs = await new BaseDao(G.L.trimEnd(col['COLUMN_NAME'], '_id')).retrieve({ id: element[col['COLUMN_NAME']] }) return rs.data[0] } } //Author表关联Book列表 let fTable = G.L.trimEnd(col['COLUMN_NAME'], '_id') if (!typeDefObj[fTable]) { typeDefObj[fTable] = [] } if (typeDefObj[fTable].length &gt;= 2) typeDefObj[fTable].splice(typeDefObj[fTable].length - 2, 0, “““关联实体集合””"${table}s: [${G.tools.bigCamelCase(table)}]\n) else typeDefObj[fTable].push(${table}s: [${G.tools.bigCamelCase(table)}]\n) resolvers[G.tools.bigCamelCase(fTable)] = { [${table}s]: async (element) =&gt; { let rs = await new BaseDao(table).retrieve({ [col['COLUMN_NAME']]: element.id}) return rs.data } }生成Query查询单条查询 if (paramId.length &gt; 0) { typeDefObj['query'].push(${G.tools.smallCamelCase(table)}(${paramId}!): ${G.tools.bigCamelCase(table)}\n) resolvers.Query[${G.tools.smallCamelCase(table)}] = async (_, { id }) =&gt; { let rs = await new BaseDao(table).retrieve({ id }) return rs.data[0] } } else { G.logger.error(Table [${table}] must have id field.) }列表查询 let complex = table.endsWith('s') ? (table.substr(0, table.length - 1) + 'z') : (table + 's') typeDefObj['query'].push(${G.tools.smallCamelCase(complex)}(${paramStr.join(’, ‘)}): [${G.tools.bigCamelCase(table)}]\n) resolvers.Query[${G.tools.smallCamelCase(complex)}] = async (_, args) =&gt; { let rs = await new BaseDao(table).retrieve(args) return rs.data }生成Mutation查询 typeDefObj['mutation'].push( create${G.tools.bigCamelCase(table)}(${paramForMutation.slice(1).join(’, ‘)}):ReviseResult update${G.tools.bigCamelCase(table)}(${paramForMutation.join(’, ‘)}):ReviseResult delete${G.tools.bigCamelCase(table)}(${paramId}!):ReviseResult ) resolvers.Mutation[create${G.tools.bigCamelCase(table)}] = async (_, args) =&gt; { let rs = await new BaseDao(table).create(args) return rs } resolvers.Mutation[update${G.tools.bigCamelCase(table)}] = async (_, args) =&gt; { let rs = await new BaseDao(table).update(args) return rs } resolvers.Mutation[delete${G.tools.bigCamelCase(table)}`] = async (, { id }) => { let rs = await new BaseDao(table).delete({ id }) return rs }项目地址https://github.com/zhoutk/gels使用方法git clone https://github.com/zhoutk/gelscd gelsyarntsc -wnodemon dist/index.js然后就可以用浏览器打开链接:http://localhost:5000/graphql 查看效果了。小结我只能把大概思路写出来,让大家有个整体的概念,若想很好的理解,得自己把项目跑起来,根据我提供的思想,慢慢的去理解。因为我在编写的过程中还是遇到了不少的难点,这块既要自动化,还要能方便的接受手动编写的schema模块,的确有点难度。 ...

April 13, 2019 · 3 min · jiezi

视频当道的时代,这些珍藏的优质 Python 播客值得推荐

我国互联网的发展道路与欧美不同,在内容的形式上,我们似乎实现了跨越式的发展——早早进入了移动互联网时代,直播和短视频等形式的内容成为了潮流,而文字形式的博客(blog)与声音形式的播客(podcast)则(逐渐)成为了小众。智能手机极大地改变了我们的上网习惯。诚然,仍有一些受众广泛的聚合类的平台,例如微信公众号、CSDN、掘金、极客时间、喜马拉雅、荔枝FM,为我们提供丰富的博客与播客,但是,不依赖平台的个人博客与个人播客,则鲜有人知。依我的使用习惯,我很喜欢听音频节目,也即是播客。中文的播客听了不少,但是,免费的 Python 播客是极其稀少。直到发现了 Full Stack Python 网站上的一篇文章,它汇总介绍了一些非常棒的 Python 播客,大部分节目仍在持续更新中。我特翻译出来,分享给大家。英文节目对大多数人来说,可能门槛较高,但是英文是程序员的必修功课 ,聆听英文节目,正好可以一边学技术,一边练习英语,一举两得。英文 | Best Python Podcasts[0]译者 | 豌豆花下猫Python 社区里有很多免费或低成本的学习资源,对新手与有经验的开发者来说,是一大福音。这些优秀的资源就包括很多定期更新的 Python 播客节目。本文介绍了一些活跃的、与 Python 或软件工程相关的、高质量的播客。Python 相关的播客这些播客的运营者都是 Python 开发者,他们关注的都是我们领域内很重要的话题。每个播客系列都有很长的历史列表,有的节目录于几年前,因此我们有很丰富的材料可以聆听与学习。Talk Python to Me[1] 专注于 Python 开发者和组织,每期节目会邀请不同的嘉宾来谈论 ta 的工作Podcast.init[2] 提供有关 Python 的故事,以及“与那些让它变得更棒的人们的访谈”Python Bytes[3] 是来自“Talk Python to Me”和“Test and Code Podcast”创作者的新播客Test and Code Podcast[4] 侧重于测试与相关主题,如模拟(mock)和代码度量Philip Guo 教授有一个名为 PG Podcast[5] 的视频播客,基本是关于 Python 主题的Import This[6] 是 Kenneth Reitz 和 Alex Gaynor 间歇更新的播客,对有影响力的 Python 社区成员进行深度的采访最喜欢的播客节目以下是我从各大播客中收集的最喜欢的一些节目,听听这些内容,你可以感受到其余播客节目的风格。SQLAlchemy and data access in Python[7] 让我理解了对象关系映射库 SQLAlchemy 的知识及其演变过程。这期节目采访了 SQLAlchemy 的作者,主持人 Michael Kennedy 根据他对 SQLAlchemy 的深入研究和使用经验提出了很多问题。Python past, present, and future with Guido van Rossum[8] 涵盖了 Python 的历史、Guido 创造并持续三十年来发展这门语言的动机。有趣的事实:当播客主持人迈克尔·肯尼迪向我征询话题时,我贡献了一个问题,即 Python 的开源是否是促使它成功的原因?Deploying Python Web Applications[9] 剧透预警:这是我在 Talk Python to Me 上的一期节目,介绍了 Python Web 应用程序部署的工作原理。Python Bytes 栏目在第 39 集中广泛地讨论了 object-relational mappers (ORMs)[10] ,其中不少讨论是基于 Full Stack Python 上的文章。谢谢大家对我们提出的反馈与建议。Python at Netflix[11] 出自 Talk Python to Me ,通过一个非常棒的视角,介绍了 Python 是怎么运用于这家最大的网络流媒体公司,以及如何适应它们的多语言组织。另一个很棒的 Talk Python to Me 节目, Python in Finance[12],介绍了 Python 在金融行业中的广泛用途:股票交易、定量分析和数据分析。如果你想知道像对冲基金这样的不透明的私营企业是如何利用 Python 赚取(大量)钱财的,一定要听听这个。通用软件开发的播客这些播客主要探讨的是软件开发相关的主题,但经常也会涉及 Python 的内容。聆听和学习这些播客,你将会成为更加优秀的软件开发者。Software Engineering Daily[13] 令人难以置信的是每天邀请不同的开发者嘉宾,谈论话题非常广泛,与开发相关。All things Git[14] 教人如何使用、构建及将 Git 用于工作,每两周一更。CodeNewbie[15] 采访新入行的开发者,谈论为什么他们要从事编程工作,以及他们的工作内容。该栏目也会采访一些经验丰富的、打造了知名项目的开发者。Developer on Fire[16] 采访程序员、架构师和测试人员,讲述他们成功、失败和卓越的故事。Command_line Heroes[17] 涵盖操作系统级的主题以及 DevOps。Embedded.fm[18] 涵盖嵌入式系统和硬件黑客攻击。The Changelog[19] 周更播客,关于常规软件开发的问题。Full Stack Radio[20] 虽与 Full Stack Python 无关,但值得关注!Exponent[21] 不是一个软件开发的播客,但它以深入的方式揭示了企业的战略和技术,使我能够更好地理解企业在构建和发布软件时所做出的决策。我听了每一集(以 1.5 倍速),非常推荐每周花 45 到 60 分钟,听 Ben Thompson 和 James Allworth 深入讨论一个主题。Test Talks[22] 每周考察一个软件测试的主题,通常会特邀一位钻研该领域的嘉宾。The Cloudcast[23] 聚焦于云计算和 DevOps 的相关主题。数据科学与数据分析的播客Python 不仅是数据科学社区的核心编程语言,而且几乎在每个使用数据分析的组织中都发挥着重要作用。 以下播客广泛地涵盖数据科学,并经常涉及到 Python 生态系统中的特定的工具。DataFramed[24] 是一个数据科学播客,内容涵盖 Python 标准库,以及数据分析者感兴趣的其它内容。Data Skeptic[25] 涵盖数据科学、统计、机器学习、人工智能,以及“科学怀疑论”(scientific skepticism)等内容。Data stories[26] 是一个关于数据可视化的播客。Partially Derivative[27] 是一个关于机器学习、人工智能和数据行业的播客,在 2017 年底已停播,节目列表包含了大量的内容。References[0] Best Python Podcasts: https://www.fullstackpython.c…[1] Talk Python to Me: https://talkpython.fm/[2] Podcast.init: http://podcastinit.com/[3] Python Bytes: https://pythonbytes.fm/[4] Test and Code Podcast: http://pythontesting.net/test…[5] PG Podcast: http://pgbovine.net/PG-Podcas…[6] Import This: https://www.kennethreitz.org/…[7] SQLAlchemy and data access in Python: https://talkpython.fm/episode…[8] Python past, present, and future with Guido van Rossum: https://talkpython.fm/episode…[9] Deploying Python Web Applications: https://talkpython.fm/episode…[10] object-relational mappers (ORMs): https://www.fullstackpython.c…[11] Python at Netflix: https://talkpython.fm/episode…[12] Python in Finance: https://talkpython.fm/episode…[13] Software Engineering Daily: https://softwareengineeringda…[14] All things Git: https://www.allthingsgit.com/[15] CodeNewbie: https://www.codenewbie.org/po…[16] Developer on Fire: http://developeronfire.com/[17] Command_line Heroes: https://www.redhat.com/en/com…[18] Embedded.fm: http://embedded.fm/[19] The Changelog: https://changelog.com/[20] Full Stack Radio: http://www.fullstackradio.com/[21] Exponent: http://exponent.fm/[22] Test Talks: https://joecolantonio.com/tes…[23] The Cloudcast: http://www.thecloudcast.net/[24] DataFramed: https://www.datacamp.com/comm…[25] Data Skeptic: https://www.dataskeptic.com/[26] Data stories: http://datastori.es/[27] Partially Derivative: http://partiallyderivative.com/公众号【Python猫】, 专注Python技术、数据科学和深度学习,力图创造一个有趣又有用的学习分享平台。本号连载优质的系列文章,有喵星哲学猫系列、Python进阶系列、好书推荐系列、优质英文推荐与翻译等等,欢迎关注哦。PS:后台回复“爱学习”,免费获得一份学习大礼包。 ...

April 12, 2019 · 2 min · jiezi

关于php数据库事务的一个坑

在使用php的PDO扩展的时候发现的一个问题,在事务开启之后,如果php与mysql之间的连接断开了,会导致php直接记录一个warning的异常,而不是直接抛出一个Exception流程如下:/** * 一个用户财产变更的场景下 /try { // 1. 开启事务 /* * 2. 变更用户财产,增加财产变更的流水记录 */ // 3. 提交事务} catch (\Exception $e) { // 4. 记错误日志 // 5. 抛出异常}// 6. 发布用户财产变更的广播以上的操作可以简单的分成五类,在以前我的认知当中,操作事务的大致流程就是上面的样子,没有异常抛出则事务就是提交成功了的但是直到有一天数据库异常,有一个事务已经开启了,处在上面的1-2的过程当中,数据库直接挂掉,那么在步骤3提交事务的时候会直接出现一个warning级别的错误,“SQLSTATE[HY000]: General error: 2006 MySQL server has gone away” ,没有捕获到异常所以在步骤6的后续步骤中,其他业务方拿到了那条没有提交的流水id并进行了统计,但是实际上用户的财产并没有增加。从而导致了问题百思不得其解的时候去看了下文档,发现了一个历史遗留很久的bug:https://bugs.php.net/bug.php?…后来我们通过临时在事务的位置配置了set_error_handler解决了问题

April 12, 2019 · 1 min · jiezi

社区征稿 | 价值3200RMB的DTCC门票免费送!

1/23 - 2/23号我们发起了开源社区的第一波投稿邀请,收到了社区用户的3篇投稿,我们也如期给小伙伴们发放了200RMB的京东券奖励。3月,征稿活动再度来袭,这次给大家准备的是价值 3200RMB的DTCC3天入场门票。DTCC详情5月8日至10日,国内顶级的数据领域技术盛会第十届DTCC(中国数据库技术大会)将在北京举办,DTCC作为数据库领域行业盛会已有十年之久,见证了数据风云的十年变迁。此次大会分为3天技术演讲+2天深度主题培训,主要面向CTO、DBA、数据库架构师、数据库开发人员等,覆盖IT服务、互联网、电商、银行、金融、政府、医药等多行业。Tips:本期提供DTCC门票在大会期间可任意观看100+各行业重量级嘉宾的现场分享。如何获取DTCC门票3/27 - 4/27,为期30天,我们面向社区征集MySQL主题文章,由「爱可生开源社区」公众号收录后,截止4月27日17:00PM,文章阅读数量最高的前两名同学可分别获得价值 3200 RMB/张的DTCC门票1张。投稿请注意1.投稿文章格式:.md / .doc / .txt均可2.主题:MySQL相关的技术内容3.内容:稿件要求原创且尚未发布过4.投稿:请联系WeChat:mg116611我们拒绝带有下列特质的划水嫌疑文章:不符合事实 / 与 MySQL 无关 / 搬运内容非原创 / 过于简短 …3/27-4/27,欢迎社区同学投稿及分享,爱可生开源社区每周三推送原创MySQL深度分析专栏文章,大家可作参考。

April 12, 2019 · 1 min · jiezi

centos7安装mysql

最近打算在家里服务器上部署云盘,所以又开始了一系列的环境搭建操作,在安装mysql的时候发现有一些与以前不同,于是记录下来,避免下次再出现像今天这样到处搜索问题。一.卸载旧版本使用下面的命令检查是否安装有MySQL Serverrpm -qa | grep mysql有的话通过下面的命令来卸载掉rpm -e mysql //普通删除模式rpm -e –nodeps mysql // 强力删除模式,如果使用上面命令删除时,提示有依赖的其它文件,则用该命令可以对其进行强力删除二:安装MySQL1.安装依赖yum -y install make gcc-c++ cmake bison-devel ncurses-devel2.获取源码(国内建议去sohu的镜像下载http://mirrors.sohu.com/mysql…mysql5.7需要boost这个库,网上很难找到合适的版本,建议直接下载有boost库的mysql版本wget http://mirrors.sohu.com/mysql/MySQL-5.7/mysql-boost-5.7.24.tar.gztar xvf mysql-boost-5.7.24.tar.gzcd mysql-5.7.243.编译安装cmake -DCMAKE_INSTALL_PREFIX=/usr/local/mysql -DMYSQL_DATADIR=/usr/local/mysql/data -DSYSCONFDIR=/usr/local/mysql/etc -DWITH_MYISAM_STORAGE_ENGINE=1 -DWITH_INNOBASE_STORAGE_ENGINE=1 -DWITH_MEMORY_STORAGE_ENGINE=1 -DWITH_READLINE=1 -DMYSQL_UNIX_ADDR=/usr/local/mysql/data/mysql.sock -DMYSQL_TCP_PORT=3306 -DENABLED_LOCAL_INFILE=1 -DWITH_PARTITION_STORAGE_ENGINE=1 -DEXTRA_CHARSETS=all -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci -DDOWNLOAD_BOOST=1 -DWITH_BOOST=./boostmake && make install编译的参数可以参考http://dev.mysql.com/doc/refm…。三:配置MySQL使用下面的命令查看是否有mysql用户及用户组cat /etc/passwd #查看用户列表cat /etc/group #查看用户组列表如果没有就创建groupadd mysqluseradd -g mysql mysql修改/usr/local/mysql权限chown -R mysql:mysql /usr/local/mysqlmysql5.7.18以后不再提供默认的mysql配置文件,这里我们再网上找了一个简单的配置,vi /etc/my.cnf 然后写入一下内容[client]port = 3306default-character-set=utf8[mysqld]# 一般配置选项basedir = /usr/local/mysqldatadir = /usr/local/mysql/dataport = 3306character-set-server=utf8default_storage_engine = InnoDBsql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION配置服务脚本cp /usr/local/mysql/support-files/mysql.server /etc/init.d/mysqlchkconfig mysql on #添加到开机启动项service mysql start #启动mysql将mysql执行文件加到path目录,vi /etc/profilePATH=/usr/local/mysql/bin:$PATHexport PATH然后执行source /etc/profile四:初始化mysql1.执行初始化脚本(初始化成功最后一行会生成mysql root密码注意记录下来,或者也可以用./mysqld –initialize–insecure初始化一个空密码的账号)cd /usr/local/mysql/bin./mysqld –initialize –user=mysql –basedir=/usr/local/mysql –datadir=/usr/local/mysql/data…2019-04-11T14:34:15.922856Z 1 [Note] A temporary password is generated for root@localhost: /rTmud(Th5Yy2.防火墙开放3306端口Firewalld中添加端口方法如下:firewall-cmd –zone=public –add-port=3306/tcp –permanentfirewall-cmd –reload参考文章https://www.cnblogs.com/hsbt2…http://www.cnblogs.com/xiongp… ...

April 12, 2019 · 1 min · jiezi

Mysql 刚配置数据库出现用户不存在error

前几天自己想搭一个简单的ssm框架,用到了mysql数据库,下载安装配置完成后出现了Mysql Error:The user specified as a definer (‘mysql.infoschema’@’localhost’) does not exist’)这样的错误,研究了很多,热门解答都无法解决我的问题。网上的类似问题: The user specified as a definer (‘root’@’%’) does not exist解决方法: mysql -u root -p 你的密码 mysql>grant all privileges on . to root@”%” identified by “你的密码“; flush privileges;但是你会遇到以下问题:check the manual that corresponds to your MySQL server version for the right syntax出现这个问题的原因可能是:字段名可能包含mysql关键字所以推出另一种解决办法以绕过上述问题:从DOS进入MYSQL数据库C:\Program Files\MySQL\MySQL Server 8.0\binmysql -u root -pmysql> SET GLOBAL innodb_fast_shutdown = 1; mysql>\qC:\Program Files\MySQL\MySQL Server 8.0\binmysql_upgrade -u root -p如果最后一条命令执行,出现这样的报告 This installation of MySQL is already upgraded to 8.0, use –force if you still need to run mysql_upgrade则将最后一条命令改为: C:\Program Files\MySQL\MySQL Server 8.0\binmysql_upgrade -u root -p –force ...

April 11, 2019 · 1 min · jiezi

mysql(常用)

常用的操作分页查询// tableName 表名,skipNum 跳过条数,limitNum 限制查询的条数SELECT * from tableName LIMIT skipNum, limitNum

April 11, 2019 · 1 min · jiezi

重启mysql报错 服务名无效。 请键入 NET HELPMSG 2185 以获得更多的帮助。

当net stop mysql , net start mysql时报错:服务名无效。 请键入 NET HELPMSG 2185 以获得更多的帮助。请查看mysql服务名windows+r, 打开运行,输入services.msc, 找到mysql的服务,右键属性,发现服务器名称是MySQL57重新回到管理员:命令提示符,输入net start mysql57

April 11, 2019 · 1 min · jiezi

开发人员常用框架文档整理及中文翻译

开发人员常用的框架文档及中文翻译,包含 Spring 系列文档(Spring, Spring Boot, Spring Cloud, Spring Security, Spring Session),日志(Apache Flume, Log4j2),Http Server(NGINX,Apache),Python,数据库(OpenTSDB,MySQL,PostgreSQL)等最新官方文档以及对应的中文翻译。Docs4devhttps://www.docs4dev.comFeatures丰富的文档资源支持多语言英文中文(beta)…同步更新官方最新版本的文档支持多版本的文档基于 Elasticsearch 的文档全文检索文档列表Spring名称文档版本语言Spring Boot Reference1.5.9.RELEASEEnglishSpring Boot 中文文档1.5.9.RELEASE中文Spring Boot Reference2.1.1.RELEASEEnglishSpring Boot 中文文档2.1.1.RELEASE中文Spring Cloud ReferenceEdgware.SR5EnglishSpring Cloud 中文文档Edgware.SR5中文Spring Cloud ReferenceFinchley.SR2EnglishSpring Cloud 中文文档Finchley.SR2中文Spring Cloud ReferenceGreenwich.RELEASEEnglishSpring Cloud 中文文档Greenwich.RELEASE中文Spring Batch Reference3.0.xEnglishSpring Batch 中文文档3.0.x中文Spring Batch Reference4.1.xEnglishSpring Batch 中文文档4.1.x中文Spring Session Reference1.3.4.RELEASEEnglishSpring Session 中文文档1.3.4.RELEASE中文Spring Session Reference2.1.2.RELEASEEnglishSpring Session 中文文档2.1.2.RELEASE中文Spring Security Reference4.2.10.RELEASEEnglishSpring Security 中文文档4.2.10.RELEASE中文Spring Security Reference5.1.2.RELEASEEnglishSpring Security 中文文档5.1.2.RELEASE中文Spring AMQP Reference1.7.11.RELEASEEnglishSpring AMQP 中文文档1.7.11.RELEASE中文Spring AMQP Reference2.1.2.RELEASEEnglishSpring AMQP 中文文档2.1.2.RELEASE中文Spring Framework Reference4.3.21.RELEASEEnglishSpring Framework 中文文档4.3.21.RELEASE中文Spring Framework Reference5.1.3.RELEASEEnglishSpring Framework 中文文档5.1.3.RELEASE中文Spring Data JDBC1.0.5.RELEASEEnglishSpring Data JDBC1.0.5.RELEASE中文Spring Data JPA1.11.18.RELEASEEnglishSpring Data JPA1.11.18.RELEASE中文Spring Data JPA2.0.13.RELEASEEnglishSpring Data JPA2.0.13.RELEASE中文Spring Data JPA2.1.5.RELEASEEnglishSpring Data JPA2.1.5.RELEASE中文Spring Data Redis1.8.18.RELEASEEnglishSpring Data Redis1.8.18.RELEASE中文Spring Data Redis2.1.5.RELEASEEnglishSpring Data Redis2.1.5.RELEASE中文Http Server名称文档版本语言NginxcurrentEnglishNginx 中文文档current中文Apache2.4EnglishApache 中文文档2.4中文Python名称文档版本语言Python2.7.15EnglishPython 中文文档2.7.15中文Python3.7.2rc1EnglishPython 中文文档3.7.2rc1中文Database名称文档版本语言MySql5.7EnglishMySql 中文文档5.7中文PostgreSQL Documentation10.7EnglishPostgreSQL 中文文档10.7中文PostgreSQL Documentation11.2EnglishPostgreSQL 中文文档11.2中文OpenTSDB2.3EnglishOpenTSDB 中文文档2.3中文Logging名称文档版本语言Log4j2 Manual2.xEnglishLog4j2 中文文档2.x中文Apache Flume User Guide1.9.0EnglishApache Flume 用户指南1.9.0中文Contributing文档翻译如果你希望翻译相关的文档,请 clone 项目到本地,翻译完成后提交 PR。目前文档的是按照以下结构进行分类的:docs4dev/文档名称/版本号/语言代码/具体文档目前只支持以下语言:zh: 中文en: 英文如果你希望翻译其它语言,请提 issue.文档纠错如果你发现某个文档和官方文档有出入,请提 issue 或是在网站中提交 Feedback。其它如果你有针对此网站好的建议或意见,也欢迎提 issue.Roadmap更多的文档和更多的文档版本支持…Screenshot ...

April 10, 2019 · 1 min · jiezi

【MySQL】explicit_defaults_for_timestamp 参数详解

简介: explicit_defaults_for_timestamp 系统变量决定MySQL服务端对timestamp列中的默认值和NULL值的不同处理方法。此变量自MySQL 5.6.6 版本引入,分为全局级别和会话级别,可动态更新,默认值为OFF。本文主要介绍该参数打开和关闭情况下对timestamp的影响 。1.explicit_defaults_for_timestamp = OFF当该参数默认设置为OFF时,其行为如下:在默认情况下,如果timestamp列没有显式的指明null属性,那么该列会被自动加上not null属性(而其他类型的列如果没有被显式的指定not null,那么是允许null值的),如果往这个列中插入null值,会自动的设置该列的值为current timestamp值。表中的第一个timestamp列,如果没有指定null属性或者没有指定默认值,也没有指定ON UPDATE语句。那么该列会自动被加上DEFAULT CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAMP属性。对于其它TIMESTAMP列,如果没有显示指定NULL和DEFAULT属性的话,会自动设置为NOT NULL DEFAULT ‘0000-00-00 00:00:00’。(当然,这个与SQL_MODE有关,如果SQL_MODE中包含’NO_ZERO_DATE’,实际上是不允许将其默认值设置为'0000-00-00 00:00:00’的。)下面我们来测试下:(本文操作基于MySQL5.7.23 版本 SQL_MODE不包含’NO_ZERO_DATE’)mysql> show variables like ’explicit_defaults_for_timestamp’;+———————————+——-+| Variable_name | Value |+———————————+——-+| explicit_defaults_for_timestamp | OFF |+———————————+——-+mysql> create table t1 -> ( -> ts1 timestamp, -> ts2 timestamp, -> ts3 timestamp default ‘2010-01-01 00:00:00’ -> );Query OK, 0 rows affected (0.03 sec)mysql> show create table t1\G*************************** 1. row *************************** Table: t1Create Table: CREATE TABLE t1 ( ts1 timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, ts2 timestamp NOT NULL DEFAULT ‘0000-00-00 00:00:00’, ts3 timestamp NOT NULL DEFAULT ‘2010-01-01 00:00:00’) ENGINE=InnoDB DEFAULT CHARSET=utf81 row in set (0.00 sec)mysql> insert into t1 values (null,null,null);Query OK, 1 row affected (0.00 sec)mysql> select * from t1;+———————+———————+———————+| ts1 | ts2 | ts3 |+———————+———————+———————+| 2019-04-09 15:54:56 | 2019-04-09 15:54:56 | 2019-04-09 15:54:56 |+———————+———————+———————+1 row in set (0.00 sec)从表结构来看,MySQL自动为第一个timestamp字段自动设置NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP属性,而后面的timestamp字段,若没有指定,则设置了 NOT NULL DEFAULT ‘0000-00-00 00:00:00’属性。如果向timestamp这个列中插入null值,系统会自动的设置该列的值为current timestamp值。即explicit_defaults_for_timestamp=OFF时,即使timestamp列设为NOT NULL也能插入NULL值,系统会自动将NULL值设为current timestamp。2.explicit_defaults_for_timestamp = ON当该参数设置为ON时,其行为如下:如果timestamp列没有显式的指定not null属性,那么默认的该列可以为null,此时向该列中插入null值时,会直接记录null,而不是current timestamp。不会自动的为表中的第一个timestamp列加上DEFAULT CURRENT_TIMESTAMP 和ON UPDATE CURRENT_TIMESTAMP属性。如果timestamp列被加上了not null属性,并且没有指定默认值。这时如果向表中插入记录,但是没有给该TIMESTAMP列指定值的时候,如果strict sql_mode被指定了,那么会直接报错。如果strict sql_mode没有被指定,那么会向该列中插入'0000-00-00 00:00:00’并且产生一个warning。同样的,我们来测试下:mysql> show variables like ’explicit_defaults_for_timestamp’;+———————————+——-+| Variable_name | Value |+———————————+——-+| explicit_defaults_for_timestamp | ON |+———————————+——-+mysql> create table t2 -> ( -> ts1 timestamp, -> ts2 timestamp, -> ts3 timestamp default ‘2010-01-01 00:00:00’ -> );Query OK, 0 rows affected (0.02 sec)mysql> show create table t2\G*************************** 1. row *************************** Table: t2Create Table: CREATE TABLE t2 ( ts1 timestamp NULL DEFAULT NULL, ts2 timestamp NULL DEFAULT NULL, ts3 timestamp NULL DEFAULT ‘2010-01-01 00:00:00’) ENGINE=InnoDB DEFAULT CHARSET=utf81 row in set (0.01 sec)mysql> insert into t2 values (null,null,null);Query OK, 1 row affected (0.01 sec)mysql> select * from t2;+——+——+——+| ts1 | ts2 | ts3 |+——+——+——+| NULL | NULL | NULL |+——+——+——+1 row in set (0.00 sec)– 指定NOT NULLmysql> create table t3 -> ( -> ts1 timestamp, -> ts2 timestamp, -> ts3 timestamp not null -> );Query OK, 0 rows affected (0.01 sec)mysql> show create table t3\G*************************** 1. row *************************** Table: t3Create Table: CREATE TABLE t3 ( ts1 timestamp NULL DEFAULT NULL, ts2 timestamp NULL DEFAULT NULL, ts3 timestamp NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf81 row in set (0.01 sec)mysql> insert into t3 values (null,null,null);ERROR 1048 (23000): Column ’ts3’ cannot be nullmysql> insert into t3 (ts1,ts2) values (null,null);Query OK, 1 row affected, 1 warning (0.01 sec)mysql> show warnings;+———+——+——————————————+| Level | Code | Message |+———+——+——————————————+| Warning | 1364 | Field ’ts3’ doesn’t have a default value |+———+——+——————————————+mysql> select * from t3;+——+——+———————+| ts1 | ts2 | ts3 |+——+——+———————+| NULL | NULL | 0000-00-00 00:00:00 |+——+——+———————+从表结构上看出,在参数开启的情况下MySQL默认会为timestamp列添加 null default null属性,而且MySQL也没有为第一个timestamp字段设置该列为current timestamp值。timestamp 字段写入null值,写入之后存储的就是null值,而不是当前的时间。当timestamp 字段指定NOT NULL时,若显式插入NULL则报错提示:该字段不能为空;若不显式插入该字段且SQL_MODE不包含’NO_ZERO_DATE’,则会向该列中插入'0000-00-00 00:00:00’并且产生一个warning。总结:实际情况下,我们经常会这样创建表:CREATE TABLE table_name ( increment_id INT UNSIGNED NOT NULL auto_increment COMMENT ‘自增主键’, … create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT ‘创建时间’, update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘修改时间’, PRIMARY KEY (increment_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8;此时timestamp字段会指定NOT NULL,这时建议explicit_defaults_for_timestamp 参数采用默认的OFF,这样当timestamp字段显式插入NULL值时不报错,特别是程序sql写的不规范时,可以避免程序插入报错。在不同环境间,此参数建议统一 ,不然可能出现程序在这个环境运行正常 在另外一个环境却出现报错的情况。笔者了解到亚马逊RDS MySQL5.7实例该参数默认为ON,在环境迁移时要特别注意下该参数。参考: http://suo.im/5bDU2o> ...

April 10, 2019 · 3 min · jiezi

SpringBoot+MySQL+MyBatis的入门教程

本博客 猫叔的博客,转载请申明出处本系列教程为HMStrange项目附带。历史文章如何在VMware12安装Centos7.6最新版Centos7.6安装Java8Centos7.6安装MySQL+Redis(最新版)教程内容备注:本系列开发工具均为IDEA1、构建项目,选择Lombok、Web、MySQL、MyBatis四个基本的Maven依赖。大家可以看看pom文件<?xml version=“1.0” encoding=“UTF-8”?><project xmlns=“http://maven.apache.org/POM/4.0.0" xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> <relativePath/> <!– lookup parent from repository –> </parent> <groupId>com.myself.mybatis</groupId> <artifactId>datademo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>datademo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>2、准备MySQL,这里可以参考历史文章的安装MySQL环节,我新建了一个数据库,针对这个项目,构建了一张简单的表。DDLCREATE TABLE t_msg ( id int(11) NOT NULL, message varchar(255) DEFAULT NULL COMMENT ‘信息’, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;3、构建项目目录,我构建了一个经典的web项目目录结构,entity实体类、mapper映射、service接口、impl接口实现、controller业务访问、resources/mapper包用于存放xml4、填写application.yml,默认生成不是yml,不过我觉得yml视觉效果好一些,就改了一下,我们需要填写数据库信息,还有mybatis的数据库映射地址,实体类地址spring: datasource: url: jdbc:mysql://192.168.192.133:3306/datademo?characterEncoding=utf-8&useSSL=false username: root password: password driver-class-name: com.mysql.cj.jdbc.Drivermybatis: mapper-locations: classpath*:mapper/Mapper.xml type-aliases-package: com.myself.mybatis.entity5、构建数据库对应的实体类TMsg,这个类放在entitypackage com.myself.mybatis.entity;import lombok.Data;import java.io.Serializable;/* * Created by MySelf on 2019/4/9. /@Datapublic class TMsg implements Serializable { private Integer id; private String message;}6、构建对应的Mapper接口(其实就类似dao层),这里与TMsgMapper.xml文件对应关系package com.myself.mybatis.mapper;import com.myself.mybatis.entity.TMsg;import org.apache.ibatis.annotations.Mapper;/* * Created by MySelf on 2019/4/9. /@Mapperpublic interface TMsgMapper { public TMsg findById(Integer id);}<?xml version=“1.0” encoding=“UTF-8”?><!DOCTYPE mapper PUBLIC “-//mybatis.org//DTD Mapper 3.0//EN” “http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace=“com.myself.mybatis.mapper.TMsgMapper”> <select id=“findById” resultType=“com.myself.mybatis.entity.TMsg”> SELECT id,message from t_msg WHERE id = #{id} </select></mapper>我这边就单纯一个方法,大家可以扩展自己的方法。7、service层与其实现,这个比较简单,一般做过web项目的都了解package com.myself.mybatis.service;import com.myself.mybatis.entity.TMsg;/* * Created by MySelf on 2019/4/9. /public interface TMsgService { public TMsg findById(Integer id);}package com.myself.mybatis.service.impl;import com.myself.mybatis.entity.TMsg;import com.myself.mybatis.mapper.TMsgMapper;import com.myself.mybatis.service.TMsgService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;/* * Created by MySelf on 2019/4/9. /@Servicepublic class TMsgServiceImpl implements TMsgService { @Autowired private TMsgMapper tMsgMapper; @Override public TMsg findById(Integer id) { return tMsgMapper.findById(id); }}8、controller层,我这边构建了一个get方法,通过id获取信息。package com.myself.mybatis.controller;import com.myself.mybatis.entity.TMsg;import com.myself.mybatis.service.TMsgService;import org.apache.ibatis.annotations.Param;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/* * Created by MySelf on 2019/4/9. */@RestController@RequestMapping("/msg”)public class TMsgController { @Autowired private TMsgService tMsgService; @GetMapping("/getMsg”) public String getMsg(@Param(“id”) Integer id){ TMsg tMsg = tMsgService.findById(id); return tMsg.getMessage(); }}9、启动项目,并使用Postman测试10、项目下载地址欢迎到HMStrange项目进行下载:https://github.com/UncleCatMy…公众号:Java猫说学习交流群:728698035现架构设计(码农)兼创业技术顾问,不羁平庸,热爱开源,杂谈程序人生与不定期干货。 ...

April 10, 2019 · 2 min · jiezi

Centos7.6安装MySQL+Redis(最新版)

本博客 猫叔的博客,转载请申明出处本系列教程为HMStrange项目附带。历史文章如何在VMware12安装Centos7.6最新版Centos7.6安装Java8MySQL教程1、下载mysql,地址:http://dev.mysql.com/get/mysq…2、使用xftp上传到自己想要得目录3、代码操作,安装并重启mysql服务# rpm -ivh mysql-community-release-el7-5.noarch.rpm# yum install mysql-community-server# service mysqld restart4、设置mysql的root密码# mysql -u rootmysql> set password for ‘root’@’localhost’ =password(‘你的密码’);mysql> flush privileges;5、配置mysql编码# vi /etc/my.cnf[mysql]default-character-set =utf86、允许远程链接,进入mysqlmysql> grant all privileges on . to root@’%‘identified by ‘远程密码’;mysql> flush privileges;7、开启3306端口,或者关闭防火墙,我是虚拟机环境且自己用的,我直接关了防火墙。systemctl stop firewalld.service #停止firewallsystemctl disable firewalld.service #禁止firewall开机启动Redis教程1、下载redis,地址: http://download.redis.io/rele...2、安装gcc等默认需要的yum install -y gcc tcl3、解压安装# tar xzf redis-5.0.4.tar.gz# cd redis-5.0.4# make install4、修改redis.confbind 0.0.0.0 #允许远程protected-mode no #关闭保护模式daemonize yes #守护进程模式开启5、启动redis# cd src# ./redis-server ./../redis.conf公众号:Java猫说学习交流群:728698035现架构设计(码农)兼创业技术顾问,不羁平庸,热爱开源,杂谈程序人生与不定期干货。

April 10, 2019 · 1 min · jiezi

一条SQL语句在MySQL中如何执行的

该文已加入笔主的开源项目——JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识的文档类项目),地址:https://github.com/Snailclimb/JavaGuide 。觉得不错的话,记得点个Star。本文来自木木匠投稿。本篇文章会分析下一个 sql 语句在 MySQL 中的执行流程,包括 sql 的查询在 MySQL 内部会怎么流转,sql 语句的更新是怎么完成的。在分析之前我会先带着你看看 MySQL 的基础架构,知道了 MySQL 由那些组件组成已经这些组件的作用是什么,可以帮助我们理解和解决这些问题。一 MySQL 基础架构分析1.1 MySQL 基本架构概览下图是 MySQL 的一个简要架构图,从下图你可以很清晰的看到用户的 SQL 语句在 MySQL 内部是如何执行的。先简单介绍一下下图涉及的一些组件的基本作用帮助大家理解这幅图,在 1.2 节中会详细介绍到这些组件的作用。连接器: 身份认证和权限相关(登录 MySQL 的时候)。查询缓存: 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。分析器: 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。优化器: 按照 MySQL 认为最优的方案去执行。执行器: 执行语句,然后从存储引擎返回数据。简单来说 MySQL 主要分为 Server 层和存储引擎层:Server 层:主要包括连接器、查询缓存、分析器、优化器、执行器等,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图,函数等,还有一个通用的日志模块 binglog 日志模块。存储引擎: 主要负责数据的存储和读取,采用可以替换的插件式架构,支持 InnoDB、MyISAM、Memory 等多个存储引擎,其中 InnoDB 引擎有自有的日志模块 redolog 模块。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始就被当做默认存储引擎了。1.2 Server 层基本组件介绍1) 连接器连接器主要和身份认证和权限相关的功能相关,就好比一个级别很高的门卫一样。主要负责用户登录数据库,进行用户的身份认证,包括校验账户密码,权限等操作,如果用户账户密码已通过,连接器会到权限表中查询该用户的所有权限,之后在这个连接里的权限逻辑判断都是会依赖此时读取到的权限数据,也就是说,后续只要这个连接不断开,即时管理员修改了该用户的权限,该用户也是不受影响的。2) 查询缓存(MySQL 8.0 版本后移除)查询缓存主要用来缓存我们所执行的 SELECT 语句以及该语句的结果集。连接建立后,执行查询语句的时候,会先查询缓存,MySQL 会先校验这个 sql 是否执行过,以 Key-Value 的形式缓存在内存中,Key 是查询预计,Value 是结果集。如果缓存 key 被命中,就会直接返回给客户端,如果没有命中,就会执行后续的操作,完成后也会把结果缓存起来,方便下一次调用。当然在真正执行缓存查询的时候还是会校验用户的权限,是否有该表的查询条件。MySQL 查询不建议使用缓存,因为查询缓存失效在实际业务场景中可能会非常频繁,假如你对一个表更新的话,这个表上的所有的查询缓存都会被清空。对于不经常更新的数据来说,使用缓存还是可以的。所以,一般在大多数情况下我们都是不推荐去使用查询缓存的。MySQL 8.0 版本后删除了缓存的功能,官方也是认为该功能在实际的应用场景比较少,所以干脆直接删掉了。3) 分析器MySQL 没有命中缓存,那么就会进入分析器,分析器主要是用来分析 SQL 语句是来干嘛的,分析器也会分为几步:第一步,词法分析,一条 SQL 语句有多个字符串组成,首先要提取关键字,比如 select,提出查询的表,提出字段名,提出查询条件等等。做完这些操作后,就会进入第二步。第二步,语法分析,主要就是判断你输入的 sql 是否正确,是否符合 MySQL 的语法。完成这 2 步之后,MySQL 就准备开始执行了,但是如何执行,怎么执行是最好的结果呢?这个时候就需要优化器上场了。4) 优化器优化器的作用就是它认为的最优的执行方案去执行(有时候可能也不是最优,这篇文章涉及对这部分知识的深入讲解),比如多个索引的时候该如何选择索引,多表查询的时候如何选择关联顺序等。可以说,经过了优化器之后可以说这个语句具体该如何执行就已经定下来。5) 执行器当选择了执行方案后,MySQL 就准备开始执行了,首先执行前会校验该用户有没有权限,如果没有权限,就会返回错误信息,如果有权限,就会去调用引擎的接口,返回接口执行的结果。二 语句分析2.1 查询语句说了以上这么多,那么究竟一条 sql 语句是如何执行的呢?其实我们的 sql 可以分为两种,一种是查询,一种是更新(增加,更新,删除)。我们先分析下查询语句,语句如下:select * from tb_student A where A.age=‘18’ and A.name=’ 张三 ‘;结合上面的说明,我们分析下这个语句的执行流程:先检查该语句是否有权限,如果没有权限,直接返回错误信息,如果有权限,在 MySQL8.0 版本以前,会先查询缓存,以这条 sql 语句为 key 在内存中查询是否有结果,如果有直接缓存,如果没有,执行下一步。通过分析器进行词法分析,提取 sql 语句的关键元素,比如提取上面这个语句是查询 select,提取需要查询的表名为 tb_student,需要查询所有的列,查询条件是这个表的 id=‘1’。然后判断这个 sql 语句是否有语法错误,比如关键词是否正确等等,如果检查没问题就执行下一步。接下来就是优化器进行确定执行方案,上面的 sql 语句,可以有两种执行方案: a.先查询学生表中姓名为“张三”的学生,然后判断是否年龄是 18。 b.先找出学生中年龄 18 岁的学生,然后再查询姓名为“张三”的学生。那么优化器根据自己的优化算法进行选择执行效率最好的一个方案(优化器认为,有时候不一定最好)。那么确认了执行计划后就准备开始执行了。进行权限校验,如果没有权限就会返回错误信息,如果有权限就会调用数据库引擎接口,返回引擎的执行结果。2.2 更新语句以上就是一条查询 sql 的执行流程,那么接下来我们看看一条更新语句如何执行的呢?sql 语句如下:update tb_student A set A.age=‘19’ where A.name=’ 张三 ‘;我们来给张三修改下年龄,在实际数据库肯定不会设置年龄这个字段的,不然要被技术负责人打的。其实条语句也基本上会沿着上一个查询的流程走,只不过执行更新的时候肯定要记录日志啦,这就会引入日志模块了,MySQL 自带的日志模块式 binlog(归档日志) ,所有的存储引擎都可以使用,我们常用的 InnoDB 引擎还自带了一个日志模块 redo log(重做日志),我们就以 InnoDB 模式下来探讨这个语句的执行流程。流程如下:先查询到张三这一条数据,如果有缓存,也是会用到缓存。然后拿到查询的语句,把 age 改为 19,然后调用引擎 API 接口,写入这一行数据,InnoDB 引擎把数据保存在内存中,同时记录 redo log,此时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,随时可以提交。执行器收到通知后记录 binlog,然后调用引擎接口,提交 redo log 为提交状态。更新完成。这里肯定有同学会问,为什么要用两个日志模块,用一个日志模块不行吗?这是因为最开始 MySQL 并没与 InnoDB 引擎( InnoDB 引擎是其他公司以插件形式插入 MySQL 的) ,MySQL 自带的引擎是 MyISAM,但是我们知道 redo log 是 InnoDB 引擎特有的,其他存储引擎都没有,这就导致会没有 crash-safe 的能力(crash-safe 的能力即使数据库发生异常重启,之前提交的记录都不会丢失),binlog 日志只能用来归档。并不是说只用一个日志模块不可以,只是 InnoDB 引擎就是通过 redo log 来支持事务的。那么,又会有同学问,我用两个日志模块,但是不要这么复杂行不行,为什么 redo log 要引入 prepare 预提交状态?这里我们用反证法来说明下为什么要这么做?先写 redo log 直接提交,然后写 binlog,假设写完 redo log 后,机器挂了,binlog 日志没有被写入,那么机器重启后,这台机器会通过 redo log 恢复数据,但是这个时候 bingog 并没有记录该数据,后续进行机器备份的时候,就会丢失这一条数据,同时主从同步也会丢失这一条数据。先写 binlog,然后写 redo log,假设写完了 binlog,机器异常重启了,由于没有 redo log,本机是无法恢复这一条记录的,但是 binlog 又有记录,那么和上面同样的道理,就会产生数据不一致的情况。如果采用 redo log 两阶段提交的方式就不一样了,写完 binglog 后,然后再提交 redo log 就会防止出现上述的问题,从而保证了数据的一致性。那么问题来了,有没有一个极端的情况呢?假设 redo log 处于预提交状态,binglog 也已经写完了,这个时候发生了异常重启会怎么样呢?这个就要依赖于 MySQL 的处理机制了,MySQL 的处理过程如下:判断 redo log 是否完整,如果判断是完整的,就立即提交。如果 redo log 只是预提交但不是 commit 状态,这个时候就会去判断 binlog 是否完整,如果完整就提交 redo log, 不完整就回滚事务。这样就解决了数据一致性的问题。三 总结MySQL 主要分为 Server 曾和引擎层,Server 层主要包括连接器、查询缓存、分析器、优化器、执行器,同时还有一个日志模块(binlog),这个日志模块所有执行引擎都可以共用,redolog 只有 InnoDB 有。引擎层是插件式的,目前主要包括,MyISAM,InnoDB,Memory 等。查询语句的执行流程如下:权限校验(如果命中缓存)—》查询缓存—》分析器—》优化器—》权限校验—》执行器—》引擎更新语句执行流程如下:分析器—-》权限校验—-》执行器—》引擎—redo log(prepare 状态—》binlog—》redo log(commit状态)四 参考《MySQL 实战45讲》MySQL 5.6参考手册:https://dev.MySQL.com/doc/ref…专注Java知识和面试技能分享!我已经整理好了一份Java 学习必备的书籍+视频+文档汇总,内容比较多,你可以在公众号后台回复关键“1”,我会免费无套路把这些都给你。 ...

April 10, 2019 · 2 min · jiezi

事务隔离级别(图文详解)

该文已加入笔主的开源项目——JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识的文档类项目),地址:https://github.com/Snailclimb…。觉得不错的话,记得点个Star。本文由 SnailClimb 和 BugSpeak 共同完成。事务隔离级别(图文详解)什么是事务?事务是逻辑上的一组操作,要么都执行,要么都不执行。事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。事物的特性(ACID)<div align=“center”> <img src=“https://user-gold-cdn.xitu.io…;h=305&f=png&s=22430” width=“200px”/></div>原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;一致性: 执行事务前后,数据保持一致;隔离性: 并发访问数据库时,一个用户的事物不被其他事物所干扰,各并发事务之间数据库是独立的;持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。并发事务带来的问题在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对统一数据进行操作)。并发虽然是必须的,但可能会导致一下的问题。脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。丢失修改(Lost to modify): 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。不可重复度和幻读区别:不可重复读的重点是修改,幻读的重点在于新增或者删除。例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读。事务隔离级别SQL 标准定义了四个隔离级别:READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生REPEATABLE-READ(可重读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)。我们可以通过SELECT @@tx_isolation;命令来查看mysql> SELECT @@tx_isolation;+—————–+| @@tx_isolation |+—————–+| REPEATABLE-READ |+—————–+这里需要注意的是:与 SQL 标准不同的地方在于InnoDB 存储引擎在 REPEATABLE-READ(可重读)事务隔离级别下使用的是Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) 已经可以完全保证事务的隔离性要求,即达到了 SQL标准的SERIALIZABLE(可串行化)隔离级别。因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是你要知道的是InnoDB 存储引擎默认使用 REPEATABLE-READ(可重读)并不会有任何性能损失。InnoDB 存储引擎在 分布式事务 的情况下一般会用到SERIALIZABLE(可串行化)隔离级别。实际情况演示在下面我会使用 2 个命令行mysql ,模拟多线程(多事务)对同一份数据的脏读问题。MySQL 命令行的默认配置中事务都是自动提交的,即执行SQL语句后就会马上执行 COMMIT 操作。如果要显式地开启一个事务需要使用命令:START TARNSACTION。我们可以通过下面的命令来设置隔离级别。SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]我们再来看一下我们在下面实际操作中使用到的一些并发控制语句:START TARNSACTION |BEGIN:显式地开启一个事务。COMMIT:提交事务,使得对数据库做的所有修改成为永久性。ROLLBACK 回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。脏读(读未提交)<div align=“center”> <img src=“https://user-gold-cdn.xitu.io…;h=728&f=png&s=232588” width=“800px”/></div>避免脏读(读已提交)<div align=“center”> <img src=“https://user-gold-cdn.xitu.io…;h=738&f=png&s=419443” width=“800px”/></div>不可重复读还是刚才上面的读已提交的图,虽然避免了读未提交,但是却出现了,一个事务还没有结束,就发生了 不可重复读问题。<div align=“center”> <img src=“https://user-gold-cdn.xitu.io…;h=682&f=png&s=230437”/></div>可重复读<div align=“center”> <img src=“https://user-gold-cdn.xitu.io…;h=738&f=png&s=506596”/></div>防止幻读(可重复读)<div align=“center”> <img src=“https://user-gold-cdn.xitu.io…;h=291&f=png&s=44012”/></div>一个事务对数据库进行操作,这种操作的范围是数据库的全部行,然后第二个事务也在对这个数据库操作,这种操作可以是插入一行记录或删除一行记录,那么第一个是事务就会觉得自己出现了幻觉,怎么还有没有处理的记录呢? 或者 怎么多处理了一行记录呢?幻读和不可重复读有些相似之处 ,但是不可重复读的重点是修改,幻读的重点在于新增或者删除。参考《MySQL技术内幕:InnoDB存储引擎》https://dev.mysql.com/doc/ref…专注Java知识和面试技能分享!我已经整理好了一份Java 学习必备的书籍+视频+文档汇总,内容比较多,你可以在公众号后台回复关键“1”,我会免费无套路把这些都给你。 ...

April 10, 2019 · 1 min · jiezi

Mysql 优化点

具体参考: https://blog.csdn.net/luoyang…

April 10, 2019 · 1 min · jiezi

MySQL 双向备份

MySQL 双向备份也被称为 主主备份 ,即两个 MySQL 服务都是 Master,其中任意一个服务又是另一个服务的 Slave。准备服务器MySQL服务器IP地址masterA192.168.1.201masterB192.168.1.202具体操作注意操作过程中注意两边数据的一致!!!masterA 配置my.cnf[mysqld]# 服务器唯一标识server-id=1# 二进制日志文件名log-bin=mysql-bin# 需要备份的数据库,多个数据库用 , 分隔binlog-do-db=piumnl# 需要复制的数据库,多个数据库用 , 分隔replicate-do-db=piumnl# 中继日志文件名relay_log=mysqld-relay-bin# 互为主从需要加入这一行log-slave-updates=ON# 禁用符号链接,防止安全风险,可不加symbolic-links=0# 可不加# resolve - [Warning] Slave SQL: If a crash happens this configuration does not guarantee that the relay log info will be consistent, Error_code: 0master-info-repository=tablerelay-log-info-repository=tablerelay-log-recovery=1# 可不加# 禁用 dns 解析,会使授权时使用的域名无效skip-host-cacheskip-name-resolvesql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLESmasterB 配置my.cnf# 不再解释各个配置项[mysqld]server-id=2log-bin=mysql-binbinlog-do-db=piumnlreplicate-do-db=piumnlrelay_log=mysql-relay-binlog-slave-updates=ONsymbolic-links=0# resolve - [Warning] Slave SQL: If a crash happens this configuration does not guarantee that the relay log info will be consistent, Error_code: 0master-info-repository=tablerelay-log-info-repository=tablerelay-log-recovery=1skip-host-cacheskip-name-resolvesql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES创建备份用户masterA & masterB 都要创建备份用户:create user ‘rep’@’%’ identified by ‘rep’; # 创建一个账户grant replication slave on . to ‘rep’@’%’; # 授予该账户对任意数据库任意表的主从备份权限备注:Linux 下 MySQL 对 root@% 关闭了 grant_priv 权限此处备份用户帐号和密码可不一致,此处为了简化操作使用一样的帐号和密码重启服务器重启服务器开启备份masterA查看 masterB 状态show master status\G;# 此处需要关注 File 和 Position 值开启备份stop slave;# master_log_file 就是第一步操作的 File 值# master_log_pos 就是第一步操作的 Position 值change master to master_host=<master_hostname>, master_user=<rep_username>, master_password=<rep_password>, master_log_file=‘mysql-log.000003’, master_log_pos=154;start slave;查看结果show slave status\G;# 查看最重要的两项,两个都必须为 Yes ,有一个为 No 都要去查看错误日志文件,看看什么地方存在问题# Slave_IO_Running: Yes# Slave_SQL_Running: YesmasterB反向重复 masterA 的操作测试分别在 masterA 和 masterB 中插入数据,并查看另一台服务器是否及时出现预期的数据注:使用 <database>.<table> 方式进行插入、更新和删除操作,将不会备份。如果操作弄乱了 master 与 slave 的日志,可使用如下操作进行重置。reset master; # 重置 master 的配置,包括二进制日志reset slave; # 重置 slave 的配置 ...

April 9, 2019 · 1 min · jiezi

mysql及proxy

整体架构/流程主流程:主函数在sql/amin.cc中调用Mysqld.cc中mysqld_main首先载入篇日志,信号注册,plugin_register (mysql是插件式存储引擎设计,innodb,myisam等都是插件,在这里注册),核心为mysqld_socket_acceptor->connection_event_loop();监听处理循环poll。 process_new_connection处理handler有三种,线程池方式只用于商业,一个线程处理所有请求,一个连接一个线程(大多数选择Per_thread_connection_handler)。若thread_cache中有空闲直接获取,否则创建新的用户线程。进入用户线程的handle_connection 3.1 mysql网络通信一共有这几层:THD | Protocol | NET | VIO | SOCKET,protocol对数据的协议格式化,NET封装了net buf读写刷到网络的操作,VIO是对所有连接类型网络操作的一层封装(TCP/IP, Socket, Name Pipe, SSL, SHARED MEMORY),handle_connection初始化THD(线程),开始do_command (关于THD,有个很好的图:http://mysql.taobao.org/month…) 3.2.do_command=>dispatch_comand=>mysql_parse=》【检查query_cache有缓存直接返回否则=》】parse_sql=》mysql_execute_cmd判断insert等调用mysql_insert,每条记录调用write_record,这个是各个引擎的基类,根据操作表类型调用引擎层的函数=》写binlog日志=》提交/回滚。注意大家可能都以为是有query_cache的。但是从8.0开启废弃了query_cache。第二正会讲一下除了用户线程和主线程,在启动时,还创建了timer_notify线程。由于为了解决DDL无法做到atomic等,从MySQL8.0开始取消了FRM文件及其他server层的元数据文件(frm, par, trn, trg, isl,db.opt),所有的元数据都用InnoDB引擎进行存储, 另外一些诸如权限表之类的系统表也改用InnoDB引擎。因此在加载这些表时,创建了innodb用到的一系列线程。从插入流程开始外层 handle_connection=>do_commannd=>dispatch_command=>mysql_parse=>mysql_execute_commannd=>sql_cmd_dml::execute=> execute_inner while{对每条记录执行write_record} =>ha_write_row【返回到这里不出错记录binlog】,调用引擎table->file->ha_write_row(table->record[0])引擎层:row_insert_for_mysql_using_ins_graph开始,有开启事务的操作,trx_start_low。首先,需要分配回滚段,因为会修改数据,就需要找地方把老版本的数据给记录下来,其次,需要通过全局事务id产生器产生一个事务id,最后,把读写事务加入到全局读写事务链表(trx_sys->rw_trx_list),把事务id加入到活跃读写事务数组中(trx_sys->descriptors)在InnoDB看来所有的事务在启动时候都是只读状态,只有接受到修改数据的SQL后(InnoDB接收到才行。因为在start transaction read only模式下,DML/DDL都被Serve层挡掉了)才调用trx_set_rw_mode函数把只读事务提升为读写事务。整体流程图如下:必须有这些步骤的原因:[1]为了快,所有数据先写入内存,再刷脏[2]为了防止数据页写过程中崩溃数据的持久性=》先写redo保证重启后可以恢复。日志写不成功不操作,日志是顺序写,内容少,可以同步等。(最好是物理重做)。[3]异常回滚=》物理回滚反解复杂,需要一个逻辑日志。 基于undo log又实现了MVCC unlog等也要保证操作持久化原子化。[4]为了删除不每次整理页,只标记,为了真正删除/undo不需要的清除=》purge[5]flush对一个pageid多次操作合并在一起减少随机操作=》二级索引非唯一change buff[6]Flush过程中一个页部分写成功就崩溃,无法正确后恢复=》二次写[7]为完整的主链路。[8]为异步的刷盘链路性能磁盘,B+树先来个准确的B+比B为何更适合:区别两点,一个是B树搜索是可以止于非页节点的,包含数据(包含数据在磁盘中页的位置),且数据只出现在树中一次。另一点是叶子节点有双向链表。第一点使得节点可以包含更多路(因为不存数据在磁盘中页的位置,只包含下一层的指针页位置,B树这两个都要包含),层高会更少;只能到页节点搜索结束,性能稳定。第二点为了扫描和范围索引。内存buffer所有数据页。都走这套。包括undo等所有的数据页都存储在一个LRU链表上,修改过的block被加到flush_list上,解压的数据页被放到unzip_LRU链表上。namedescbuf_pool_t::page_hashpage_hash用于存储已经或正在读入内存的page。根据<space_id, page_no>快速查找。当不在page hash时,才会去尝试从文件读取buf_pool_t::LRULRU上维持了所有从磁盘读入的数据页,该LRU上又在链表尾部开始大约3/8处将链表划分为两部分,新读入的page被加入到这个位置;当我们设置了innodb_old_blocks_time,若两次访问page的时间超过该阀值,则将其挪动到LRU头部;这就避免了类似一次性的全表扫描操作导致buffer pool污染buf_pool_t::free存储了当前空闲可分配的blockbuf_pool_t::flush_list存储了被修改过的page,根据oldest_modification(即载入内存后第一次修改该page时的Redo LSN)排序buf_pool_t::flush_rbt在崩溃恢复阶段在flush list上建立的红黑数,用于将apply redo后的page快速的插入到flush list上,以保证其有序buf_pool_t::unzip_LRU压缩表上解压后的page被存储到unzip_LRU。 buf_block_t::frame存储解压后的数据,buf_block_t::page->zip.data指向原始压缩数据。buf_pool_t::zip_free[BUF_BUDDY_SIZES_MAX]用于管理压缩页产生的空闲碎片page。压缩页占用的内存采用buddy allocator算法进行分配。page_hash查找。LRU只是用于淘汰。一份block。指针保存在hash和lru上当另外一个线程也想请求相同page时,首先根据space id 和page no找到对应的buffer pool instance。然后查询page hash。如果看到page hash中已经有对应的block了,说明page已经或正在被读入buffer pool,如果io_fix为BUF_IO_READ,说明正在进行IO,就通过加X锁的方式做一次sync(buf_wait_for_read),确保IO完成。如果没有则表示需要从磁盘读取。在读盘前首先我们需要为即将读入内存的数据页分配一个空闲的block。当free list上存在空闲的block时,可以直接从free list上摘取;如果没有,就需要从unzip_lru 或者 lru上驱逐page。先unzip lru。再lru是否有可替换page,直接释放,否则可能是脏页多,再线程在LRU上做脏页刷新。后台线程也会定期做脏页刷新。一个流程对buffer的操作步骤:索引:聚簇索引见上二级索引 change buffer对非唯一二级索引页,delete_mark,delete,insert顺序插入缓冲区,合并减少随机IO。物理:ibdata第4个page B+ Tree(key:spaceid,offset,counter)内存:ibuf,B+树内容: space,offset,发生change的数据写入:1 不会导致空page:delete时只有一条记录 拒绝2 不会导致分裂,insert时检查IBUF BITMAP标识剩余空间大小,超出触发merge 拒绝merge:(在很多情况都需要把ibuf里的页进行合并)1.辅助索引页被读取到缓冲池时2.插入时预估page no空间不足3.ibuf空间不足4.插入ibuf可能产生ibuf Tree的索引分裂5.Master (IDLE ,ACTIVE,SHUTDOWN)……Purge操作和insert在ibuf并发问题在purge模式下,用ibuf同时将watch插入到hash table中,如果都在内存里,会给同一份page加锁,没问题,但是要两个线程都写入ibuf_insert时,是没办法控制顺序的(本来就允许这种无序,因为非唯一)。所以需要一个进入后,另一个就放弃,不能都写入ibuf。在purge模式下,用ibuf同时将watch插入到hash table中,insert就不会再放入ibuf中了其他读取清除这个buf.功能——事务A undologC(一个事务中间状态可见性) MVCCI (多个事物之间可见性/操作不干扰) MVCCD redolog内存总体流程undolog物理:回滚段,rseg0在ibdata第6个page,132临时表空间,33128独立表空间或ibdata,存储在ibdata,临时表空间或单独表空间。每个表空间可以包含若干个段。每个段有1024个控制页slot和历史表。每个slot对应一个undo log对象,有一个undo log header.内存:全局trx_sys->rseg_array。每个事务trx->rsegs内容: 逻辑日志 Insert undo日志记录插入的唯一键值的len和value。 Update undo日志在insert undo基础上,同时记录了旧记录事务id,以及被更新字段的旧数据写入 入口函数:btr_cur_ins_lock_and_undoa) 从chached_list或分配一个空闲slot创建undo页b) 顺序写undo log header和记录c) 在事务提交阶段,加入到history list或释放【见事务提交】Undo log的写入在一个单独的mtr中,受redo log的保护,先讲一个子事务mtr。Mtr是InnoDB对物理数据文件操作的最小原子单元,保证持久性,用于管理对Page加锁、修改、释放、以及日志提交到公共buffer等工作。 开启时初始化m_impl,比如mlog用于存储redo log记录 提交时需要将本地产生的日志拷贝到公共缓冲区,将修改的脏页放到flush list上。回滚: 入口函数:row_indo_step 解析老版本记录,做逆向操作事务提交时undolog 入口函数:trx_commit_low–>trx_write_serialisation_history 事务提交整体流程,注意看下大概顺序,后面会具体讲写完redo就可以提交了。生成事务no。如果有update类的undo日志 。加入到purge_queue(清理垃圾),history链表(维护历史版本)子事务提交。Redo log写到公共缓存释放MVCC的readview;insert的undo日志释放(可cache重用,否则全部释放包括page页)刷日志在该函数中,需要将该事务包含的Undo都设置为完成状态,先设置insert undo,再设置update undo(trx_undo_set_state_at_finish),完成状态包含三种: 如果当前的undo log只占一个page,且占用的header page大小使用不足其3/4时(TRX_UNDO_PAGE_REUSE_LIMIT),则状态设置为TRX_UNDO_CACHED,该undo对象会随后加入到undo cache list上; 如果是Insert_undo(undo类型为TRX_UNDO_INSERT),则状态设置为TRX_UNDO_TO_FREE; 如果不满足a和b,则表明该undo可能需要Purge线程去执行清理操作,状态设置为TRX_UNDO_TO_PURGE。对于undate undo需要调用trx_undo_update_cleanup进行清理操作。注意上面只清理了update_undo,insert_undo直到事务释放记录锁、从读写事务链表清除、以及关闭read view后才进行,这里的slot,undo page ,history关系:每个rseg控制页有1024个slot和history。undo page释放后或者移到history list后,就可以把slot清空、undo page转为cache不释放则不动slotpurge:删除(更新数据的真正删除),清除过期undo。入口函数srv_do_purge作用: 对于用户删除的数据,InnoDB并不是立刻删除,而是标记一下,后台线程批量的真正删除。类似的还有InnoDB的二级索引的更新操作,不是直接对索引进行更新,而是标记一下,然后产生一条新的。这个线程就是后台的Purge线程。此外,清除过期的undo,histroy list,指的是undo不需要被用来构建之前的版本,也不需要用来回滚事务。我们先来分析一下Purge Coordinator的流程。启动线程后,会进入一个大的循环,循环的终止条件是数据库关闭。在循环内部,首先是自适应的sleep,然后才会进入核心Purge逻辑。sleep时间与全局历史链表有关系,如果历史链表没有增长,且总数小于5000,则进入sleep,等待事务提交的时候被唤醒(srv_purge_coordinator_suspend)。退出循环后,也就是数据库进入关闭的流程,这个时候就需要依据参数innodb_fast_shutdown来确定在关闭前是否需要把所有记录给清除。接下来,介绍一下核心Purge逻辑。 srv_do_purge1)首先依据当前的系统负载来确定需要使用的Purge线程数(srv_do_purge),即如果压力小,只用一个Purge Cooridinator线程就可以了。如果压力大,就多唤醒几个线程一起做清理记录的操作。如果全局历史链表在增加,或者全局历史链表已经超过innodb_max_purge_lag,则认为压力大,需要增加处理的线程数。如果数据库处于不活跃状态(srv_check_activity),则减少处理的线程数。2)如果历史链表很长,超过innodb_max_purge_lag,则需要重新计算delay时间(不超过innodb_max_purge_lag_delay)。如果计算结果大于0,则在后续的DML中需要先sleep,保证不会太快产生undo(row_mysql_delay_if_needed)。3)从全局视图链表中,克隆最老的readview(快照、拿视图为了拿事务id.undo日志中upadte记了事务id),所有在这个readview开启之前提交的事务所产生的undo都被认为是可以清理的。克隆之后,还需要把最老视图的创建者的id加入到view->descriptors中,因为这个事务修改产生的undo,暂时还不能删除(read_view_purge_open)。4)从undo segment的最小堆中(堆存放每个段未被purge的最老的undo页),找出最早提交事务的undolog(trx_purge_get_rseg_with_min_trx_id),如果undolog标记过delete_mark(表示有记录删除操作),则把先关undopage信息暂存在purge_sys_t中(trx_purge_read_undo_rec)。5)依据purge_sys_t中的信息,读取出相应的undo,同时把相关信息加入到任务队列中。同时更新扫描过的指针,方便后续truncate undolog。6)循环第4步和第5步,直到为空,或者接下到view->low_limit_no,即最老视图创建时已经提交的事务,或者已经解析的page数量超过innodb_purge_batch_size。(把delete和Undopage分别存放,detele给工作线程删除)7)把所有的任务都放入队列后,就可以通知所有Purge Worker线程(如果有的话)去执行记录删除操作了。删除记录的核心逻辑在函数row_purge_record_func中。有两种情况,一种是数据记录被删除了,那么需要删除所有的聚集索引和二级索引(row_purge_del_mark),另外一种是二级索引被更新了(总是先删除+插入新记录),所以需要去执行清理操作。8)在所有提交的任务都已经被执行完后,就可以调用函数trx_purge_truncate去删除update undo(insert undo在事务提交后就被清理了)。每个undo segment分别清理,从自己的histrory list中取出最早的一个undo,进行truncate(trx_purge_truncate_rseg_history)。truncate中,最终会调用fseg_free_page来清理磁盘上的空间。MVCCundo+read view 写时并发读ReadView::id 创建该视图的事务ID;m_ids 创建ReadView时,活跃的读写事务ID数组,有序存储;记录trx_id不在m_ids中可见m_low_limit_id 当前最大事务ID;记录rx_id>=ReadView::m_low_limit_id,则说明该事务是创建readview之后开启的,不可见Rem_up_limit_id ;m_ids 集合中的最小值;记录trx_id< m_up_limit_id该事务在创建ReadView时已经提交了,可见二级索引回聚簇索引中。若不可见,则通过undo构建老版本记录。redolog物理:log文件,ib_logfile 覆盖写内存:log buffer log_sys(记了日志在磁盘和内存中用到的信息,比如总大小,一些需要刷盘的阈值等)内容:记录物理位置spaceid,page,offset上要操作的逻辑日志写入 每个子事务的操作都会写入log(mtr.m_impl.m_log中) mlog_open_and_write_index=》memcpy=》mlog_close提交 子事务提交写入缓冲区 将本次的表空间和文件信息加入到一个内存链表上 (去除恢复中对数据字典的依赖) 提交时,准备log内容,提交到公共buffer中,并将对应的脏页加到flush list上 Step 1: mtr_t::Command::prepare_write() 1.若当前mtr的模式为MTR_LOG_NO_REDO 或者MTR_LOG_NONE,则获取log_sys->mutex,从函数返回 2.若当前要写入的redo log记录的大小超过log buffer的二分之一,则去扩大log buffer,大小约为原来的两倍。 3.持有log_sys->mutex 4.调用函数log_margin_checkpoint_age检查本次写入:如果本次产生的redo log size的两倍超过redo log文件capacity,则打印一条错误信息;若本次写入可能覆盖检查点,还需要去强制做一次同步chekpoint 5.检查本次修改的表空间是否是上次checkpoint后第一次修改(fil_names_write_if_was_clean) 如果space->max_lsn = 0,表示自上次checkpoint后第一次修改该表空间: a. 修改space->max_lsn为当前log_sys->lsn; b. 调用fil_names_dirty_and_write将该tablespace加入到fil_system->named_spaces链表上; c. 调用fil_names_write写入一条类型为MLOG_FILE_NAME的日志,写入类型、spaceid, page no(0)、文件路径长度、以及文件路径名。 在mtr日志末尾追加一个字节的MLOG_MULTI_REC_END类型的标记,表示这是多个日志类型的mtr。 如果不是从上一次checkpoint后第一次修改该表,则根据mtr中log的个数,或标识日志头最高位为MLOG_SINGLE_REC_FLAG,或附加一个1字节的MLOG_MULTI_REC_END日志。 Step 2: 拷贝 若日志不够,log_wait_for_space_after_reserving Step 3:如果本次修改产生了脏页,获取log_sys->log_flush_order_mutex,随后释放log_sys->mutex。 Step 4. 将当前Mtr修改的脏页加入到flush list上,脏页上记录的lsn为当前mtr写入的结束点lsn。基于上述加锁逻辑,能够保证flush list上的脏页总是以LSN排序。 Step 5. 释放log_sys->log_flush_order_mutex锁 Step 6. 释放当前mtr持有的锁(主要是page latch)及分配的内存,mtr完成提交。刷盘 整个事务的提交 trx_commit. 参数innodb_flush_log_at_trx_commit当设置该值为1时,每次事务提交都要做一次fsync,这是最安全的配置,即使宕机也不会丢失事务 当设置为2时,则在事务提交时只做write操作,只保证写到系统的page cache,因此实例crash不会丢失事务,但宕机则可能丢失事务 当设置为0时,事务提交不会触发redo写操作,而是留给后台线程每秒一次的刷盘操作,因此实例crash将最多丢失1秒钟内的事务,写入一条MLOG_FILE_NAME刷脏 刷脏会在以下情形被触发 启动和关闭时会唤醒刷脏线程 redo log可能覆盖写时,调用单独线程把未提交LSN对应的redo log刷盘 LRU LIST在未能自己释放时,先自己刷脏一页,不行再 唤醒刷脏线程刷脏线程innodb_page_cleaners设置为4,那么就是一个协调线程(本身也是工作线程),加3个工作线程,工作方式为生产者-消费者。工作队列长度为buffer pool instance的个数,使用一个全局slot数组表示。buf_flush_page_cleaner_coordinator协调线程主循环主线程以最多1s的间隔或者收到buf_flush_event事件就会触发进行一轮的刷脏。协调线程首先会调用pc_request()函数,这个函数的作用就是为每个slot代表的缓冲池实例计算要刷脏多少页,然后把每个slot的state设置PAGE_CLEANER_STATE_REQUESTED, 唤醒等待的工作线程。由于协调线程也会和工作线程一样做具体的刷脏操作,所以它在唤醒工作线程之后,会调用pc_flush_slot(),和其它的工作线程并行去做刷脏页操作。一但它做完自己的刷脏操作,就会调用pc_wait_finished()等待所有的工作线程完成刷脏操作。完成这一轮的刷脏之后,协调线程会收集一些统计信息,比如这轮刷脏所用的时间,以及对LRU和flush_list队列刷脏的页数等。然后会根据当前的负载计算应该sleep的时间、以及下次刷脏的页数,为下一轮的刷脏做准备。buf_flush_page_cleaner_worker工作线程主循环启动后就等在page_cleaner_t的is_requested事件上,一旦协调线程通过is_requested唤醒所有等待的工作线程,工作线程就调用pc_flush_slot()函数去完成刷脏动作。pc_flush_slot:先找到一个空间的slot,page_cleaner->n_slots_requested–; // 表明这个slot开始被处理,将未被处理的slot数减1 page_cleaner->n_slots_flushing++; //这个slot开始刷脏,将flushing加1 slot->state = PAGE_CLEANER_STATE_FLUSHING;刷LRU,FLUSH LISTpage_cleaner->n_slots_flushing–; // 刷脏工作线程完成次轮刷脏后,将flushing减1 page_cleaner->n_slots_finished++; //刷脏工作线程完成次轮刷脏后,将完成的slot加一 slot->state = PAGE_CLEANER_STATE_FINISHED; // 设置此slot的状态为FINISHED若是最后一个,os_event_set(page_cleaner->is_finished)pc_wait_finished:os_event_wait(page_cleaner->is_finished);统计等每次刷多少srv_max_buf_pool_modified_pct决定2. log_checkpoint在主线程,合并insertbuffer,等需要redo log空间的以及事务提交后等都会调用log_free_check_wait =》调用单独的log checkpoint进程 这里如果page cleaner在ACTIVE唤醒buf_flush_event(buf_flush_page_coordinator_thread监听),否则自己buf_flush_sync_all_buf_pools。redo checkpoint的入口函数为log_checkpoint,其执行流程如下:Step1. 持有log_sys->mutex锁,并获取buffer pool的flush list链表尾的block上的lsn,这个lsn是buffer pool中未写入数据文件的最老lsn,在该lsn之前的数据都保证已经写入了磁盘。Step 2. 调用函数fil_names_clear扫描fil_system->named_spaces上的fil_space_t对象,如果表空间fil_space_t->max_lsn小于当前准备做checkpoint的Lsn,则从链表上移除并将max_lsn重置为0。同时为每个被修改的表空间构建MLOG_FILE_NAME类型的redo记录。(这一步未来可能会移除,只要跟踪第一次修改该表空间的min_lsn,并且min_lsn大于当前checkpoint的lsn,就可以忽略调用fil_names_write)写入一个MLOG_CHECKPOINT类型的CHECKPOINT REDO记录,并记入当前的checkpoint LSNStep3 . fsync redo log到当前的lsn 调用fil_flush_file_spaces(关闭时srv_shutdown_log,崩溃恢复……)Step4. 写入checkpoint信息函数:log_write_checkpoint_info –> log_group_checkpointcheckpoint信息被写入到了第一个iblogfile的头部,但写入的文件偏移位置比较有意思,当log_sys->next_checkpoint_no为奇数时,写入到LOG_CHECKPOINT_2(3 *512字节)位置,为偶数时,写入到LOG_CHECKPOINT_1(512字节)位置。在crash recover重启时,会读取记录在checkpoint中的lsn信息,然后从该lsn开始扫描redo日志。Checkpoint操作由异步IO线程执行写入操作,当完成写入后,会调用函数log_io_complete执行如下操作:fsync 被修改的redo log文件更新相关变量:log_sys->next_checkpoint_no++log_sys->last_checkpoint_lsn = log_sys->next_checkpoint_lsn释放log_sys->checkpoint_lock锁崩溃恢复1.从第一个iblogfile的头部定位要扫描的LSN(数据落盘点) 2.扫描redo log 1) 第一次redo log的扫描,主要是查找MLOG_CHECKPOINT,不进行redo log的解析, 2) 第二次扫描是在第一次找到MLOG_CHECKPOINT(获取表和路径)基础之上进行的,该次扫描会把redo log解析到哈希表中,如果扫描完整个文件,哈希表还没有被填满,则不需要第三次扫描,直接进行recovery就结束 3)第二次扫描把哈希表填满后,还有redo log剩余,则需要循环进行扫描,哈希表满后立即进行recovery,直到所有的redo log被apply完为止。 3.具体redo log的恢复 MLOG_UNDO_HDR_CREATE:解析事务ID,为其重建undo log头; MLOG_REC_INSERT 解析出索引信息(mlog_parse_index)和记录信息( page_cur_parse_insert_rec)等 在完成修复page后,需要将脏页加入到buffer pool的flush list上;查找红黑树找到合适的插入位置 MLOG_FILE_NAME用于记录在checkpoint之后,所有被修改过的信息(space, filepath); MLOG_CHECKPOINT用于标志MLOG_FILE_NAME的结束。在恢复过程中,只需要打开这些ibd文件即可,当然由于space和filepath的对应关系通过redo存了下来,恢复的时候也不再依赖数据字典。在恢复数据页的过程中不产生新的redo 日志;二次写 redo会记spaceid,pageno,偏移量内的逻辑日志只记录:’这是一个插入操作’和’这行数据的内容‘。 为了省地方。但是这样就有个问题。在redo log恢复执行时,如果执行逻辑时一半断电了,比如已经某地址加了一个记录数。但又断电了,重新灰度执行又会执行一遍。就会在地址后又加一条。写坏了。这就需要在脏页落盘时采取二次写。数据写入ibd前先顺序写入ibdata.在崩溃恢复时,先检验checksum.不合法载入ibdata的数据。Redo为了保证原子性,要求一块一写。不够的话要先读旧的然后改然后写。以512字节(最小扇区)对其方式写入,不需要二次写。设置一个值innodb_log_write_ahead_size,不需要这个过程,超过该值补0到一块直接插入server与innodb的事务保证server和引擎层事务的界限 1.开启事务。server只会调用引擎层。 server层如果不以命令,是不会显示开启事务的。在SQLCOM_BEGIN等命令会调用trans_begin 分布式事务会调trans_begin(跟踪下) 证明是正确的,在外层trans_begin并没有调用。并不研究了 提交会在server层调用各个引擎的事务提交。 下面说下innodb层的trx 2.提交事务。根据是否开启binlog和是否有多个引擎执行不同。比如开了Binlog且使用了事务引擎,用Mysql_bin_log的两阶段和组提交。如果没有用事务引擎,直接记log等就可以 3.事务回滚:分为真正xa回滚还是普通回滚。普通回滚调用引擎层回滚 4.崩溃恢复:没有server层的崩溃恢复开启 分配回滚段,获取事务id,加入事务链表提交 入口: MYSQL_BIN_LOG::commit,如果是分布式事务,用xa,两阶段。prepare和commit。我们先研究普通的提交。XA不作为重点。但是由于server层和Innodb层两个日志,需要保证顺序,也按照XA的两阶段设计。也叫内部xa 1) xa两阶段 Prepare undo log写入xid,设置状态为PREPARED Commit Flush Stage:由leader依次为别的线程对flush redo log到LSN,再写binlog文件 Sync Stage:如果sync_binlog计数超过配置值,以组为维度文件fsync Commit Stage:队列中的事务依次进行innodb commit, 修改undo头的状态为完成;并释放事务锁,清理读写事务链表、readview等一系列操作。2) 原因 两阶段是为了保证binlog和redo log一致性。server和备库用binlog来恢复同步。innodb用undo和redo恢复。 1落undo 2flush redo 3 flush binlog 4fsync binlog 5fsync redo [ps:sync可能只是内核缓冲放入磁盘队列,fsync只保证放入磁盘,都是同步] 2要在3前 因为要先保证redo写了才算是事务执行了,否则无法回滚就记了binlog 4要在5前 提交成功,redo才会落盘成功,如果先redo再binlog, 但是binlog落盘失败,不会回滚,binlog少记录 但是如果按照4到5的顺序,有binlog落盘成功, 如果此时正常,5失败会回滚,按照undo的内容逆着执行一次,binlog记录了重新执行的,可以正常恢复数据; 如果此时异常宕机,无法回滚,就会出问题。binlog已经落盘,redo没有落盘。此时数据同步导致主从不一致。因此加两阶段。在第一阶段在写入xid,undo设置为prepare。如果在宕机恢复时对于prepare中的发现binlog的xid没有被执行,重新执行一遍。 prepare阶段的redo log可以先不写,在commit阶段一起写。只要保证flush redo在flush binlog前就可以 两阶段其实也是undo标识一下binlog和redolog还不一致。3) 组提交:两阶段提交,在并发时无法保证顺序一致,用ordered_commit控制如图在Online backup taken时,那么事务 T1 在备机恢复 MySQL 数据库时,发现 T1 未在存储引擎内提交,那么在恢复时,T1 事务就会被回滚。数据不一致。如果关闭binlog_order_commits。事务各自提交。这时候没有设置不写redo log。不能保证Innodb commit顺序和Binlog写入顺序一直,但是不会影响数据一致性。只是物理备份数据不一致。但是依赖于事务页上记录binlog恢复的,比如xtrabackup就会发生备份数据不一致的情况。回滚两阶段:正常应该根据undo回滚,但看到undo为iprepare且binlog有,就不回滚当由于各种原因(例如死锁,或者显式ROLLBACK)需要将事务回滚时,ha_rollback_trans=》ha_rollback_low,进而调用InnoDB函数trx_rollback_for_mysql来回滚事务。对于innodb回滚的方式是提取undo日志,做逆向操作。提交失败会回滚。走的非xa,调用trx_rollback_for_mysql。原来一直纠结binlog会不会删除。。。跟踪了好久也没看出来,其实是undo中的在提交时重新写一下binlog。这里在子事务里会介绍。分布式分布式事务主从复制三种日志模式1.基于行的复制 row优点:符合幂等性,高度保障数据一致。缺点:数据量大2.基于语句的复制 statement优点:日志量少缺点:特定功能函数导致主从数据不一致,重复执行时无法保证幂等3.混合类型的复制 mixed (默认语句,语句无法精准复制,则基于行)主从同步过程其中1. Slave 上面的IO线程连接上 Master,并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容;重放过程和master一样,也redologGTID MySQL 5.6引入全局事务ID的首要目的,是保证Slave在复制的时候不会重复执行相同的事务操作;其次,是用全局事务IDs代替由文件名和物理偏移量组成的复制位点,定位Slave需要复制的binlog内容。因此,MySQL必须在写binlog时记录每个事务的全局GTID,保证Master / Slave可以根据这些GTID忽略或者执行相应的事务。GTID的组成部分: 前面是server_uuid:后面是一个串行号 例如:server_uuid:sequence number 7800a22c-95ae-11e4-983d-080027de205a:10 UUID:每个mysql实例的唯一ID,由于会传递到slave,所以也可以理解为源ID。 Sequence number:在每台MySQL服务器上都是从1开始自增长的串行,一个数值对应一个事务。 当事务提交时,不管是STATEMENT还是ROW格式的binlog,都会添加一个XID_EVENT事件作为事务的结束。该事件记录了该事务的id。同步方案 同步复制 所谓的同步复制,意思是master的变化,必须等待slave-1,slave-2,…,slave-n完成后才能返回。 异步复制 master只需要完成自己的数据库操作即可。至于slaves是否收到二进制日志,是否完成操作,不用关心 半同步复制 master只保证slaves中的一个操作成功,就返回,其他slave不管。master既要负责写操作,还的维护N个线程,负担会很重。可以这样,slave-1是master的从,slave-1又是slave-2,slave-3,…的主,同时slave-1不再负责select。slave-1将master的复制线程的负担,转移到自己的身上。这就是所谓的多级复制的概念。 这里会有个一致性的问题。 开始提交事务 =>write binlog => sync binlog => engine commit => send events =>返回 send_events失败会导致master有slave没有 开始提交事务 =>write binlog => sync binlog => send events => engine commit =>返回 send_events失败若sync binlog未同步,导致XA不会重做,slave领先并行复制定期checkout-point将队列中执行结束的删除。记录checkpoint后每个worker是否执行过的bitmap。崩溃恢复时执行Bitmap未执行的部分。按db分粒度大可以换成table扩展性当主库支撑不了。水平扩展。拆表。可靠性需要proxy保证一致性同步策略影响。XA分为外部和内部。对于外部。要应用程序或proxy作为协调者。(二阶段提交协调者判断所有prepare后commit)。对于内部,binlog控制。proxy功能共享式的缓存读写分离路由可靠性的保证,主从切换,故障发现与定位XA一致性的实现过滤加注释例如:https://github.com/mariadb-co… ...

April 9, 2019 · 2 min · jiezi

[SeqNo] - 一款基于MySQL的分布式自增序列发号器。

通常地,在一些业务中我们需要像MySQL的自增列一样来生成与存储数据。比如餐厅取餐号,排队号码等。SeqNo 就是基于以上场景而诞生,拥有以下特性:分布式。 基于分布式锁实现。自设步长。定期归零。基于时间格式。严格自增。https://github.com/imiskolee/…

April 9, 2019 · 1 min · jiezi

sql数据文件导入到mysql

使用命令行将sql文件导入mysql][1]

April 9, 2019 · 1 min · jiezi

带你了解数据库中group by的用法

前言本章主要介绍数据库中group by的用法,也是我们在使用数据库时非常基础的一个知识点。并且也会涉及Join的使用,关于Join的用法,可以看我写的上一篇文章:带你了解数据库中JOIN的用法 如有错误还请大家及时指出以下都是采用mysql数据库Group By概念Group By语句从英文的字面意义上理解就是“根据(by)一定的规则进行分组(Group)”。作用:通过一定的规则将一个数据集划分成若干个小的区域,然后针对若干个小区域进行数据处理。注意:group by 是先排序后分组!语法SELECT expression1, expression2, … expression_n, aggregate_function (aggregate_expression)FROM tables[WHERE conditions]GROUP BY expression1, expression2, … expression_n[ORDER BY expression [ ASC | DESC ]];语法说明expression1,expression2,… expression_n表达式未封装在聚合函数中,必须包含在SQL语句末尾的GROUP BY子句中。aggregate_function这是一个聚合函数,例如SUM,COUNT,MIN,MAX或AVG函数。aggregate_expression这是将使用aggregate_function的列或表达式。tables您希望从中检索记录的表。FROM子句中必须至少列出一个表。where可选的。这些是要选择的记录必须满足的条件。ORDER BY表达式可选的。用于对结果集中的记录进行排序的表达式。如果提供了多个表达式,则值应以逗号分隔。ASC可选的。ASC按表达式按升序对结果集进行排序。如果没有修饰符是提供者,则这是默认行为。DESC可选的。DESC按表达式按降序对结果集进行排序。常用聚合函数count() 计数sum() 求和avg() 平均数max() 最大值min() 最小值举例我们以下面两张表举例:学生表(Student)IDStudentNameStudentClassID1小明12小红23小兰34小吕25小梓1班级表(Class)ClassIDClassName1软件一班2软件二班3软件三班计算每个班都有多少学生?SELECT cl.ClassName,COUNT(stu.StudentClassID) AS studentNumFROM student stu LEFT JOIN class cl ON stu.StudentClassID = cl.ClassIDGROUP BY cl.ClassName;计算结果:ClassNameStudentName软件一班2软件二班2软件三班1HAVINGHAVING语句通常与GROUP BY语句联合使用,用来过滤由GROUP BY语句返回的记录集。HAVING语句的存在弥补了WHERE关键字不能与聚合函数联合使用的不足。通俗的说:WHERE过滤行,HAVING过滤组语法SELECT expression1, expression2, … expression_n, aggregate_function (aggregate_expression)FROM tables[WHERE conditions]GROUP BY expression1, expression2, … expression_n语法说明HAVING condition这是另一个条件,仅应用于聚合结果以限制返回行的组。只有那些条件评估为TRUE的组才会包含在结果集中。举例采用上面两张数据表查询学生人数大于2人的班级?SELECT cl.ClassName,COUNT(stu.StudentClassID) AS studentNumFROM student stu LEFT JOIN class cl ON stu.StudentClassID = cl.ClassIDGROUP BY cl.ClassName HAVING COUNT(stu.StudentClassID)>=2;计算结果:ClassNameStudentName软件一班2软件二班2小结当group by 与聚合函数配合使用时,功能为分组后计算当group by 与 having配合使用时,功能为分组后过滤,获得满足条件的分组的返回结果。having与where区别:where过滤行,having过滤组文末本章节主要简单介绍了数据库中group by的用法,并没有详细的展开讲解,相信大家只要把基础打扎实,再复杂的查询也可以通过分而治之的思想来解决。欢迎关注公众号:Coder编程获取最新原创技术文章和相关免费学习资料,随时随地学习技术知识!推荐阅读带你了解数据库中JOIN的用法 一篇让你理解进程与线程的区别与联系一篇带你读懂TCP之“滑动窗口”协议 参考文章:https://www.techonthenet.com/…https://www.techonthenet.com/...https://www.cnblogs.com/8335I...Github个人主页目录Gitee个人主页目录欢迎大家关注并Star ...

April 9, 2019 · 1 min · jiezi

MySQL 复制 - 性能与扩展性的基石 3:常见问题及解决方案

主备复制过程中有很大可能会出现各种问题,接下来我们就讨论一些比较普遍的问题,以及当遇到这些问题时,如何解决或者预防问题发生。1 数据损坏或丢失问题描述:服务器崩溃、断电、磁盘损坏、内存或网络错误等问题,导致数据损坏或丢失。问题原因:非正常关机导致没有把数据及时的写入硬盘。这种问题,一般可以分为几种情况导致:1.1 主库意外关闭问题未发生,避免方案:设置主库的 sync_binlog 选项为 1。此选项表示 MySQL 是否控制 binlog 的刷新。当设置为 1 时,表示每次事务提交,MySQL 都会把 binlog 刷下去,是最安全,性能损耗也最大的设置。问题已发生,解决方案:指定备库从下一个二进制日志的开头重新读日志。但是一些日志事件将永久性丢失。可以使用 Percona Toolkit 中的 pt-table-checksum 工具来检查主备一致性,以便于修复。1.2 备库意外关闭备库意外关闭重启时,会去读 master.info 文件以找到上次停止复制的位置。但是在意外关闭的情况下,这个文件存储的信息可能是错误的。此外,备库也可能会尝试重新执行一些二进制文件,这可能会导致唯一索引错误。我们可以通过 Percona Toolkit 中的 pt-slave-restart 工具,帮助备库重新执行日志文件。如果使用的是 InnoDB 表,可以在重启后观察 MySQL 的错误日志。InnoDB 在恢复过程中会打印出恢复点的二进制日志坐标,可以使用这个值来决定备库指向主库的偏移量。1.3 主库二进制日志损坏如果主库上的二进制日志损坏,除了忽略损坏的位置外,别无选择。在忽略存货位置后,我们可以通过 FLUSH LOGS 命令在主库开始一个新的日志文件,然后将备库指向该文件的开始位置。1.4 备库中继日志损坏如果主库上的日志是完好的,有两种解决方案:1) 手工处理。找到 master binlog 日志的 pos 点,然后重新同步。2) 自动处理。mysql5.5 考虑到 slave 宕机中继日志损坏这一问题,只要在 slave 的的配置文件 my.cnf 里增加一个参数 relay_log_recovery=1 即可。1.5 二进制日志与 InnoDB 事务日志不同步由于各种各样的原因,MySQL 的复制碰到服务器崩溃、断电、磁盘损坏、内存或网络错误时,很难恢复当时丢失的数据。几乎都需要从某个点开始重启复制。2 未定义的服务器 ID如果没有再 my.cnf 里定义服务器 ID,虽然可以通过 CHANGE MASTER TO 来设置备库,但在启动复制时会遇到:mysql> START SLAVE;ERROR 1200 (HY000): The server us bit configured as slave; fix in config file or with CHANGE MASTER TO这个报错可能会让人困惑。因为我们可能已经通过 CHANGE MASTER TO 设置了备库,并且通过 SHOW MASTER STATUS 也确认了,为什么还会有这样的报错呢?我们通过 SELECT @@server_id 可以获得一个值,要注意的是,这个值只是默认值,我们必须为备库显式地设置服务器 ID。也就是在 my.cnf 里显示的设置服务器 ID。3 对未复制数据的依赖性如果在主库上有备库上不存在的数据库或数据表,复制就很容易中断,反之亦然。对于前者,假设在主库上有一个 single_master 表,备库没有。在主库上对此表进行操作后,备库在尝试回放这些操作时就会出现问题,导致复制中断。对于后者,假设备库上有一个 single_slave 表,主库没有。在主库上执行创建 single_slave 表的语句时,备库在回放该建表语句时就会出现问题。对于此问题,我们能做的就是做好预防:主备切换时,尽量在切换后对比数据,查清楚是否有不一致的表或库。一定不要在备库执行写操作。4 丢失的临时表临时表和基于语句的复制方式不相容。如果备库崩溃或者正常关闭,任何复制线程拥有的临时表都会丢失。重启备库后,所有依赖于该临时表的语句都会失败。复制时出现找不到临时表的异常时,可以做:直接跳过错误,或者手动地创建一个名字和结构相同的表来代替消失的的临时表。临时表的特性:只对创建临时表的连接可见。不会和其他拥有相同名字的临时表的连接起冲突;随着连接关闭而消失,无须显式的移除它们。4.1 更好使用临时表的方式保留一个专用的数据库,在其中创建持久表,把它们作为伪临时表,以模拟临时表特性。只需要通过 CONNETCTION_ID() 的返回值,给临时表创建唯一的名字。伪临时表的优劣势优势:更容易调试应用程序。可以通过别的连接来查看应用正在维护的数据;劣势:比临时表多一些开销。创建较慢伪临时表会较慢,因为表的 .frm 文件需要刷新到磁盘。5 InnoDB 加锁读导致主备数据不一致使用共享锁,串行化更新,保证备库复制时数据一致。某些情况下,加锁读可以防止混乱。假设有两张表:tab1 没有数据,tab2 只有一行数据,值为 99。此时,有两个事务更新数据。事务 1 将 tab2 的数据插入到 tab1,事务 2 更新 tab2。事务 1 使用获取 tab2 数据时,加入共享锁,并插入 tab1;同时,事务 2 更新 tab2 数据时,由于写操作的排它锁机制,无法获取 tab2 的锁,等待;事务 1 插入数据后,删除共享锁,提交事务,写入 binlog(此时 tab1 和 tab2 的记录值 都是 99);事务 2 获取到锁,更新数据,提交事务,写入 binlog(此时 tab1 的记录值为 99,tab2 的记录值为 100)。上述过程中,第二步非常重要。事务 2 尝试去更新 tab2 表,这需要在更新的行上加排他锁(写锁)。排他锁与其他锁不相容,包括事务 1 在行记录上加的共享锁。因此事务 2 需要等待事务 1 完成。备库在根据 binlog 进行复制时,会按同样的顺序先执行事务 1,再执行事务 2。主备数据一致。同样的过程,如果事务 1 在第一步时没有加共享锁,流程就变成:事务 1 无锁读取 tab2 数据,并插入 tab1(此时 tab1 和 tab2 的记录值 都是 99);同时,事务 2 更新 tab2 数据,先与事务 1 提交事务,写入 binlog(此时 tab1 的记录值为 99,tab2 的记录值为 100);事务 1 提交事务,写入 binlog(此时记录值无变化);mysqldump –single-transaction –all-databases –master-data=1 –host=server1 | mysql –host=server2要注意的是,上述过程中,事务 2 先提交,先写入 binlog。在备库复制时,同样先执行事务 2,将 tab2 的记录值更新为 100。然后执行事务 1,读取 tab2 数据,插入 tab1,所以最终的结果是,tab1 的记录值和 tab2 的记录值都是 100。很明显,数据和主库有差异。建议在大多数情况下将 innodb_unsafe_for_binlog 的值设置为 0。基于行的复制由于记录了数据的变化而非语句,因此不会存在这个问题。6 复制延迟过大产生延迟的两种方式突然产生延迟,然后再跟上;稳定的延迟增大前者通常是由于一条执行时间过长的 SQL 导致,而后者即使在没有慢语句也会出现。对于前者,我们可以通过备库上的慢查询日志来进行优化。在备库上开启 log_slow_slave_statement 选项,可以在慢查询日志中记录复制线程执行的语句。而对于后者,没有针对性的解决方案,只能通过各种方式提高备库的复制效率。而当我们想去对备库做优化时,会发现,除了购买更快的磁盘和 CPU,并没有太多的调优空间。只能通过 MySQL 选项禁止某些额外的工作以减少备库的复制。可以通过下面几种方式:使用 InnoDB 引擎时,设置 innodb_flush_log_at_trx_commit 值为 2,来使备库不要频繁的刷新磁盘,以提高事务提交效率。禁止二进制日志记录。把 innodb_locks_unsafe_for_binlog 设置为 1,并把 MyISAM 的 delay_key_write 设置为 ALL。要注意的是,这些设置是以安全换取速度,在将备库提升为主库时,记得把这些选项设置回安全的值。拆分效率较低的复制 SQL,分离复杂语句中的 SELECT 和 UPDATE 语句,降低复制消耗,提高效率。总结复制问题要分清楚是 master 的问题,还是 slave 的问题。master 问题找 binlog,slave 问题找 relaylog。 ...

April 8, 2019 · 2 min · jiezi

连接 MySQL 8.x 版本报错解决

在使用DBeaver连接 MySQL 8.x 版本时遇见了两个问题,通过查询找到了解决方案,记录一下:问题问题一Public Key Retrieval is not allowed解决在驱动属性里设置 allowPublicKeyRetrieval 的值为 true问题二The server time zone value ‘Öйú±ê׼ʱ¼ä’ is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utili解决通过上面的报错信息可以发现是时区的问题,因此只要把时区改为当前系统时区即可。使用root用户登录mysql,然后执行以下命令:show variables like ‘%time_zone%’;set global time_zone=’+8:00’; 设置为北京时区

April 8, 2019 · 1 min · jiezi

CentOS7-MySQL排坑历程

PS:如果觉得文章有什么地方写错了,哪里写得不好,或者有什么建议,欢迎指点。一、报错及起因今天在 CentOS7 安装了 mysql5.7,然后为了测试数据库环境是否配置成功,便写了个基于 mybatis+Spring 的 java web 程序连接操作 mysql 数据库,于是就一些发生了令人感到很烦的报错和故事:当涉及到关于数据库的操作如查询、插入等操作时,首先浏览器访问会很慢,进度条一直旋转,然后页面会报 500 错误: org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exception 。然而我在 CentOS7 服务端和 Windows 本地的 Navicat 连接 mysql 都没问题。。。二、排错历程1.检查 sql 语句看着这似乎是 mybatis 引起的错误,所有先检查 mapper 的 xml 中 sql 语句是否有错误,例如参数的格式转化、resultType 为 JavaBean、resultMap 需要定义、JavaBean 和 dao 的引入等。检查中并没有发现什么问题,而且错误仍然存在。2. MySql Host is blocked because of many connection errors; unblock with ‘mysqladmin flush-hosts’ 报错原因分析:查看 tomcat 的日志文件,发现在报错开始部分出现了这个错误。经过查询,发现这个错误的原因是:同一个 ip 在短时间内产生太多(超过 mysql 数据库 max_connection_errors 的最大值)中断的数据库连接而导致的阻塞。解决方法:进入 CentOS7 服务器:方法一:提高允许的max_connection_errors数量(治标不治本):进入Mysql数据库查看max_connection_errors: show variables like ‘%max_connection_errors%’;修改max_connection_errors的数量为1000: set global max_connect_errors = 1000;查看是否修改成功:show variables like ‘%max_connection_errors%’;方法二:使用 mysqladmin flush-hosts 命令清理一下 hosts 文件:查找 mysqladmin 的路径:whereis mysqladmin执行命令,如:/usr/local/mysql5.5.35/bin/mysqladmin -uroot -pyourpwd flush-hosts注: 方法二清理 hosts 文件,也可以直接进入 mysql 数据库执行命令:flush hosts;3.com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure 异常解决了 hosts 的问题后,在 tomcat 的日志文件,发现在报错开始部分又出现了这个错误: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failureThe last packet successfully received from the server was 32 milliseconds ago. The last packet sent successfully to the server was 32 milliseconds ago. at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at com.mysql.jdbc.Util.handleNewInstance(Util.java:425) at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:990) ……原因分析:表示程序与 MySQL 通讯失败了,即连接失败了。The last packet successfully received from the server was 32 milliseconds ago 表示 mysql 重连,连接丢失。此为数据库连接空闲回收问题,程序打开数据库连接后,等待做数据库操作时,发现连接被 MySQL 关闭掉了。一开始认为是连接等待超时问题。在 mysql 数据库的配置中,其连接的等待时间(wait_timeout)缺省值为 8 小时。在 mysql 中可以查看:mysql﹥ mysql﹥ show global variables like ‘wait_timeout’; +—————+———+ | Variable_name | Value | +—————+———+ | wait_timeout | 28800 | +—————+———+ 1 row in set (0.00 sec) 28800 seconds,也就是8小时。如果在 wait_timeout 秒期间内,数据库连接(java.sql.Connection)一直处于等待状态,mysql 就将该连接关闭。这时,Java 应用的连接池仍然合法地持有该连接的引用。当用该连接来进行数据库操作时,就碰到上述错误。MySQL 连接一次连接需求会经过 6 次「握手」方可成功,任何一次「握手」失败都可能导致连接失败。前三次握手可以简单理解为 TCP 建立连接所必须的三次握手,MySQL 无法控制,更多的受制于 tcp 协议的不同实现,后面三次握手过程超时与 connect_timeout 有关。解决方法:改变数据库参数是最简单的处理方式(修改 /etc/my.cnf 中的 wait_timeout 值),但是需要重启数据库,影响较大。在不修改数据库参数的前提下,可以做已下处理:如果使用的是 jdbc ,在 jdbc url 上添加 autoReconnect=true ,如:dataSource.url=jdbc:mysql://132.231.xx.xxx:3306/shop?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true如果是在Spring中使用DBCP连接池,在定义datasource增加属性 validationQuery 和 testOnBorrow ,如:<bean id=“vrsRankDataSource” class=“org.apache.commons.dbcp.BasicDataSource” destroy-method=“close”> <property name=“driverClassName” value="${dataSource.driver}"/> <property name=“url” value="${dataSource.url}"/> <property name=“username” value="${dataSource.user}"/> <property name=“password” value="${dataSource.password}"/> <property name=“validationQuery” value=“SELECT 1”/> <property name=“testOnBorrow” value=“true”/></bean>如果是在Spring中使用c3p0连接池,则在定义datasource的时候,添加属性 testConnectionOnCheckin 和 testConnectionOnCheckout ,如:<bean name=“cacheCloudDB” class=“com.mchange.v2.c3p0.ComboPooledDataSource”> <property name=“driverClass” value="${dataSource.driver}"/> <property name=“jdbcUrl” value="${dataSource.url}"/> <property name=“user” value="${dataSource.user}"/> <property name=“password” value="${dataSource.password}"/> <property name=“initialPoolSize” value=“10”/> <property name=“maxPoolSize” value=“10”/> <property name=“testConnectionOnCheckin” value=“false”/> <property name=“testConnectionOnCheckout” value=“true”/> <property name=“preferredTestQuery” value=“SELECT 1”/></bean>4. 远程连接 Mysql 太慢问题尝试解决了一下上面的连接超时问题,但是发现并没有什么用,还是会出现上面的问题。于是便怀疑是不是远程连接 Mysql 太慢导致了连接超时?因为我在 CentOS7 服务端和 Windows 本地的 Navicat 连接 mysql 都没问题。在网上查询了下,发现在 mysql 的配置文件 /etc/my.cnf 中增加如下配置参数:# 注意该配置是加在[mysqld]下面[mysqld]skip-name-resolve然后需要重启 mysql 服务。因为根据说明,如果 mysql 主机查询和解析 DNS 会导致缓慢或是有很多客户端主机时会导致连接很慢。同时,请注意在增加该配置参数后,mysql的授权表中的host字段就不能够使用域名而只能够使用ip地址了,因为这是禁止了域名解析的结果。5. 终极解决:Could not create connection to database server. Attempted reconnect 3 times. Giving up 报错原因分析:经过上面的配置后,重新测试程序,就又出现了这个错误。经过查询,发现这是由于 SSL 引起的错误。因为我是在 CentOS7 上新装的 MySQL5.7,默认是没有配置 MySQL SSL 的。而我以前一直使用的 Ubuntu16.04 上的 mysql 数据库,它默认是配置了 MySQL SSL 的,所以我习惯在连接数据库的 jdbc 的 url 里面加上 &useSSL=false ,即使用 SSL 加密。导致我在连接当前 mysql 的时候,一直连接不上。查看 tomcat 日志的报错末尾,会有如下报错:Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Path does not chain with any of the trust anchors at sun.security.ssl.Alerts.getSSLException(Alerts.java:192) at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1946) at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:316) at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:310) at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1639) at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:223) at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1037) at sun.security.ssl.Handshaker.process_record(Handshaker.java:965) at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1064) at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367) at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395) at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379) at com.mysql.jdbc.ExportControlled.transformSocketToSSLSocket(ExportControlled.java:186) … 24 moreCaused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Path does not chain with any of the trust anchors at com.mysql.jdbc.ExportControlled$X509TrustManagerWrapper.checkServerTrusted(ExportControlled.java:302) at sun.security.ssl.AbstractTrustManagerWrapper.checkServerTrusted(SSLContextImpl.java:1091) at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1621) … 32 moreCaused by: java.security.cert.CertPathValidatorException: Path does not chain with any of the trust anchors at sun.security.provider.certpath.PKIXCertPathValidator.validate(PKIXCertPathValidator.java:154) at sun.security.provider.certpath.PKIXCertPathValidator.engineValidate(PKIXCertPathValidator.java:80) at java.security.cert.CertPathValidator.validate(CertPathValidator.java:292) at com.mysql.jdbc.ExportControlled$X509TrustManagerWrapper.checkServerTrusted(ExportControlled.java:295) … 34 more遂恍然大悟。。。解决方法:在配置 JDBC URL 时使用 &useSSL=false ,即不使用 SSL 加密,配置后连接正常:dataSource.url = jdbc:mysql://132.231.xx.xxx:3306/shop?useUnicode=true&characterEncoding=utf8&useSSL=false&autoReconnect=true如果安全性较高的数据建议在 MySQL 端还是把 SSL 配上。三、总结由于 MySQL 的 SSL 问题引起了一连串的问题。由于 SSL 加密的问题,导致程序向 mysql 的连接超时,然后一直向 mysql 发送连接,导致了同一个 ip 在短时间内产生太多中断的数据库连接而导致的阻塞。以后看报错日志,除了看报错的最开始部分,也要看看报错的结尾部分,弄不好会有一些其他的发现的。欢迎您的点赞、收藏和评论!(完) ...

April 8, 2019 · 3 min · jiezi

MySQL笔记-0-Install(Centos7.4)

server版本:Centos7.4MySQL版本:8.0一. 安装前置# 安装Oracle补丁包yum install libaio二. 安装添加MySQL的yum源格式:sudo yum localinstall mysql80-community-release-el7-{version-number}.noarch.rpm例子:sudo yum localinstall mysql80-community-release-el7-2.noarch.rpm检查源添加情况:yum repolist enabled | grep “mysql.-community.“选择版本命令:yum repolist enabled | grep mysql显示:如果不是你想要的版本,则 vim /etc/yum.repo.d/mysql-community.repo,将想安装版本的enabled置为1,并把其他enabled置0,如果有多个1则自动选择版本最高的一个安装命令:sudo yum install mysql-community-server Tip: 如果安装速度过慢,可以清楚缓存并更新镜像源:yum clean allyum makecacheyum update三. 基础操作1. 启动/查询状态/停止sudo service mysqld start/status/stop # Centos6+sudo systemctl start/status/stop mysqld.service # Centos7+2.修改初始密码sudo grep ’temporary password’ /var/log/mysqld.logmysql -uroot -palter user ‘root’@’localhost’ identified by ‘新密码’;# Tip:新密码最少一个大写&一个小写&一个数字&一个特殊符号3、使用前置mysql -uroot -pshow databases;use mysql;show tables;

April 7, 2019 · 1 min · jiezi

centos7下利用yum搭建LAMP环境

由于在阿里云上重置了系统,所以得重新搭建环境,搭建过程中遇到不少坑,在网上搜索之后才得以解决,在这里做个记录,方便日后直接看自己博文。一:安装Apache1、安装Apacheyum -y install httpd2、Apache配置httpd.conf通过命令find / -name httpd.conf 找到Apache配置文件httpd.conf(默认在/etc/httpd/conf/httpd.conf)编辑httpd.conf文件vi /etc/httpd/conf/httpd.conf修改DocumentRoot “/xx/xx/xxx”,项目在哪个目录下,就将"/xx/xx/xxx"改为哪个目录,例如DocumentRoot “/var/www/html” ,同样将<Directory “/xx/xx/xxx”>修改为<Directory “/var/www/html”>。查看是否安装成功(启动Apache,并查看Apache状态) service httpd start service httpd status显示OK说明安装成功配置Apache开机启动项 chkconfig –add httpd //(在服务清单中添加httpd服务) chkconfig httpd on二:安装PHP1、更换RPM源#Centos 5.X:rpm -Uvh http://mirror.webtatic.com/yum/el5/latest.rpm#CentOs 6.x:rpm -Uvh http://mirror.webtatic.com/yum/el6/latest.rpm#CentOs 7.X:rpm -Uvh https://mirror.webtatic.com/yum/el7/epel-release.rpmrpm -Uvh https://mirror.webtatic.com/yum/el7/webtatic-release.rpm2、安装新版本 PHPphp 7.0/7.1/7.2 分别表示为 70w/71w/72wyum install php71w php71w-cli php71w-common php71w-devel php71w-embedded php71w-fpm php71w-gd php71w-mbstring php71w-mysqlnd php71w-opcache php71w-pdo php71w-xml php71w-ldap php71w-mcrypt3、 重新启动相关服务service php-fpm restartservice httpd restart4、检查版本php -v三:安装mysql一般网上给出的资料都是yum install mysqlyum install mysql-serveryum install mysql-devel安装mysql和mysql-devel都成功,但是安装mysql-server失败,查资料发现是CentOS 7 版本将MySQL数据库软件从默认的程序列表中移除,用mariadb代替了。有两种解决方法,一种是安装mariadb,另一种是直接从官网下载mysql-server,看个人喜欢了,本人采取的是第二种。方法一:安装mariadbyum install mariadb-server mariadb mariadb数据库的相关命令是:systemctl start mariadb #启动MariaDBsystemctl stop mariadb #停止MariaDBsystemctl restart mariadb #重启MariaDBsystemctl enable mariadb #设置开机启动所以先启动数据库systemctl start mariadb方法二:官网下载安装mysql-serverwget http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpmrpm -ivh mysql-community-release-el7-5.noarch.rpmyum install mysql-community-server安装成功后重启mysql服务。service mysqld restart初次安装mysql,root账户没有密码。进入mysql,执行命令:set password for ‘root’@’localhost’ =password(‘password’); //括号内写上新密码配置mysql,执行/etc/my.cnf按 i 进入编辑,追加default-character-set =utf8esc退出编辑, :wq 回车保存退出。进入数据库,远程连接设置#把在所有数据库的所有表的所有权限赋值给位于所有IP地址的root用户。mysql>grant all privileges on . to root@’%‘identified by ‘password’;ysql#如果是新用户而不是root,则要先新建用户mysql>create user ‘username’@’%’ identified by ‘password’; 此时就可以进行远程连接了。ok,大功告成。参考链接:centos7 mysql数据库安装和配置 Centos 6/7 升级 PHP 5.6 到 7.1/7.2 ...

April 7, 2019 · 1 min · jiezi

MySQL高级(索引优化+慢查询定位)

一、先谈谈事务1. ACID特性1.1 原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;1.2 一致性: 执行事务前后,数据库从一个一致性状态转换到另一个一致性状态。1.3 隔离性: 并发访问数据库时,一个用户的事物不被其他事务所干扰,各并发事务之间数据库是独立的;1.4 持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库 发生故障也不应该对其有任何影响。2. 事务隔离级别2.1 READ_UNCOMMITTED(未提交读): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 2.2 READ_COMMITTED(提交读): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 2.3 REPEATABLE_READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。 2.4 SERIALIZABLE(串行): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。Mysql 默认采用的 REPEATABLE_READ隔离级别二、了解索引1. 什么是索引索引是一种帮助MySQL高效获取数据的数据结构2. 优势和劣势优势:提高数据检索的效率,降低数据库的IO成本降低数据排序的成本,降低了CPU的消耗劣势:索引列要占用空间降低了更新表的速度(INSERT、UPDATE)建立优秀索引的时间成本3. 哪些情况需要创建索引主键自动建立唯一索引频繁作为查询条件的字段应该创建索引查询中与其他表关联的字段,外键关系建立索引where条件里用不到的字段不创建索引单键/组合索引的选择问题,在高并发倾向创建组合索引查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度查询中统计或分组字段4. 哪些情况下不需要创建索引表记录太少频繁更新的字段不适合创建索引,因为每次更新不单单是更新了记录还会更新索引,加重了IO负担数据重复且分布平均的表字段(若某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果,即过滤性不好的字段)三、EXPLAIN【重点】使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MYSQL是如何处理你的SQL语句的,分析你的查询语句或是表结构的性能瓶颈EXPLAIN查询出来的字段a、id①id相同,执行顺序由上至下②id不同,id值越大优先级越高,越先被执行b、select_type查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询c、table显示这行的数据是关于哪张表的。d、type(最好到最差的顺序)system:表只有一行记录(等于系统表),这是const的特例,平时不会出现,这个可以忽略const:表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快。如将主键置于where列表中,MySQL就能将该查询转换为一个常量eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常用于主键或唯一索引扫描ref:非唯一性索引扫描,返回匹配某个单独值的所有行,可能会找到多个符合符合条件的行range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引。一般是你的where语句中出现between、<、>、in等的查询,这种范围扫描索引扫描比全表扫描要好index:index与all的区别为index类型只遍历索引树,也就是说,虽然all和index都是读全表,但index是从索引中读取的,而all是从硬盘中读取的all:遍历全表备注:一般来说,得保证查询至少达到range级别,最好能达到refe、possible_keys可能应用到这张表的索引f、key实际使用的索引g、key_len索引使用的字节数,在不损失精确性的情况下,长度越短越好h、ref显示索引的哪一列被使用了i、rows大致估算出找到所需记录所需要读取的行数j、ExtraUsing filesort:说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为“文件排序”Using temporary:使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表,常见于排序order by和分组查询group byUSING index:表示相应的select操作中使用了覆盖索引,避免访问了表的数据行什么情况下索引会失效全值匹配最佳左前缀法则不在索引列上做任何操作(计算、函数、(手动或自动)类型转换),会导致索引失效而转向全表扫描存储引擎不能使用索引中范围条件右边的列尽量使用覆盖索引MySQL在使用不等于(!=或<>)的时候无法使用索引会导致全表扫描is null,is not null也无法使用索引like以通配符开头(“%abc..‘)MySQL索引失效会变成全表扫描的操作字符串不加单引号索引失效(自动类型转换)or左边有索引、右边没索引也会失效order by关键字优化尽量使用index方式排序,避免使用filesort方式。order by满足两种情况会使用index排序:①、order by语句使用索引最左前列,②、使用where子句与order by子句条件列组合满足索引最左前列双路排序:MySQL4.1之前,两次扫描磁盘单路排序:从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序后的列进行输出,效率更高一点,但是它会使用更多的空间,因为它把每一行都保存在内存中了优化策略: 增大sort_buffer_size参数的设置、增大max_length_for_sort_data参数的设置group by关键字优化实质是先排序后进行分组,遵照索引键的最佳左前缀,当无法使用索引列时,增大sort_buffer_size+max_length_for_sort_data参数的设置三、慢查询【重点】1. 慢查询日志是什么 MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10,意思是运行10秒以上的语句。由他来查看哪些SQL超出了我们的最大忍耐时间值,比如一条sql执行超过5秒钟,我们就算慢SQL,希望能收集超过5秒的sql,结合之前explain进行全面分析。2. 怎么用默认情况下,MySQL数据库没有开启慢查询日志,需要我们手动来设置这个参数。(当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件。)– 查看开启情况SHOW VARIABLES LIKE ‘%slow_query_log%’;– 开启(只对当前数据库生效,如果要永久生效,就必须修改配置文件my.cnf)set global slow_query_log=1;3. Show Profile【重点】3.1. 是什么mysql提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL的调优的测量,相比explain,show profile展示的数据更加详尽。 3.2. 怎么用– 查看是否开启show variables like ‘profiling’;– 开启功能,默认是关闭,使用前需要开启set profiling=1;– 查看结果show profiles;– 诊断SQLshow profile cpu,block io for query n;– 还可以通过SELECT * FROM information_schema.profiling WHERE query_id = n ORDER BY seq;获取 ...

April 7, 2019 · 1 min · jiezi

InnoDB与MyISAM的区别(详细)

一、InnoDB的特点支持行锁,采用MVCC来支持高并发,有可能死锁支持事务支持外键支持崩溃后的安全恢复不支持全文索引B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。二、MyISAM的特点不支持行锁(MyISAM只有表锁)不支持事务不支持外键不支持崩溃后的安全恢复支持BLOB和TEXT的前500个字符索引,支持全文索引对于不会进行修改的表,支持压缩表,极大地减少了磁盘空间的占用其数据文件本身就是索引文件。相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。而其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值而不是地址,这也是和MyISAM不同的地方。在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再走一遍主索引。 因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。三、关于二者的对比和总结MyISAM 强调的是性能,每次查询具有原子性,其执行速度比InnoDB类型更快,但是不提供事务支持。但是InnoDB 提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。MyISAM更适合读密集的表,而InnoDB更适合写密集的的表

April 7, 2019 · 1 min · jiezi

SpringBoot 仿抖音短视频小程序开发(一)

一、项目简介模仿抖音做的一个短视频微信小程序,用SpringBoot搭建小程序后台,用SSM框架搭建短视频后台管理系统,小程序后台通过分布式zookeeper监听节点自动下载或删除短视频后台管理系统上传的视频。二、环境参数核心框架:SpringBoot、SSM数据库:MySQL、 HikariCP数据源、MyBatis逆向工程中间件:zookeeper,redis,swagger2前端框架: Bootstrap + Jquery、jqGrid分页组件音频处理: FFmpeg开发工具: IDEA 热门技术点 三、项目展示 功能: 小程序【注册登录注销】、【上传头像】、【上传作品】、【查看所有/单个短视频】、【点赞】、【关注某用户】、【短视频和BGM合并】、【留言评论回复】、【举报】、【下载短视频到手机】 四、数据库设计CREATE TABLE bgm ( id varchar(64) NOT NULL, author varchar(255) NOT NULL, name varchar(255) NOT NULL, path varchar(255) NOT NULL COMMENT ‘播放地址’, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;CREATE TABLE comments ( id varchar(20) NOT NULL, father_comment_id varchar(20) DEFAULT NULL, to_user_id varchar(20) DEFAULT NULL, video_id varchar(20) NOT NULL COMMENT ‘视频id’, from_user_id varchar(20) NOT NULL COMMENT ‘留言者,评论的用户id’, comment text NOT NULL COMMENT ‘评论内容’, create_time datetime NOT NULL, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘课程评论表’;CREATE TABLE search_records ( id varchar(64) NOT NULL, content varchar(255) NOT NULL COMMENT ‘搜索的内容’, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘视频搜索的记录表’;CREATE TABLE vuser ( id varchar(64) NOT NULL, username varchar(20) NOT NULL COMMENT ‘用户名’, password varchar(64) NOT NULL COMMENT ‘密码’, face_image varchar(255) DEFAULT NULL COMMENT ‘我的头像,如果没有默认给一张’, nickname varchar(20) NOT NULL COMMENT ‘昵称’, fans_counts int(11) DEFAULT ‘0’ COMMENT ‘我的粉丝数量’, follow_counts int(11) DEFAULT ‘0’ COMMENT ‘我关注的人总数’, receive_like_counts int(11) DEFAULT ‘0’ COMMENT ‘我接受到的赞美/收藏 的数量’, PRIMARY KEY (id), UNIQUE KEY id (id), UNIQUE KEY username (username)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;CREATE TABLE users_fans ( id varchar(64) NOT NULL, user_id varchar(64) NOT NULL COMMENT ‘用户’, fan_id varchar(64) NOT NULL COMMENT ‘粉丝’, PRIMARY KEY (id), UNIQUE KEY user_id (user_id,fan_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘用户粉丝关联关系表’;CREATE TABLE users_like_videos ( id varchar(64) NOT NULL, user_id varchar(64) NOT NULL COMMENT ‘用户’, video_id varchar(64) NOT NULL COMMENT ‘视频’, PRIMARY KEY (id), UNIQUE KEY user_video_rel (user_id,video_id) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘用户喜欢的/赞过的视频’;CREATE TABLE users_report ( id varchar(64) NOT NULL, deal_user_id varchar(64) NOT NULL COMMENT ‘被举报用户id’, deal_video_id varchar(64) NOT NULL, title varchar(128) NOT NULL COMMENT ‘类型标题,让用户选择,详情见 枚举’, content varchar(255) DEFAULT NULL COMMENT ‘内容’, userid varchar(64) NOT NULL COMMENT ‘举报人的id’, create_date datetime NOT NULL COMMENT ‘举报时间’, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘举报用户表’;CREATE TABLE videos ( id varchar(64) NOT NULL, user_id varchar(64) NOT NULL COMMENT ‘发布者id’, audio_id varchar(64) DEFAULT NULL COMMENT ‘用户使用音频的信息’, video_desc varchar(128) DEFAULT NULL COMMENT ‘视频描述’, video_path varchar(255) NOT NULL COMMENT ‘视频存放的路径’, video_seconds float(6,2) DEFAULT NULL COMMENT ‘视频秒数’, video_width int(6) DEFAULT NULL COMMENT ‘视频宽度’, video_height int(6) DEFAULT NULL COMMENT ‘视频高度’, cover_path varchar(255) DEFAULT NULL COMMENT ‘视频封面图’, like_counts bigint(20) NOT NULL DEFAULT ‘0’ COMMENT ‘喜欢/赞美的数量’, status int(1) NOT NULL COMMENT ‘视频状态:\r\n1、发布成功\r\n2、禁止播放,管理员操作’, create_time datetime NOT NULL COMMENT ‘创建时间’, PRIMARY KEY (id)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘视频信息表’; ...

April 7, 2019 · 2 min · jiezi

MySQL 常用命令

大学学的数据库系统概论工作后几年没有使用都已忘了, 现在项目需要用到数据库, 但来不及细看相关书籍了, 遂将一些常用的记录下来.常用类型MySQL 支持多种类型, 大致可以分为三类: 数值, 日期/时间和字符串(字符)类型, 大致如下:数值类型整数: tinyint, smallint, mediumint, int, bigint浮点数: float, double, decimal日期和时间date, time, datetime, timestamp, year字符串类型字符串: char, varchar文本: tinytext, text, mediumtext, longtext二进制字符串: tinyblob, blob, mediumblob, longblob下面将详细给出每种类型的大小和描述.数值类型类型字节描述tinyint1小整数值smallint2大整数值mediumint3大整数值int4大整数值bigint8极大整数值float4单精度浮点数double8双精度浮点数decimaldecimal(M,D)定点数DECIMAL(M,D) 高精度的数据类型, 常用来存储交易相关的数据M 代表总精度, N 代表小数点右侧的位数. 1 < M < 254, 0 < N < 60, 存储空间变长日期和时间类型字节描述date3精确到年月日, 如: 2016-09-01time3精确到时分秒, 如: 09:10:11datetime8精确到年月日时分秒, 如: 2016-09-01 09:10:11timedtamp8精确到年月日时分秒, 如: 2016-09-01 09:10:11MySQL 5.6.4 之后, datetime 和 timestamp 支持到微秒timestamp 会根据时区进行转换, datetime 则不会timestamp 存储范围: 1970-01-01 00::00:01 ~ 2038-01-19 03:14:07datetime 的存储范围: 1000-01-01 00:00:00 to 9999-12-31 23:59:59一般使用 timestamp 国际化如存时间戳使用数字类型 BIGINT字符串类型类型大小描述char最大为255字符存储定长, 容易造成空间的浪费varchar可以超过255个字符存储变长,节省存储空间text总大小为65535字节,约为64KB长文本数据根据 MySQL 版本的不同, 类型的大小范围可能会有改变.常用命令数据库创建CREATE DATABASE <name>;删除DROP DATABASE <name>;列出数据库show databases;使用数据库use <name>;修改 CHARSETALTER DATABASE <name> DEFAULT CHARACTER SET <char name>;显示创建类型show create database <name>表创建CREATE TABLE <name>(id INT(11) AUTO_INCREMENT),name VARCHAR(64) NOT NULL,…)CHARACTER SET=utf8mb4;删除DROP TABLE <name>;插入INSERT INTO <name>(filed1, filed2,…) VALUES (value1, value2,…),(value3,value4,…),…;可同时插入多条记录查询SELECT filed1,filed2,… FROM <name> WHERE <condition>多表查询: SELECT t1.id,t1.name,t2.name AS desc FROM t1,t2 WHERE t1.t2_id=t2.id, 这将会返回两个表的交集多表查询也可使用联合查询, 联合查询详情见后文.使用 LIKE 可以模糊查询, % 来表示任意字符, 如: SELECT * FROM <name> WHERE name LIKE ‘%oy%’;显示表显示表字段desc <name>显示表创建字段show create table <name>列出表show tables;修改值UPDATE <name> SET <filed1>=value1 WHERE <condition>添加列ALTER TABLE <name> ADD filed1 <type>删除列ALTER TABLE <name> DROP filed1修改列类型ALTER TABLE <name> MODIFY filed1 <type>ALTER TABLE <name> CHANGE filed1 filed1 <type> 可以修改列名修改 CHARSET修改表的默认 CHARSETALTER TABLE <name> DEFAULT CHARACTER SET utf8mb4修改表及所以列的字符ALTER TABLE <name> CONVERT TO CHARACTER SET utf8mb4修改列的字符集ALTER TABLE <name> CHANGE filed1 CHARACTER SET utf8mb4备份/恢复备份mysqldump -h<address> –port <port> -u<username> -p<password> <datebase name> <table name> > bak.sql其中 table name 可选恢复mysql -h<address> –port <port> -u<username> -p<password> <datebase name> < bak.sql导入数据到指定的数据库中联合查询假设存在表: user(id, name, email) 和 user_profile(id,uid,avatar), 几种联合方式如下:左联LEFT JOIN 或 LEFT OUTER JOIN 返回的结果包含左表中的所有行, 若左行在右行中匹配, 则在对应的右表中显示为 NULLSELECT * FROM user LEFT JOIN user_profile ON user.id=user_profile.uid右联RIGHT JOIN 或 RIGHT OUTER JOIN 返回的结果包含右表中的所有行, 若左行在右行中匹配, 则在对应的左表中显示为 NULL全联FULL JOIN 或 FULL OUTER JOIN 返回左右两表中的所有行, 如果右表中某行在左表中没有匹配, 则结果中对应行右表的部分全部为 NULL;如果左表中某行在右表中没有匹配, 则结果中对应行左表的部分全部为空 NULL.内联inner join 是比较运算符, 只返回符合条件的行, 如:SELECT * FROM user INNER JOIN user_profile ON user.id=user_profile.uid等同于 SELECT * FROM user,user_profile WHERE user.id=user_profile.uid ...

April 7, 2019 · 2 min · jiezi

删库后,除了跑路还能怎么办?

阅读本文大概需要 1.4 分钟。当年悟空学艺于菩提祖师门下,老师遣他下山,悟空觉得自己蒙受师傅传授大恩,还没有报答。菩提祖师就说:不要提什么报答之恩,只要你日后闯出祸来不把为师说出来就行了。我听说过挺多删库的事件,于是开玩笑的略改一下:“日后你删了库后,不要把师傅说出来就行了”,不知道那些删库的工程师们,其师傅有没有交代过这句话,emmm。。。话说日后,孙悟空真的删过一个数据库里的记录,这就是:生死薄。孙悟空寿命只有342岁,在大闹地府那天其实阳寿已尽,在原著中曾这样写道:“悟空亲自检阅,直到那魂字一千三百五十号上,方注着孙悟空名字,乃天产石猴,该寿三百四十二岁,善终。”孙悟空哪能受得了这个,拿起生死簿把自己的名字就划了,不仅如此,他也不能让自己的猴子猴孙也经历生老病死,便顺手把生死簿中所有的猴子都给划掉了。所以问题来了:生死簿,这个庞大的数据库系统,如果没有灾备,没有备份,只有当前态,其数据就被永久的改变了。无可挽回。从表象来看,生死薄是一个平板文件的日志记录,但是事实上并非如此,这内部一定是一个庞大而复杂的数据库系统,其中:要存储所有生灵的出生寿元;要存储所有生灵的善恶功德;要存储所有的前世今生循环;要存储所有生灵的关系关联;要高并发高吞吐全宇宙联网;大家想想这个数据结构要怎么设计?数据量实在太大,分库分表分布式,这是少不了的;主键唯一如何规划?前世今生生生不息,关系网实在复杂;天灾人祸批量处理高并发;前车之鉴,容灾备份高可用必须要有?太复杂了,还是作为面试题,找几个人问问,或者招个标搞个方案吧!投标应标咱不管了,可是删除了数据库怎么办?等传票?拿护照?跑路去?nonono !以 MySQL 为例,这里对删库语句做下分类:1. 使用 delete 语句误删数据行,通过闪回 +binlog 可以找回;2. 使用 drop table/database 或者 truncate table 语句误删数据库/表,通过全量数据定期备份 +binlog 可以找回;3. 使用 rm -rf 命令误删整个 MySQL 实例,只要还有备份节点就可以找回。一分钟系列的文章,篇幅有限,这里只简单介绍下采取什么补救措施,不写详细内容,想看详细的文章,可以在下方或后台给我留言。操作需谨慎,删库别跑路!·END·程序员的成长之路路虽远,行则必至微信ID:cxydczzl往期精彩回顾程序员接私活的7大平台利器码农深夜骑车逆行被拦后大哭,称压力好大! 四面美团,收割 offerMySQL 优化实战记录 龙岗一个月350的出租房,我搬出来了 程序员工作 996 生病 ICU ? 一次非常有趣的 SQL 优化经历 如何阅读Java源码? 没执行过 rm -rf /* 的开发不是好运维 你与一份好简历之间的距离 还在使用SimpleDateFormat? Java程序员的成长之路送给程序员们的经典电子书大礼包

April 7, 2019 · 1 min · jiezi

除了996 ICU,GitHub上还有哪些奇葩的项目?

GitHub是一个面向开源及私有软件项目的托管平台,因为只支持git 作为唯一的版本库格式进行托管,故名GitHub。是全球最大的同性交友网站,哦不,最大的程序员交流网站。GitHub是通过Git进行版本控制的软件源代码托管服务,并且,GitHub还引入了一些社交属性。号称是世界上最大的程序员(同性)交友网站。虽然定位只是一个项目托管工具,但是,显然,GitHub近几年有点被玩坏了,本文就来盘点一下,GitHub上有哪些奇葩的项目!?996.ICU 工作996,生病ICU996.ICU项目是一位匿名人士在Github上建立的项目,项目名字调侃近些年IT界流行的一种工作制度:996是指工作日早9点上班,晚上9点下班,中午和晚上休息1小时(或不到),总计10小时以上,并且一周工作6天的工作制度。名字非常形象的形容为只要你天天996早晚进ICU,非常真切的反应了现代加班制度下许多员工过劳死在工作岗位的现实。地址:http://t.cn/EJp5ScRStar数:163kFreeCodeCamp 全宇宙Start数最多的项目FreeCodeCamp 是目前为止(2019年4月)GitHub上Star数最多的项目,Star总数30+万。FreeCodeCamp 是 GitHub 上排名第一的开源项目,致力于帮助人们利用零散时间学习编程。它的使命是Learn to code and help nonprofits(学习编程,去帮助那些非盈利组织)。FCC涵盖HTML5、CSS、React、JavaScript、Database等课程,游戏化程度非常高,学员可以通过线上聊天室和线下社区活动(Coffee and Code)相互帮助。目前覆盖全球160+个国家、1000+个城市,40W+开发者在平台学习。地址:http://t.cn/R6Dz4k6 Start数:300kDress 女装大佬聚集地有129名贡献者在这个名为Dress的项目里集结,上传了数千张女装照,收获了8000余颗星。你敢相信这是个码农?地址:http://t.cn/RRSVwV1Star数:8.5kcore-values-encoder 核心价值观编码这程序使用社会主义核心价值观作为编码器,旨在通过编程学习党的十八大提出的社会主义核心价值观。社会主义核心价值观:富强、民主、文明、和谐;自由、平等、公正、法治;爱国、敬业、诚信、友善地址:http://t.cn/R3maS2H Star数:1.6kThanos 灭霸命令Thanos.sh是一个开源的个灭霸命令,可随机删除电脑上一半文件。不知道这个命令有多少人执行过!?地址:http://t.cn/RkIy7B2Star数:0.7klogoly 自动生成 Pornhub 风格 Logo不明白什么是Pornhub的小编一脸懵逼的问一句:这项目有个蛋用?地址:http://t.cn/EJtvcKC Star数:1.8kWTFPL 你XX想干啥都行协议我们知道市面上有许多开源许可证,大概有上百种。目前比较常用的有GPL、BSD、MIT、Mozilla、Apache和LGPL等,但是GitHub上有人开源了一个LICENSE,这个协议可谓是非常开放:你XX爱干啥就干啥。默默的问一句,想干啥就干啥,我用你干啥呢?地址:http://t.cn/8sHPRLA Star数:0.1knocode 一行代码都没有作为一个代码托管平台,GitHub上有一个项目没有一行代码,但是竟然有2万多人关注。这是一个Issue比项目更精彩的项目,项目中一行代码都没有!项目名字也简单直接:nocode想问下,Star这个项目的2.7万人是有多无聊???地址:http://t.cn/R8d7I8B Star数:27kprogrammer-job-blacklist 码农找工作黑名单程序员列出的找工作黑名单,记录了一些不建议大家入职的公司。地址:http://t.cn/RiPQ3Jn Star数:19khangzhou_house_knowledge 杭州买房攻略作者根据自己2017年买房经历,总结出来的买房购房知识。在这里,作者从杭州区域划分、房产基础概念、购房渠道、杭州房产规划、杭州发展规划、房产配套设施、综合分析几个角度分析了杭州的购房现状。地址:http://t.cn/RE4mIXo Star数:19khowmanypeoplearearound 通过 Wi-Fi 信号估计附近有多少人这位程序员大哥是有多无聊,要是能通过WIFI信号添加附近的妹子微信的话这个项目一定火!地址:http://t.cn/Ra9004B Star数:5.8kchinese-xinhua 新华字典数据库中华新华字典数据库和 API 。收录包括 14032 条歇后语,16142 个汉字,264434 个词语,31648 个成语。地址:http://t.cn/ReKJLq8Star数:4.8kseq2seq-couplet 用深度学习对对联地址:http://t.cn/REn0T92Star数:1.5k好了,今天就盘点这么多了,你觉得哪个项目最奇葩呢?另外,你还知道哪些奇葩的项目呢?欢迎评论!!!

April 4, 2019 · 1 min · jiezi

MySQL 复制 - 性能与扩展性的基石 2:部署及其配置

正所谓理论造航母,现实小帆船。单有理论,不动手实践,学到的知识犹如空中楼阁。接下来,我们一起来看下如何一步步进行 MySQL Replication 的配置。为 MySQL 服务器配置复制非常简单。但由于场景不同,基本的步骤还是有所差异。最基本的场景是新安装主库和备库,总得来说分为以下几步:在每台服务器上创建复制账号。配置主库和备库。通知备库连接到主库并从主库复制数据。此外,由于主备部署需要多台服务器,但是这种要求对大多数人来说并不怎么友好,毕竟没有必要为了学习部署主备结构,多买个云服务器。因此,为了测试方便,我们通过 docker 容器技术在同台机器上部署多个容器,从而实现在一台机器上部署主备结构。这里我们先假定大部分配置采用默认值,在主库和备库都是全新安装并且拥有同样的数据。接下来,我们将展示如何通过 docker 技术一步步进行复制配置。此外,我们将推荐一些“安全配置”,以便在不清楚如何配置时,确保数据的安全。1 部署 docker 环境1) 部署 docker什么?docker 还没部署?赶紧参考这里配一个,docker 都没玩,怎么和面试官吹水呀!2) 拉取 MySQL 镜像docker pull mysql:5.73) 使用 mysql 镜像启动容器docker run -p 3339:3306 –name mysql-master -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7 # 启动 master 容器docker run -p 3340:3306 –name mysql-slave -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7 # 启动 slave 容器master 对外的端口是 3339,slave 对外的端口是 3340,我们在使用客户端连接要使用对应的端口连接对应 mysql。4) 使用命令查看正在运行的容器docker ps5) 使用客户端连接工具测试丽连接 mysql2 配置 Master 和 Slave1) 配置 master通过以下命令进入容器内部docker exec -it mysql-master /bin/basha) 更新 apt-get 源apt-get updateb) 安装 vimapt-get install vimc) 配置 my.cnfvim /etc/mysql/my.cnf// 在my.cnf 中添加如下配置server-id=110 # 服务器 id,同一局域网内唯一log-bin=/var/lib/mysql/mysql-bin # 二进制日志路径d) 重启 mysql 服务使配置生效service mysql restarte) 启动容器重启 mysql 服务时会使得 docker 容器停止,需要重启容器。docker start mysql-masterf) 创建数据同步用户并授权CREATE USER ‘slave’@’%’ IDENTIFIED BY ‘123456’;GRANT REPLICATION SLAVE,REPLICATION CLIENT ON . TO ‘slave’@’%’;2) 配置 slave通过以下命令进入容器内部docker exec -it mysql-slave /bin/basha) 配置 my.cnfvim /etc/mysql/my.cnf// 在my.cnf 中添加如下配置server-id=120 # 服务器 id,同一局域网内唯一log-bin=/var/lib/mysql/mysql-bin # 二进制日志路径relay_log=/path/to/logs/relay-bin # 中继日志路径 3) 关联 master 和 slave配置完 master 和 slave,接下来就要让 master 和 slave 相关联。回到我们的服务器,先找出 master 和 slave 容器的 IP,执行:docker inspect –format=’{{.NetworkSettings.IPAddress}}’ mysql-master因此,我们知道了 mysql-master 容器的 IP 是:172.17.0.3。同样的方法,mysq-slave 容器的 IP 是:172.17.0.4。记住这两个值,后面的配置需要用到。我们首先配置 master。在 master 容器内通过 mysql -u root -p 进入 MySQL 命令行,执行 show master status;上图中,File 和 Position 字段对应的值要记录下来,后续在 slave 配置时需要用到这两个值。要注意的是,记录完这两个值后,就不能在 master 库上做任何操作,否则会出现数据不同步的情况。接下来配置 slave,同样的,在 slave 上进入 MySQL 命令行。然后执行下面语句:change master to master_host=‘172.17.0.3’, master_user=‘slave’, master_password=‘123456’, master_port=3306, master_log_file=‘mysql-bin.000001’, master_log_pos=42852, master_connect_retry=30;change master to 是 slave 配置 master 的命令,相关参数含义如下:master_host:master 的IP,就是我们上面获取的 IP 地址master_port:master 的端口号,也就是我们 master mysql 的端口号master_user:进行数据同步的用户master_password:同步用户的密码master_log_file:指定 slave 从 master 的哪个日志文件开始复制数据,也就是我们上面提到的 File 字段的值master_log_pos:从 master 日志文件的那个位置开始读,上面提到的 Position 字段的值master_connect_retry:重试时间间隔。单位是秒,默认 603 启动复制配置完 slave 后,可以通过 show slave statusG; 查看 slave 的状态。![slave 状态]](/img/bVbqPxZ)正常情况下,刚配置完 slave 的 Slave_IO_Running 和 Slave_SQL_Runing 都是 NO,因为我们还没开启主从复制。使用 start slave 开启主从复制,然后再查下 slave 状态。slave 的 Slave_IO_Running 和 Slave_SQL_Runing 都是 YES,说明主从复制已成功启动。此时,可以通过客户端能否成功复制数据。我们在 master 新建 replication 库,然后观察 slave 库是否创建了 replication 库,如下图,表示复制成功。另外,开启主从复制后,如果出现以下情况:Slave_IO_Running: CONNECTINGSlave_SQL_RUNNING: Yes表示开启主从复制后, slave 的 IO 进程连接 master 出现问题,一直在重试连接。我们可以根据 Last_IO_Error 的提示进行解决:网络不通。检查 IP、port。密码错误。检查配置的同步用户和密码是否正确。pos 错误。检查 slave 配置的 Position 的值 与 master 是否一致。4 从另一个服务器开始复制前面的设置都是假定主备库均为刚刚安装好且都是默认的数据,也就是说两台服务器上数据相同,并且知道当前主库的二进制日志。但在实际环境中,大多数情况下是有一个一级运行了一段时间的主库,然后用一台新安装的备库与之同步,此时这台备库还没有数据。有几种方法来初始化备库或者从其他服务器克隆数据到备库。包括从主库复制数据、从另外一台备库克隆数据,以及使用最近的一次备份来启动备库等。而这些方法都需要有三个条件来让主库与备库保持同步:在某个时间点的主库的数据快照。主库当前的二进制日志文件,和获得数据快照时在该二进制日志文件中的偏移量。我们把这两个值称为日志文件坐标(log file coordinates)。通过这两个值可以确定二进制日志的位置。可以通过 SHOW MASTER STATUS 命令来获取这些值。从快照时间到现在的二进制日志。下面是一些从别的服务器克隆备库的方法:使用冷备份。最基本的方法是关闭主库,把数据复制到备库。重启主库后,会使用一个新的二进制日志文件,我们在备库通过执行 CHANGE MASTER TO 指向这个文件的起始处。不过这个方法的缺点很明显:在复制数据时需要关闭主库。使用热备份。如果仅使用了 MyISAM 表,可以在主库运行时使用 mysqlhotcopy 或 rsync 来复制数据。使用 mysqldump。如果只包含 InnoDB 表,可以使用以下命令来转储主库数据并将其加载到备库,然后设置相应的二进制日志坐标:mysqldump –single-transaction –all-databases –master-data=1 –host=server1 | mysql –host=server2。选项 –single-transaction 使得转储的数据为事务开始前的数据。如果使用的是非事务型表,可以使用 –lock-all-tables 选项来获得所有表的一致性转储。使用快照或备份。只要知道对应的二进制日志坐标,就可以使用主库的快照或者备份来初始化备库。(如果使用备份,需要确保从备份的时间点开始的主库二进制日志都要存在)。只需要把备份或快照恢复到备库,然后使用 CHANGE MASTER TO 指定二进制日志的坐标。使用 Percona Xtrabackup。Percona 的 Xtrabackup 是一款开源的热备份工具。它能够在备份时不阻塞服务器的操作,因此可以在不影响主库的情况下设置备库。可以通过克隆主库或另一个已存在的备库的方式来建立备库。使用另外的备库。可以使用任何一种克隆或拷贝技术从任意一台备库上将数据克隆到另外一台服务器。但是如果使用的是 mysqldump,–master-data 选项就会不起作用。此外,不能使用 SHOW MASTER STATUS 来获得主库的二进制日志坐标,而是在获取快照时使用 SHOW SLAVE STATUS 来获取备库在主库上的执行位置。使用另外的备库进行数据克隆最大的缺点是,如果这台备库的数据已经和主库不同步,克隆得到的就是脏数据。5 推荐的复制配置我们知道,MySQL 的复制有许多参数可以控制,其中一些会对数据安全和性能产生影响。这里,我们介绍一种“安全配置”,可以最小化问题发生的概率。在主库上二进制日志最重要的选项是 sync_binlog:sync_binlog=1如果开启该选项,MySQL 每次在提交事务前会将二进制日志同步到磁盘上,保证在服务器崩溃时不会丢失时间。如果禁止该选项,服务器会少做一些工作,但二进制日志文件可能在服务器崩溃时损坏或丢失信息。在一个不需要作为主库的备库上 ,该选项会带来不必要的开销。要注意的是,它只适用于二进制日志,而非中继日志。如果无法接受服务器崩溃导致表损坏,推荐使用 InnoDB。MyISAM 表在备库服务器崩溃重启后,可能已经处于不一致状态。如果使用 InnoDB,推荐设置如下选项:innodb_flush_logs_at_trx_commit=1 # 每次事务提交时,将 log buffer 写入到日志文件并刷新到磁盘。默认值为 1innodb_safe_binlog明确指定二进制日志文件的名称。当服务器间转移文件、克隆新的备库、转储备份或者其他场景下,如果以服务器名来命名二进制日志可能会导致很多问题。因此,我们需要给 log_bin 选项指定一个参数。log_bin=/var/lib/mysql/mysql-bin在备库上,同样开启如下培训,为中继日志指定绝对路径:relay_log=/path/to/logs/relay-binskip_slave_startread_only通过设置 relay_log 可以避免中继日志文件基于机器名来命名,防止之前提到的可能在主库上发生的问题。而 skip_slave_start 选项能够阻止备库在崩溃后自动启动复制,以留出时间修复可能发生的问题。read_only 选项可以阻止大部分用户更改非临时表。6 小结复制初始化配置三部曲:创建账号、配置主备库、备库连接到主库开始复制;从已有服务器复制时,可用热备份或 mysqldump 命令进行备份;在不确定相关配置时,选择最安全的配置准没错; ...

April 3, 2019 · 2 min · jiezi

PHP面试知识点梳理

思维导图地址:https://www.processon.com/vie…前段时间复习的时候总结的,在此分享给大家。

April 3, 2019 · 1 min · jiezi

django创建model错误解答

1.在settings没有添加AUTH_USER_MODELauth.User.groups: (fields.E304) Reverse accessor for ‘User.groups’ clashes with reverse accessor for ‘User.groups’.HINT: Add or change a related_name argument to the definition for ‘User.groups’ or ‘User.groups’.解决:在settings中添加AUTH_USER_MODEL2.数据库配置参数出错django.core.exceptions.ImproperlyConfigured: settings.DATABASES is improperly configured. Please supply the ENGINE value. Check settings documentation for more details.解决:查看settings中DATABASES参数是否配置出错,比如数据库指定,数据库权限,密码之类3.因为在django中自定义了命令,报命令重复django.core.exceptions.ImproperlyConfigured: Application labels aren’t unique, duplicates: commands解决:删掉自定制的commands,简单的也可以在INSTALLED_APPS中,注释掉commands模块.

April 2, 2019 · 1 min · jiezi

web缓存

一:缓存对性能优化请求更快:通过将内容缓存在本地浏览器或距离最近的缓存服务器(如CDN),在不影响网站交互的前提下可以大大加快网站加载速度。降低服务器压力:在大量用户并发请求的情况下,服务器的性能受到限制,此时将一些静态资源放置在网络的多个节点,可以起到均衡负载的作用,降低服务器的压力。缓存控制机制优先级:Expires < Cache:max-ageLast-Modified < ETag二:缓存方式服务端缓存,例如CDN客户端缓存,即浏览器缓存,可通过cache,manifest等实现.浏览器缓存分两类,强制缓存和协商缓存强制缓存:浏览器在加载资源时,根据http header判断它是否命中强缓存,如果命中,浏览器直接从自己的缓存中读取资源,不会发请求到服务器。比如某个css文件,如果浏览器在加载它所在的网页时,这个css文件的缓存配置命中了强缓存,浏览器就直接从缓存中加载这个css,连请求都不会发送到网页所在服务器.协商缓存:当强缓存没有命中的时候,浏览器一定会发送一个请求到服务器,通过服务器端依据资源的另外一些http header验证这个资源是否命中协商缓存,如果协商缓存命中,服务器会将这个请求返回(304),但是不会返回这个资源的数据,而是告诉客户端可以直接从缓存中加载这个资源,于是浏览器就又会从自己的缓存中去加载这个资源;若未命中请求,则将资源返回客户端,并更新本地缓存数据(200)。强制缓存不发请求到服务器,协商缓存会发请求到服务器。强制缓存:Expires、Cache-control协商缓存:Last-Modified/If-Modified-Since, Etag/If-None-Match三:强制缓存控制Expires,HTTP/1.0提出的一个表示资源过期时间的header,它描述的是一个绝对时间,由服务器返回,用GMT格式的字符串表示,如:Expires:Thu, 31 Dec 2016 23:55:55 GMT,Expires是较老的强缓存管理header,由于它是服务器返回的一个绝对时间,这样存在一个问题,如果客户端的时间与服务器的时间相差很大(比如时钟不同步,或者跨时区),那么误差就很大,所以在HTTP/1.1版开始,使用Cache-Control: max-age=秒替代。Cache-Control:针对上述的“Expires时间是相对服务器而言,无法保证和客户端时间统一”的问题,http1.1新增了 Cache-Control 来定义缓存过期时间,若报文中同时出现了 Pragma、Expires 和 Cache-Control,会以 Cache-Control 为准。Cache-Control也是一个通用首部字段,这意味着它能分别在请求报文和响应报文中使用。读取缓存数据条件:上次缓存时间(客户端的)+max-age < 当前时间(客户端的).Cache-Control取值的含义:各个消息中的指令含义如下:no-cache指示请求或响应消息不能缓存,该选项并不是说可以设置”不缓存“,而是需要和服务器确认。max-age指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应。上次缓存时间(客户端的)+max-age(64200s)<客户端当前时间min-fresh指示客户机可以接收响应时间小于当前时间加上指定时间的响应。四:协商缓存Last-Modified/If-Modified-Since:Last-Modified:标示这个响应资源的最后修改时间。web服务器在响应请求时,告诉浏览器资源的最后修改时间。If-Modified-Since:当资源过期时(强缓存失效),发现资源具有Last-Modified声明,则再次向web服务器请求时带上If-Modified-Since标签,把上次服务器返回的Last-Modified时间返回到服务器端。web服务器收到请求后根据If-Modified-Since 时间与被请求资源的最后修改时间进行比对。若最后修改时间较新,说明资源又被改动过,则响应整片资源内容(写在响应消息包体内),HTTP 200;若最后修改时间较旧,说明资源无新修改,则响应HTTP 304 (无需包体,节省浏览),告知浏览器继续使用所保存的cache。注: Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间(无法及时更新文件)如果某些文件会被定期生成,当有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存,有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形(无法使用缓存)。Etag/If-None-Match:Etag:web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)。Apache中,ETag的值,默认是对文件的索引节(INode),大小(Size)和最后修改时间(MTime)进行Hash后得到的。If-None-Match:当资源过期时(使用Cache-Control标识的max-age),发现资源具有Etage声明,则再次向web服务器请求时带上头If-None-Match (Etag的值)。web服务器收到请求后发现有头If-None-Match 则与被请求资源的相应校验串进行比对,决定返回200或304。注:Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。五:CDNCDN的全称是Content Delivery Network,即内容分发网络。通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近取得所需内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度。CDN是一个经策略性部署的整体系统,包括分布式存储、负载均衡、网络请求的重定向和内容管理4个要件,而内容管理和全局的网络流量管理(Traffic Management)是CDN的核心所在。通过用户就近性和服务器负载的判断,CDN确保内容以一种极为高效的方式为用户的请求提供服务。

April 2, 2019 · 1 min · jiezi

mysql 查看时区 修改时区

方法1#查看时区show variables like “%time_zone%";set global time_zone = ‘+8:00’; ##修改mysql全局时区为北京时间,即我们所在的东8区set time_zone = ‘+8:00’; ##修改当前会话时区flush privileges; #立即生效方法2# vim /etc/my.cnf ##在[mysqld]区域中加上default-time_zone = ‘+8:00’# /etc/init.d/mysqld restart ##重启mysql使新时区生效

April 2, 2019 · 1 min · jiezi

你见过的最全面的python重点

前端span设置margin上下无效果,因为span是行内元素,是没有宽高的。Py2 VS Py3print成为了函数,python2是关键字不再有unicode对象,默认str就是unicodepython3除号返回浮点数没有了long类型rangex不存在,range替代了rangex可以使用中文定义函数名变量名高级解包 和*解包限定关键字参数 后的变量必须加入名字=值raise fromiteritems移除变成items()yield from 链接子生成器asyncio,async/await原生协程支持异步编程新增enum,mock,ipaddress,concurrent.futures,asyncio urllib,selector不同枚举类间不能进行比较同一枚举类间只能进行相等的比较枚举类的使用(编号默认从1开始)为了避免枚举类中相同枚举值的出现,可以使用@unique装饰枚举类#枚举的注意事项from enum import Enumclass COLOR(Enum): YELLOW=1#YELLOW=2#会报错 GREEN=1#不会报错,GREEN可以看作是YELLOW的别名 BLACK=3 RED=4print(COLOR.GREEN)#COLOR.YELLOW,还是会打印出YELLOWfor i in COLOR:#遍历一下COLOR并不会有GREEN print(i)#COLOR.YELLOW\nCOLOR.BLACK\nCOLOR.RED\n怎么把别名遍历出来for i in COLOR.members.items(): print(i)# output:(‘YELLOW’, <COLOR.YELLOW: 1>)\n(‘GREEN’, <COLOR.YELLOW: 1>)\n(‘BLACK’, <COLOR.BLACK: 3>)\n(‘RED’, <COLOR.RED: 4>)for i in COLOR.members: print(i)# output:YELLOW\nGREEN\nBLACK\nRED#枚举转换#最好在数据库存取使用枚举的数值而不是使用标签名字字符串#在代码里面使用枚举类a=1print(COLOR(a))# output:COLOR.YELLOWpy2/3转换工具six模块:兼容pyton2和pyton3的模块2to3工具:改变代码语法版本__future__:使用下一版本的功能常用的库必须知道的collectionspython排序操作及heapq模块itertools模块超实用方法不常用但很重要的库dis(代码字节码分析)inspect(生成器状态)cProfile(性能分析)bisect(维护有序列表)fnmatchfnmatch根据系统决定fnmatch(string,".txt") #win下不区分大小写fnmatchcase完全区分大小写timeit(代码执行时间) def isLen(strString): #还是应该使用三元表达式,更快 return True if len(strString)>6 else False def isLen1(strString): #这里注意false和true的位置 return [False,True][len(strString)>6] import timeit print(timeit.timeit(‘isLen1(“5fsdfsdfsaf”)’,setup=“from main import isLen1”)) print(timeit.timeit(‘isLen(“5fsdfsdfsaf”)’,setup=“from main import isLen”))contextlib@contextlib.contextmanager使生成器函数变成一个上下文管理器types(包含了标准解释器定义的所有类型的类型对象,可以将生成器函数修饰为异步模式) import types types.coroutine #相当于实现了__await__html(实现对html的转义) import html html.escape("<h1>I’m Jim</h1>") # output:’&lt;h1&gt;I&#x27;m Jim&lt;/h1&gt;’ html.unescape(’&lt;h1&gt;I&#x27;m Jim&lt;/h1&gt;’) # <h1>I’m Jim</h1>mock(解决测试依赖)concurrent(创建进程池河线程池)from concurrent.futures import ThreadPoolExecutorpool = ThreadPoolExecutor()task = pool.submit(函数名,(参数)) #此方法不会阻塞,会立即返回task.done()#查看任务执行是否完成task.result()#阻塞的方法,查看任务返回值task.cancel()#取消未执行的任务,返回True或False,取消成功返回Truetask.add_done_callback()#回调函数task.running()#是否正在执行 task就是一个Future对象for data in pool.map(函数,参数列表):#返回已经完成的任务结果列表,根据参数顺序执行 print(返回任务完成得执行结果data) from concurrent.futures import as_completedas_completed(任务列表)#返回已经完成的任务列表,完成一个执行一个wait(任务列表,return_when=条件)#根据条件进行阻塞主线程,有四个条件selectot(封装select,用户多路复用io编程)asynciofuture=asyncio.ensure_future(协程) 等于后面的方式 future=loop.create_task(协程)future.add_done_callback()添加一个完成后的回调函数loop.run_until_complete(future)future.result()查看写成返回结果asyncio.wait()接受一个可迭代的协程对象asynicio.gather(*可迭代对象,可迭代对象) 两者结果相同,但gather可以批量取消,gather对象.cancel()一个线程中只有一个loop在loop.stop时一定要loop.run_forever()否则会报错loop.run_forever()可以执行非协程最后执行finally模块中 loop.close()asyncio.Task.all_tasks()拿到所有任务 然后依次迭代并使用任务.cancel()取消偏函数partial(函数,参数)把函数包装成另一个函数名 其参数必须放在定义函数的前面loop.call_soon(函数,参数)call_soon_threadsafe()线程安全 loop.call_later(时间,函数,参数)在同一代码块中call_soon优先执行,然后多个later根据时间的升序进行执行如果非要运行有阻塞的代码使用loop.run_in_executor(executor,函数,参数)包装成一个多线程,然后放入到一个task列表中,通过wait(task列表)来运行通过asyncio实现httpreader,writer=await asyncio.open_connection(host,port)writer.writer()发送请求async for data in reader: data=data.decode(“utf-8”) list.append(data)然后list中存储的就是htmlas_completed(tasks)完成一个返回一个,返回的是一个可迭代对象 协程锁async with Lock():Python进阶进程间通信:Manager(内置了好多数据结构,可以实现多进程间内存共享)from multiprocessing import Manager,Processdef add_data(p_dict, key, value): p_dict[key] = valueif name == “main”: progress_dict = Manager().dict() from queue import PriorityQueue first_progress = Process(target=add_data, args=(progress_dict, “bobby1”, 22)) second_progress = Process(target=add_data, args=(progress_dict, “bobby2”, 23)) first_progress.start() second_progress.start() first_progress.join() second_progress.join() print(progress_dict)Pipe(适用于两个进程)from multiprocessing import Pipe,Process#pipe的性能高于queuedef producer(pipe): pipe.send(“bobby”)def consumer(pipe): print(pipe.recv())if name == “main”: recevie_pipe, send_pipe = Pipe() #pipe只能适用于两个进程 my_producer= Process(target=producer, args=(send_pipe, )) my_consumer = Process(target=consumer, args=(recevie_pipe,)) my_producer.start() my_consumer.start() my_producer.join() my_consumer.join()Queue(不能用于进程池,进程池间通信需要使用Manager().Queue())from multiprocessing import Queue,Processdef producer(queue): queue.put(“a”) time.sleep(2)def consumer(queue): time.sleep(2) data = queue.get() print(data)if name == “main”: queue = Queue(10) my_producer = Process(target=producer, args=(queue,)) my_consumer = Process(target=consumer, args=(queue,)) my_producer.start() my_consumer.start() my_producer.join() my_consumer.join()进程池def producer(queue): queue.put(“a”) time.sleep(2)def consumer(queue): time.sleep(2) data = queue.get() print(data)if name == “main”: queue = Manager().Queue(10) pool = Pool(2) pool.apply_async(producer, args=(queue,)) pool.apply_async(consumer, args=(queue,)) pool.close() pool.join()sys模块几个常用方法argv 命令行参数list,第一个是程序本身的路径path 返回模块的搜索路径modules.keys() 返回已经导入的所有模块的列表exit(0) 退出程序a in s or b in s or c in s简写采用any方式:all() 对于任何可迭代对象为空都会返回True # 方法一 True in [i in s for i in [a,b,c]] # 方法二 any(i in s for i in [a,b,c]) # 方法三 list(filter(lambda x:x in s,[a,b,c]))set集合运用{1,2}.issubset({1,2,3})#判断是否是其子集{1,2,3}.issuperset({1,2}){}.isdisjoint({})#判断两个set交集是否为空,是空集则为True代码中中文匹配[u4E00-u9FA5]匹配中文文字区间[一到龥]查看系统默认编码格式 import sys sys.getdefaultencoding() # setdefaultencodeing()设置系统编码方式getattr VS getattributeclass A(dict): def getattr(self,value):#当访问属性不存在的时候返回 return 2 def getattribute(self,item):#屏蔽所有的元素访问 return item类变量是不会存入实例__dict__中的,只会存在于类的__dict__中globals/locals(可以变相操作代码)globals中保存了当前模块中所有的变量属性与值locals中保存了当前环境中的所有变量属性与值python变量名的解析机制(LEGB)本地作用域(Local)当前作用域被嵌入的本地作用域(Enclosing locals)全局/模块作用域(Global)内置作用域(Built-in)实现从1-100每三个为一组分组 print([[x for x in range(1,101)][i:i+3] for i in range(0,100,3)])什么是元类?即创建类的类,创建类的时候只需要将metaclass=元类,元类需要继承type而不是object,因为type就是元类type.bases #(<class ‘object’>,)object.bases #()type(object) #<class ’type’> class Yuan(type): def new(cls,name,base,attr,args,**kwargs): return type(name,base,attr,args,**kwargs) class MyClass(metaclass=Yuan): pass什么是鸭子类型(即:多态)?Python在使用传入参数的过程中不会默认判断参数类型,只要参数具备执行条件就可以执行深拷贝和浅拷贝深拷贝拷贝内容,浅拷贝拷贝地址(增加引用计数)copy模块实现神拷贝单元测试一般测试类继承模块unittest下的TestCasepytest模块快捷测试(方法以test_开头/测试文件以test_开头/测试类以Test开头,并且不能带有 init 方法)coverage统计测试覆盖率 class MyTest(unittest.TestCase): def tearDown(self):# 每个测试用例执行前执行 print(‘本方法开始测试了’) def setUp(self):# 每个测试用例执行之前做操作 print(‘本方法测试结束’) @classmethod def tearDownClass(self):# 必须使用 @ classmethod装饰器, 所有test运行完后运行一次 print(‘开始测试’) @classmethod def setUpClass(self):# 必须使用@classmethod 装饰器,所有test运行前运行一次 print(‘结束测试’) def test_a_run(self): self.assertEqual(1, 1) # 测试用例gil会根据执行的字节码行数以及时间片释放gil,gil在遇到io的操作时候主动释放什么是monkey patch?猴子补丁,在运行的时候替换掉会阻塞的语法修改为非阻塞的方法什么是自省(Introspection)?运行时判断一个对象的类型的能力,id,type,isinstancepython是值传递还是引用传递?都不是,python是共享传参,默认参数在执行时只会执行一次try-except-else-finally中else和finally的区别else在不发生异常的时候执行,finally无论是否发生异常都会执行except一次可以捕获多个异常,但一般为了对不同异常进行不同处理,我们分次捕获处理GIL全局解释器锁同一时间只能有一个线程执行,CPython(IPython)的特点,其他解释器不存在cpu密集型:多进程+进程池io密集型:多线程/协程什么是Cython将python解释成C代码工具生成器和迭代器实现__next__和__iter__方法的对象就是迭代器可迭代对象只需要实现__iter__方法使用生成器表达式或者yield的生成器函数(生成器是一种特殊的迭代器)什么是协程比线程更轻量的多任务方式实现方式yieldasync-awiatdict底层结构为了支持快速查找使用了哈希表作为底层结构哈希表平均查找时间复杂度为o(1)CPython解释器使用二次探查解决哈希冲突问题Hash扩容和Hash冲突解决方案循环复制到新空间实现扩容冲突解决:链接法二次探查(开放寻址法):python使用 for gevent import monkey monkey.patch_all() #将代码中所有的阻塞方法都进行修改,可以指定具体要修改的方法判断是否为生成器或者协程 co_flags = func.code.co_flags # 检查是否是协程 if co_flags & 0x180: return func # 检查是否是生成器 if co_flags & 0x20: return func 斐波那契解决的问题及变形#一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。#请问用n个21的小矩形无重叠地覆盖一个2n的大矩形,总共有多少种方法?#方式一:fib = lambda n: n if n <= 2 else fib(n - 1) + fib(n - 2)+fib(n-3)#方式二:def fib(n): a, b = 0, 1 for _ in range(n): a, b = b, a + b return b#一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。fib = lambda n: n if n < 2 else 2 * fib(n - 1)获取电脑设置的环境变量 import os os.getenv(env_name,None)#获取环境变量如果不存在为None垃圾回收机制引用计数标记清除分代回收 #查看分代回收触发 import gc gc.get_threshold() #output:(700, 10, 10)True和False在代码中完全等价于1和0,可以直接和数字进行计算,inf表示无穷大C10M/C10KC10M:8核心cpu,64G内存,在10gbps的网络上保持1000万并发连接C10K:1GHz CPU,2G内存,1gbps网络环境下保持1万个客户端提供FTP服务yield from与yield的区别:yield from跟的是一个可迭代对象,而yield后面没有限制GeneratorExit生成器停止时触发单下划线的几种使用在定义变量时,表示为私有变量在解包时,表示舍弃无用的数据在交互模式中表示上一次代码执行结果可以做数字的拼接(111_222_333)使用break就不会执行else10进制转2进制 def conver_bin(num): if num == 0: return num re = [] while num: num, rem = divmod(num,2) re.append(str(rem)) return “".join(reversed(re)) conver_bin(10)list1 = [‘A’, ‘B’, ‘C’, ‘D’] 如何才能得到以list中元素命名的新列表 A=[],B=[],C=[],D=[]呢 list1 = [‘A’, ‘B’, ‘C’, ‘D’] # 方法一 for i in list1: globals()[i] = [] # 可以用于实现python版反射 # 方法二 for i in list1: exec(f’{i} = []’) # exec执行字符串语句memoryview与bytearray$\color{#000}(不常用,只是看到了记载一下)$ # bytearray是可变的,bytes是不可变的,memoryview不会产生新切片和对象 a = ‘aaaaaa’ ma = memoryview(a) ma.readonly # 只读的memoryview mb = ma[:2] # 不会产生新的字符串 a = bytearray(‘aaaaaa’) ma = memoryview(a) ma.readonly # 可写的memoryview mb = ma[:2] # 不会会产生新的bytearray mb[:2] = ‘bb’ # 对mb的改动就是对ma的改动Ellipsis类型# 代码中出现…省略号的现象就是一个Ellipsis对象L = [1,2,3]L.append(L)print(L) # output:[1,2,3,[…]]lazy惰性计算 class lazy(object): def init(self, func): self.func = func def get(self, instance, cls): val = self.func(instance) #其相当于执行的area(c),c为下面的Circle对象 setattr(instance, self.func.name, val) return val` class Circle(object): def init(self, radius): self.radius = radius @lazy def area(self): print(’evalute’) return 3.14 * self.radius ** 2遍历文件,传入一个文件夹,将里面所有文件的路径打印出来(递归)all_files = [] def getAllFiles(directory_path): import os for sChild in os.listdir(directory_path): sChildPath = os.path.join(directory_path,sChild) if os.path.isdir(sChildPath): getAllFiles(sChildPath) else: all_files.append(sChildPath) return all_files文件存储时,文件名的处理#secure_filename将字符串转化为安全的文件名from werkzeug import secure_filenamesecure_filename(“My cool movie.mov”) # output:My_cool_movie.movsecure_filename(”../../../etc/passwd") # output:etc_passwdsecure_filename(u’i contain cool \xfcml\xe4uts.txt’) # output:i_contain_cool_umlauts.txt日期格式化from datetime import datetimedatetime.now().strftime("%Y-%m-%d")import time#这里只有localtime可以被格式化,time是不能格式化的time.strftime("%Y-%m-%d",time.localtime())tuple使用+=奇怪的问题# 会报错,但是tuple的值会改变,因为t[1]id没有发生变化t=(1,[2,3])t[1]+=[4,5]# t[1]使用append\extend方法并不会报错,并可以成功执行__missing__你应该知道class Mydict(dict): def missing(self,key): # 当Mydict使用切片访问属性不存在的时候返回的值 return key+与+=# +不能用来连接列表和元祖,而+=可以(通过iadd实现,内部实现方式为extends(),所以可以增加元组),+会创建新对象不可变对象没有__iadd__方法,所以直接使用的是__add__方法,因此元祖可以使用+=进行元祖之间的相加如何将一个可迭代对象的每个元素变成一个字典的所有键?dict.fromkeys([‘jim’,‘han’],21) # output:{‘jim’: 21, ‘han’: 21}wireshark抓包软件网络知识什么是HTTPS?安全的HTTP协议,https需要cs证书,数据加密,端口为443,安全,同一网站https seo排名会更高常见响应状态码 204 No Content //请求成功处理,没有实体的主体返回,一般用来表示删除成功 206 Partial Content //Get范围请求已成功处理 303 See Other //临时重定向,期望使用get定向获取 304 Not Modified //求情缓存资源 307 Temporary Redirect //临时重定向,Post不会变成Get 401 Unauthorized //认证失败 403 Forbidden //资源请求被拒绝 400 //请求参数错误 201 //添加或更改成功 503 //服务器维护或者超负载http请求方法的幂等性及安全性WSGI # environ:一个包含所有HTTP请求信息的dict对象 # start_response:一个发送HTTP响应的函数 def application(environ, start_response): start_response(‘200 OK’, [(‘Content-Type’, ’text/html’)]) return ‘<h1>Hello, web!</h1>‘RPCCDNSSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。SSH(安全外壳协议) 为 Secure Shell 的缩写,由 IETF 的网络小组(Network Working Group)所制定;SSH 为建立在应用层基础上的安全协议。SSH 是目前较可靠,专为远程登录会话和其他网络服务提供安全性的协议。利用 SSH 协议可以有效防止远程管理过程中的信息泄露问题。SSH最初是UNIX系统上的一个程序,后来又迅速扩展到其他操作平台。SSH在正确使用时可弥补网络中的漏洞。SSH客户端适用于多种平台。几乎所有UNIX平台—包括HP-UX、Linux、AIX、Solaris、Digital UNIX、Irix,以及其他平台,都可运行SSH。TCP/IPTCP:面向连接/可靠/基于字节流UDP:无连接/不可靠/面向报文三次握手四次挥手三次握手(SYN/SYN+ACK/ACK)四次挥手(FIN/ACK/FIN/ACK)为什么连接的时候是三次握手,关闭的时候却是四次握手?因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。XSS/CSRFHttpOnly禁止js脚本访问和操作Cookie,可以有效防止XSSMysql索引改进过程线性结构->二分查找->hash->二叉查找树->平衡二叉树->多路查找树->多路平衡查找树(B-Tree)Mysql面试总结基础篇Mysql面试总结进阶篇深入浅出Mysql清空整个表时,InnoDB是一行一行的删除,而MyISAM则会从新删除建表text/blob数据类型不能有默认值,查询时不存在大小写转换什么时候索引失效以%开头的like模糊查询出现隐士类型转换没有满足最左前缀原则对于多列索引,不是使用的第一部分,则不会使用索引失效场景:应尽量避免在 where 子句中使用 != 或 <> 操作符,否则引擎将放弃使用索引而进行全表扫描尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,即使其中有条件带索引也不会使用,这也是为什么尽量少用 or 的原因如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不会使用索引应尽量避免在 where 子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描例如:select id from t where substring(name,1,3) = ‘abc’ – name;以abc开头的,应改成:select id from t where name like ‘abc%’ 例如:select id from t where datediff(day, createdate, ‘2005-11-30’) = 0 – ‘2005-11-30’;应改为:不要在 where 子句中的 “=” 左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描如:select id from t where num/2 = 100 应改为:select id from t where num = 1002;不适合键值较少的列(重复数据较多的列)比如:set enum列就不适合(枚举类型(enum)可以添加null,并且默认的值会自动过滤空格集合(set)和枚举类似,但只可以添加64个值)如果MySQL估计使用全表扫描要比使用索引快,则不使用索引什么是聚集索引B+Tree叶子节点保存的是数据还是指针MyISAM索引和数据分离,使用非聚集InnoDB数据文件就是索引文件,主键索引就是聚集索引Redis命令总结为什么这么快?基于内存,由C语言编写使用多路I/O复用模型,非阻塞IO使用单线程减少线程间切换因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了(毕竟采用多线程会有很多麻烦!)。数据结构简单自己构建了VM机制,减少调用系统函数的时间优势性能高 – Redis能读的速度是110000次/s,写的速度是81000次/s丰富的数据类型原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行丰富的特性 – Redis还支持 publish/subscribe(发布/订阅), 通知, key 过期等等特性什么是redis事务?将多个请求打包,一次性、按序执行多个命令的机制通过multi,exec,watch等命令实现事务功能Python redis-py pipeline=conn.pipeline(transaction=True)持久化方式RDB(快照)save(同步,可以保证数据一致性)bgsave(异步,shutdown时,无AOF则默认使用)AOF(追加日志)怎么实现队列pushrpop常用的数据类型(Bitmaps,Hyperloglogs,范围查询等不常用)String(字符串):计数器整数或sds(Simple Dynamic String)List(列表):用户的关注,粉丝列表ziplist(连续内存块,每个entry节点头部保存前后节点长度信息实现双向链表功能)或double linked listHash(哈希):Set(集合):用户的关注者intset或hashtableZset(有序集合):实时信息排行榜skiplist(跳跃表)与Memcached区别Memcached只能存储字符串键Memcached用户只能通过APPEND的方式将数据添加到已有的字符串的末尾,并将这个字符串当做列表来使用。但是在删除这些元素的时候,Memcached采用的是通过黑名单的方式来隐藏列表里的元素,从而避免了对元素的读取、更新、删除等操作Redis和Memcached都是将数据存放在内存中,都是内存数据库。不过Memcached还可用于缓存其他东西,例如图片、视频等等虚拟内存–Redis当物理内存用完时,可以将一些很久没用到的Value 交换到磁盘存储数据安全–Memcached挂掉后,数据没了;Redis可以定期保存到磁盘(持久化)应用场景不一样:Redis出来作为NoSQL数据库使用外,还能用做消息队列、数据堆栈和数据缓存等;Memcached适合于缓存SQL语句、数据集、用户临时性数据、延迟查询数据和Session等Redis实现分布式锁使用setnx实现加锁,可以同时通过expire添加超时时间锁的value值可以是一个随机的uuid或者特定的命名释放锁的时候,通过uuid判断是否是该锁,是则执行delete释放锁常见问题缓存雪崩短时间内缓存数据过期,大量请求访问数据库缓存穿透请求访问数据时,查询缓存中不存在,数据库中也不存在缓存预热初始化项目,将部分常用数据加入缓存缓存更新数据过期,进行更新缓存数据缓存降级当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级一致性Hash算法使用集群的时候保证数据的一致性基于redis实现一个分布式锁,要求一个超时的参数setnx虚拟内存内存抖动LinuxUnix五种i/o模型阻塞io非阻塞io多路复用io(Python下使用selectot实现io多路复用)select并发不高,连接数很活跃的情况下poll比select提高的并不多epoll适用于连接数量较多,但活动链接数少的情况信号驱动io异步io(Gevent/Asyncio实现异步)比man更好使用的命令手册tldr:一个有命令示例的手册kill -9和-15的区别-15:程序立刻停止/当程序释放相应资源后再停止/程序可能仍然继续运行-9:由于-15的不确定性,所以直接使用-9立即杀死进程分页机制(逻辑地址和物理地址分离的内存分配管理方案):操作系统为了高效管理内存,减少碎片程序的逻辑地址划分为固定大小的页物理地址划分为同样大小的帧通过页表对应逻辑地址和物理地址分段机制为了满足代码的一些逻辑需求数据共享/数据保护/动态链接每个段内部连续内存分配,段和段之间是离散分配的查看cpu内存使用情况?topfree 查看可用内存,排查内存泄漏问题设计模式单例模式 # 方式一 def Single(cls,*args,**kwargs): instances = {} def get_instance (*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return get_instance @Single class B: pass # 方式二 class Single: def init(self): print(“单例模式实现方式二。。。”) single = Single() del Single # 每次调用single就可以了 # 方式三(最常用的方式) class Single: def new(cls,*args,**kwargs): if not hasattr(cls,’_instance’): cls._instance = super().new(cls,*args,**kwargs) return cls._instance 工厂模式 class Dog: def init(self): print(“Wang Wang Wang”) class Cat: def init(self): print(“Miao Miao Miao”) def fac(animal): if animal.lower() == “dog”: return Dog() if animal.lower() == “cat”: return Cat() print(“对不起,必须是:dog,cat”)构造模式 class Computer: def init(self,serial_number): self.serial_number = serial_number self.memory = None self.hadd = None self.gpu = None def str(self): info = (f’Memory:{self.memoryGB}’, ‘Hard Disk:{self.hadd}GB’, ‘Graphics Card:{self.gpu}’) return ‘’.join(info) class ComputerBuilder: def init(self): self.computer = Computer(‘Jim1996’) def configure_memory(self,amount): self.computer.memory = amount return self #为了方便链式调用 def configure_hdd(self,amount): pass def configure_gpu(self,gpu_model): pass class HardwareEngineer: def init(self): self.builder = None def construct_computer(self,memory,hdd,gpu) self.builder = ComputerBuilder() self.builder.configure_memory(memory).configure_hdd(hdd).configure_gpu(gpu) @property def computer(self): return self.builder.computer数据结构和算法内置数据结构和算法python实现各种数据结构快速排序 def quick_sort(_list): if len(_list) < 2: return _list pivot_index = 0 pivot = _list(pivot_index) left_list = [i for i in _list[:pivot_index] if i < pivot] right_list = [i for i in _list[pivot_index:] if i > pivot] return quick_sort(left) + [pivot] + quick_sort(right)选择排序 def select_sort(seq): n = len(seq) for i in range(n-1) min_idx = i for j in range(i+1,n): if seq[j] < seq[min_inx]: min_idx = j if min_idx != i: seq[i], seq[min_idx] = seq[min_idx],seq[i]插入排序 def insertion_sort(_list): n = len(_list) for i in range(1,n): value = _list[i] pos = i while pos > 0 and value < _list[pos - 1] _list[pos] = _list[pos - 1] pos -= 1 _list[pos] = value print(sql)归并排序 def merge_sorted_list(_list1,_list2): #合并有序列表 len_a, len_b = len(_list1),len(_list2) a = b = 0 sort = [] while len_a > a and len_b > b: if _list1[a] > _list2[b]: sort.append(_list2[b]) b += 1 else: sort.append(_list1[a]) a += 1 if len_a > a: sort.append(_list1[a:]) if len_b > b: sort.append(_list2[b:]) return sort def merge_sort(_list): if len(list1)<2: return list1 else: mid = int(len(list1)/2) left = mergesort(list1[:mid]) right = mergesort(list1[mid:]) return merge_sorted_list(left,right)堆排序heapq模块 from heapq import nsmallest def heap_sort(_list): return nsmallest(len(_list),_list)栈 from collections import deque class Stack: def init(self): self.s = deque() def peek(self): p = self.pop() self.push(p) return p def push(self, el): self.s.append(el) def pop(self): return self.pop()队列 from collections import deque class Queue: def init(self): self.s = deque() def push(self, el): self.s.append(el) def pop(self): return self.popleft()二分查找 def binary_search(_list,num): mid = len(_list)//2 if len(_list) < 1: return Flase if num > _list[mid]: BinarySearch(_list[mid:],num) elif num < _list[mid]: BinarySearch(_list[:mid],num) else: return _list.index(num)面试加强题:关于数据库优化及设计如何使用两个栈实现一个队列反转链表合并两个有序链表删除链表节点反转二叉树设计短网址服务?62进制实现设计一个秒杀系统(feed流)?为什么mysql数据库的主键使用自增的整数比较好?使用uuid可以吗?为什么?如果InnoDB表的数据写入顺序能和B+树索引的叶子节点顺序一致的话,这时候存取效率是最高的。为了存储和查询性能应该使用自增长id做主键。对于InnoDB的主索引,数据会按照主键进行排序,由于UUID的无序性,InnoDB会产生巨大的IO压力,此时不适合使用UUID做物理主键,可以把它作为逻辑主键,物理主键依然使用自增ID。为了全局的唯一性,应该用uuid做索引关联其他表或做外键如果是分布式系统下我们怎么生成数据库的自增id呢?使用redis基于redis实现一个分布式锁,要求一个超时的参数setnx()如果redis单个节点宕机了,如何处理?还有其他业界的方案实现分布式锁码?使用hash一致算法缓存算法LRU(least-recently-used):替换最近最少使用的对象LFU(Least frequently used):最不经常使用,如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小服务端性能优化方向使用数据结构和算法数据库索引优化慢查询消除slow_query_log_file开启并且查询慢查询日志通过explain排查索引问题调整数据修改索引批量操作,从而减少io操作使用NoSQL:比如Redis网络io批量操作pipeline缓存Redis异步Asyncio实现异步操作使用Celery减少io阻塞并发多线程Gevent ...

April 2, 2019 · 6 min · jiezi

mysql查询优化总结

使用慢查询日志去发现慢查询,使用执行计划去判断查询是否正常运行,总是去测试你的查询看看是否他们运行在最佳状态下。久而久之性能总会变化,避免在整个表上使用count(*),它可能锁住整张表,使查询保持一致以便后续相似的查询可以使用查询缓存 ,在适当的情形下使用GROUP BY而不是DISTINCT,,保持索引简单,不在多个索引中包含同一个列,有时候MySQL会使用错误的索引,对于这种情况使用USE INDEX,检查使用SQL_MODE=STRICT的问题,对于记录数小于5的索引字段,在UNION的时候使用LIMIT不是是用OR。 为了 避免在更新前SELECT,使用INSERT ON DUPLICATE KEY或者INSERT IGNORE ,不要用UPDATE去实现,不要使用 MAX,使用索引字段和ORDER BY子句,LIMIT M,N实际上可以减缓查询在某些情况下,有节制地使用,在WHERE子句中使用UNION代替子查询,考虑持久连接,而不是多个连接,以减少开销,基准查询,包括使用服务器上的负载,有时一个简单的查询可以影响其他查询,当负载增加您的服务器上,使用SHOW PROCESSLIST查看慢的和有问题的查询,在开发环境中产生的镜像数据中 测试的所有可疑的查询。EXPLAIN SELECT 查询用来跟踪查看效果 使用 EXPLAIN 关键字可以让你知道MySQL是如何处理你的SQL语句的。这可以帮你分析你的查询语句或是表结构的性能瓶颈。EXPLAIN 的查询结果还会告诉你你的索引主键被如何利用的,你的数据表是如何被搜索和排序的……等等

April 2, 2019 · 1 min · jiezi

了解MySQL中EXPLAIN解释命令

1 EXPLAIN概念EXPLAIN会向我们提供一些MySQL是执行sql的信息:EXPLAIN可以解释说明 SELECT, DELETE, INSERT, REPLACE, and UPDATE 等语句.当EXPLAIN与可解释的语句一起使用时,mysql会显示一些来自于优化器的关于sql执行计划的信息。即mysql解释它是如何处理这些语句的,和表之间是如何连接的。想获取更多关于EXPLAIN如何获取执行计划信息的。当EXPLAIN后面是一个会话的connection_id 而不是一个可执行的语句时,它会展示会话的信息。对于SELECT语句,EXPLAIN会产生额外的执行计划信息,这些信息可以用SHOW WARNINGS显示出来。EXPLAIN对于检查设计分区表的查询时非常有用。FORMAT选项可以用于选择输出格式,如果没有配置FORMAT选项,默认已表格形式输出。JSON 选项让信息已json格式展示。2 EXPLAIN 输出列信息EXPLAIN输出的字段信息第一列:列名, 第二列:FORMAT = JSON时输出中显示的等效属性名称 ,第三列:字段含义ColumnJSON NameMeaningidselect_idselect标识号select_typeNoneselect类型tabletable_name这一行数据是关于哪张表的partitionspartitions匹配的分区,对于未分区表,该值为空typeaccess_type使用的连接类别,有无使用索引possible_keyspossible_keysMySQL能使用哪个索引在该表中找到行keykeyMySQL实际决定使用的键(索引)key_lenkey_lengthMySQL决定使用的键长度。如果键是NULL,长度为NULLrefref与索引关联的列rowsrowsmysql认为执行sql时必须被校验的行数filteredfiltered表示此查询条件所过滤的数据的百分比ExtraNone附加信息2.1 idSELECT标识符。SELECT在查询中的序列号,可以为空。2.2 select_typeSELECT类型,所有类型在下表中展示,JSON格式的EXPLAIN将SELECT类型公开为query_block的属性,除非它是SIMPLE或PRIMARY。 JSON名称(不适用为None)也显示在表中。select_type ValueJSON NameMeaningSIMPLENone简单SELECT(不使用UNION或子查询等)PRIMARYNone嵌套查询时最外层的查询UNIONNoneUNION中的第二个或后面的SELECT语句DEPENDENT UNIONdependent (true)UNION中的第二个或以后的SELECT语句,取决于外部查询UNION RESULTunion_resultUNION的结果SUBQUERYNone子查询中的第一个选择DEPENDENT SUBQUERYdependent (true)子查询中的第一个选择,取决于外部查询DERIVEDNone派生表(子查询中产生的临时表)MATERIALIZEDmaterialized_from_subquery物化子查询UNCACHEABLE SUBQUERYcacheable (false)无法缓存结果的子查询,必须对外部查询的每一行进行重新计算UNCACHEABLE UNIONcacheable (false)UNION中属于不可缓存子查询的第二个或以后的选择(请参 UNCACHEABLE SUBQUERY)表信息(后面演示用):mysql> show create table t_a;——+| t_a | CREATE TABLE t_a ( id bigint(20) NOT NULL DEFAULT ‘0’, age int(20) DEFAULT NULL, code int(20) NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_code (code), KEY age_key (age)) ENGINE=InnoDB DEFAULT CHARSET=gbk |+——-+—————————————–+1 row in set (0.03 sec)SIMPLE:简单SELECT(不使用UNION或子查询等)mysql> explain select * from t_a where id =1;+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+——-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+——-+| 1 | SIMPLE | t_a | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL |+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+——-+1 row in set, 1 warning (0.03 sec)PRIMARY:嵌套查询时最外层的查询mysql> explain select * from t_a where num >(select num from t_a where id = 3);+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+————————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+————————–+| 1 | PRIMARY | t_a | NULL | range | num_key | num_key | 5 | NULL | 6 | 100.00 | Using where; Using index || 2 | SUBQUERY | t_a | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL |+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+————————–+2 rows in set, 1 warning (0.03 sec)UNION:UNION中的第二个或后面的SELECT语句mysql> explain select * from t_a where id =9 union all select * from t_a;+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+————-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+————-+| 1 | PRIMARY | t_a | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL || 2 | UNION | t_a | NULL | index | NULL | num_key | 5 | NULL | 9 | 100.00 | Using index |+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+————-+2 rows in set, 1 warning (0.04 sec)DEPENDENT UNION:UNION中的第二个或以后的SELECT语句,取决于外部查询mysql> explain select * from t_a where id in (select id from t_a where id >8 union all select id from t_a where id =5);+—-+——————–+——-+————+——–+—————+———+———+——-+——+———-+————————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+——————–+——-+————+——–+—————+———+———+——-+——+———-+————————–+| 1 | PRIMARY | t_a | NULL | index | NULL | num_key | 5 | NULL | 9 | 100.00 | Using where; Using index || 2 | DEPENDENT SUBQUERY | t_a | NULL | eq_ref | PRIMARY | PRIMARY | 8 | func | 1 | 100.00 | Using where; Using index || 3 | DEPENDENT UNION | t_a | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | Using index |+—-+——————–+——-+————+——–+—————+———+———+——-+——+———-+————————–+3 rows in set, 1 warning (0.08 sec)UNION RESULT:UNION的结果mysql> explain select num from t_a where id = 3 union select num from t_a where id =4;+—-+————–+————+————+——-+—————+———+———+——-+——+———-+—————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————–+————+————+——-+—————+———+———+——-+——+———-+—————–+| 1 | PRIMARY | t_a | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL || 2 | UNION | t_a | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL || NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |+—-+————–+————+————+——-+—————+———+———+——-+——+———-+—————–+3 rows in set, 1 warning (0.03 sec)SUBQUERY:子查询中的第一个选择mysql> explain select * from t_a where num >(select num from t_a where id = 3);+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+————————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+————————–+| 1 | PRIMARY | t_a | NULL | range | num_key | num_key | 5 | NULL | 6 | 100.00 | Using where; Using index || 2 | SUBQUERY | t_a | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL |+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+————————–+2 rows in set, 1 warning (0.03 sec)DEPENDENT SUBQUERY:子查询中的第一个选择,取决于外部查询mysql> explain select * from t_a where num in(select num from t_a where id = 3 union select num from t_a where id =4);+—-+——————–+————+————+——-+—————–+———+———+——-+——+———-+————————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+——————–+————+————+——-+—————–+———+———+——-+——+———-+————————–+| 1 | PRIMARY | t_a | NULL | index | NULL | num_key | 5 | NULL | 9 | 100.00 | Using where; Using index || 2 | DEPENDENT SUBQUERY | t_a | NULL | const | PRIMARY,num_key | PRIMARY | 8 | const | 1 | 100.00 | NULL || 3 | DEPENDENT UNION | t_a | NULL | const | PRIMARY,num_key | PRIMARY | 8 | const | 1 | 100.00 | NULL || NULL | UNION RESULT | <union2,3> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |+—-+——————–+————+————+——-+—————–+———+———+——-+——+———-+————————–+4 rows in set, 1 warning (0.12 sec)DERIVED:派生表(子查询中产生的临时表)mysql> explain select a.id from (select id from t_a where id >8 union all select id from t_a where id =5) a;+—-+————-+————+————+——-+—————+———+———+——-+——+———-+————————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+————+————+——-+—————+———+———+——-+——+———-+————————–+| 1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | NULL || 2 | DERIVED | t_a | NULL | range | PRIMARY | PRIMARY | 8 | NULL | 1 | 100.00 | Using where; Using index || 3 | UNION | t_a | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | Using index |+—-+————-+————+————+——-+—————+———+———+——-+——+———-+————————–+3 rows in set, 1 warning (0.12 sec)2.3 table显示这一行的数据是关于哪张表的,有时是真实的表名字,有时也可能是以下几种结果<unionM,N>: 指id为M,N行结果的并集<derivedN>: 该行是指id值为n的行的派生表结果。派生表可能来自例如from子句中的子查询。<subqueryN>: 该行是指id值为n的行的物化子查询的结果。2.4 partitions查询的记录所属于的分区,对于未分区表,该值为NULL。2.5 type连接使用了哪种类别,有无使用索引,常用的类型有:system, const, eq_ref, ref, range, index, ALL(从左到右,性能越来越差),详情查看 EXPLAIN Join TypesNULL:MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成system:这个表(也可能是查询出来的临时表)只有一行数据 (= system table). 是const中的一个特例const:表最多有一个匹配行,它将在查询开始时被读取。因为仅有一行,在这行的列值可被优化器剩余部分认为是常数。const表很快,因为它们只读取一次!const用于查询条件为PRIMARY KEY或UNIQUE索引并与常数值进行比较时的所有部分。 在下面的查询中,tbl_name可以用于const表:SELECT * from tbl_name WHERE primary_key=1;SELECT * from tbl_name WHERE primary_key_part1=1和 primary_key_part2=2;–例子mysql> explain select * from t_a where id =1;+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+——-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+——-+| 1 | SIMPLE | t_a | NULL | const | PRIMARY | PRIMARY | 8 | const | 1 | 100.00 | NULL |+—-+————-+——-+————+——-+—————+———+———+——-+——+———-+——-+1 row in set, 1 warning (0.07 sec)eq_ref:对于前几个表中的每一行组合,从该表中读取一行。除了system和const,这是最好的连接类型。当连接使用索引的所有部分,并且索引是主键或唯一非空索引时,将使用它。eq_ref可以用于使用= 操作符比较的带索引的列。比较值可以为常量或一个使用在该表前面所读取的表的列的表达式。在下面的例子中,MySQL可以使用eq_ref联接去处理ref_tables:SELECT * FROM ref_table,other_table WHERE ref_table.key_column=other_table.column;SELECT * FROM ref_table,other_table WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1;–例子(t_b为t_a的复制表,表结构相同)mysql> explain select * from t_a,t_b where t_a.code=t_b.code;+—-+————-+——-+————+——–+—————+———+———+—————+——+———-+——-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——–+—————+———+———+—————+——+———-+——-+| 1 | SIMPLE | t_a | NULL | ALL | uk_code | NULL | NULL | NULL | 9 | 100.00 | NULL || 1 | SIMPLE | t_b | NULL | eq_ref | uk_code | uk_code | 4 | test.t_a.code | 1 | 100.00 | NULL |+—-+————-+——-+————+——–+—————+———+———+—————+——+———-+——-+2 rows in set, 1 warning (0.03 sec)ref对于每个来自于前面的表的行组合,所有有匹配索引值的行将从这张表中读取。如果联接只使用键的最左边的前缀,或如果键不是UNIQUE或PRIMARY KEY(换句话说,如果联接不能基于关键字查询结果为单个行的话),则使用ref。如果使用的键仅仅匹配少量行,该联接类型是不错的。ref可以用于使用=或<=>操作符的带索引的列。在下面的例子中,MySQL可以使用ref联接来处理ref_tables:SELECT * FROM ref_table WHERE key_column=expr;SELECT * FROM ref_table,other_table WHERE ref_table.key_column=other_table.column;SELECT * FROM ref_table,other_table WHERE ref_table.key_column_part1=other_table.column AND ref_table.key_column_part2=1;–例子(t_b为t_a的复制表,表结构相同)mysql> explain select * from t_a,t_b where t_a.age=t_b.age;+—-+————-+——-+————+——+—————+———+———+————–+——+———-+————-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——+—————+———+———+————–+——+———-+————-+| 1 | SIMPLE | t_a | NULL | ALL | age_key | NULL | NULL | NULL | 9 | 100.00 | Using where || 1 | SIMPLE | t_b | NULL | ref | age_key | age_key | 5 | test.t_a.age | 1 | 100.00 | NULL |+—-+————-+——-+————+——+—————+———+———+————–+——+———-+————-+2 rows in set, 1 warning (0.03 sec)fulltext:使用FULLTEXT索引执行连接ref_or_null:该联接类型ref类似,但是添加了MySQL可以专门搜索包含NULL值的行。在解决子查询中经常使用该联接类型的优化。 在下面的例子中,MySQL可以使用ref_or_null联接来处理ref_tables:SELECT * FROM ref_table WHERE key_column=expr OR key_column IS NULL;–例子mysql> explain select * from t_a where t_a.age =3 or t_a.age is null;+—-+————-+——-+————+————-+—————+———+———+——-+——+———-+———————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+————-+—————+———+———+——-+——+———-+———————–+| 1 | SIMPLE | t_a | NULL | ref_or_null | age_key | age_key | 5 | const | 2 | 100.00 | Using index condition |+—-+————-+——-+————+————-+—————+———+———+——-+——+———-+———————–+1 row in set, 1 warning (0.03 sec)index_merge:该联接类型表示使用了索引合并优化方法。在这种情况下,key列包含了使用的索引的清单,key_len包含了使用的索引的最长的关键元素。SELECT * FROM ref_table WHERE idx1=expr1 OR idx2 =expr2;–例子mysql> explain select * from t_a where t_a.code =3 or t_a.age = 3;+—-+————-+——-+————+————-+—————–+—————–+———+——+——+———-+——————————————-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+————-+—————–+—————–+———+——+——+———-+——————————————-+| 1 | SIMPLE | t_a | NULL | index_merge | uk_code,age_key | uk_code,age_key | 4,5 | NULL | 2 | 100.00 | Using union(uk_code,age_key); Using where |+—-+————-+——-+————+————-+—————–+—————–+———+——+——+———-+——————————————-+1 row in set, 1 warning (0.03 sec)unique_subquery:该类型替换了下面形式的IN子查询的ref:value IN (SELECT primary_key FROM single_table WHERE some_expr) unique_subquery是一个索引查找函数,可以完全替换子查询,效率更高。index_subquery:该联接类型类似于unique_subquery。可以替换IN子查询,但只适合下列形式的子查询中的非唯一索引:value IN (SELECT key_column FROM single_table WHERE some_expr)range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引。key_len包含所使用索引的最长关键元素。在该类型中ref列为NULL。当使用=、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN或者IN操作符,用常量比较关键字列时,可以使用rangemysql> explain select * from t_a where id > 8;+—-+————-+——-+————+——-+—————+———+———+——+——+———-+————-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+——-+————+——-+—————+———+———+——+——+———-+————-+| 1 | SIMPLE | t_a | NULL | range | PRIMARY | PRIMARY | 8 | NULL | 1 | 100.00 | Using where |+—-+————-+——-+————+——-+—————+———+———+——+——+———-+————-+1 row in set, 1 warning (0.03 sec)index:该联接类型与ALL相同,除了只有索引树被扫描。这通常比ALL快,因为索引文件通常比数据文件小。当查询只使用作为单索引一部分的列时,MySQL可以使用该联接类型。ALL:对于每个来自于先前的表的行组合,进行完整的表扫描。如果表是第一个没标记const的表,这通常不好,并且通常在它情况下很差。通常可以增加更多的索引而不要使用ALL,使得行能基于前面的表中的常数值或列值被检索出。2.6 possible_keyspossible_keys列指出MySQL能使用哪个索引在该表中找到行。注意,该列完全独立于EXPLAIN输出所示的表的次序。这意味着在possible_keys中的某些键实际上不能按生成的表次序使用。如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查WHERE子句看是否它引用某些列或适合索引的列来提高你的查询性能。如果是这样,创造一个适当的索引并且再次用EXPLAIN检查查询2.7 keykey列显示MySQL实际决定使用的键(索引)。如果没有选择索引,键是NULL。要想强制MySQL使用或忽视possible_keys列中的索引,在查询中使用FORCE INDEX、USE INDEX或者IGNORE INDEX。2.8 key_lenkey_len列显示MySQL决定使用的键长度。如果键是NULL,则长度为NULL。 使用的索引的长度。在不损失精确性的情况下,长度越短越好2.9 refref列显示使用哪个列或常数与key一起从表中选择行。2.10 rowsrows列显示MySQL认为它执行查询时必须检查的行数。2.11 Extra该列包含MySQL解决查询的详细信息,下面详细.Distinct:一旦MYSQL找到了与行相联合匹配的行,就不再搜索了Not exists:MYSQL优化了LEFT JOIN,一旦它找到了匹配LEFT JOIN标准的行,就不再搜索了Range checked for each:没有找到理想的索引,因此对于从前面表中来的每一个行组合,MYSQL检查使用哪个索引,并用它来从表中返回行。这是使用索引的最慢的连接之一Using filesort:看到这个的时候,查询就需要优化了。MYSQL需要进行额外的步骤来发现如何对返回的行排序。它根据连接类型以及存储排序键值和匹配条件的全部行的行指针来排序全部行Using index:列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候Using temporary:看到这个的时候,查询需要优化了。这里,MYSQL需要创建一个临时表来存储结果,这通常发生在对不同的列集进行ORDER BY上,而不是GROUP BY上Using where:使用了WHERE从句来限制哪些行将与下一张表匹配或者是返回给用户。如果不想返回表中的全部行,并且连接类型ALL或index,这就会发生,或者是查询有问题参考:MySQL5.7 EXPLAIN Output Format ...

April 1, 2019 · 8 min · jiezi

了解MySQL死锁日志

锁的种类&概念Shared and Exclusive LocksShared lock: 共享锁,官方描述:permits the transaction that holds the lock to read a roweg:select * from xx where a=1 lock in share modeExclusive Locks:排他锁: permits the transaction that holds the lock to update or delete a roweg: select * from xx where a=1 for updateIntention Locks这个锁是加在table上的,表示要对下一个层级(记录)进行加锁Intention shared (IS):Transaction T intends to set S locks on individual rows in table tIntention exclusive (IX): Transaction T intends to set X locks on those rows在数据库层看到的结果是这样的:TABLE LOCK table lc_3.a trx id 133588125 lock mode IXRecord Locks在数据库层看到的结果是这样的:RECORD LOCKS space id 281 page no 3 n bits 72 index PRIMARY of table lc_3.a trx id 133588125 lock_mode X locks rec but not gap该锁是加在索引上的(从上面的index PRIMARY of table lc_3.a 就能看出来)记录锁可以有两种类型:lock_mode X locks rec but not gap && lock_mode S locks rec but not gapGap Locks在数据库层看到的结果是这样的:RECORD LOCKS space id 281 page no 5 n bits 72 index idx_c of table lc_3.a trx id 133588125 lock_mode X locks gap before rec Gap锁是用来防止insert的Gap锁,中文名间隙锁,锁住的不是记录,而是范围,比如:(negative infinity, 10),(10, 11)区间,这里都是开区间哦Next-Key Locks在数据库层看到的结果是这样的:RECORD LOCKS space id 281 page no 5 n bits 72 index idx_c of table lc_3.a trx id 133588125 lock_mode XNext-Key Locks = Gap Locks + Record Locks 的结合, 不仅仅锁住记录,还会锁住间隙, 比如: (negative infinity, 10】,(10, 11】区间,这些右边都是闭区间哦Insert Intention Locks在数据库层看到的结果是这样的:RECORD LOCKS space id 279 page no 3 n bits 72 index PRIMARY of table lc_3.t1 trx id 133587907 lock_mode X insert intention waitingInsert Intention Locks 可以理解为特殊的Gap锁的一种,用以提升并发写入的性能AUTO-INC Locks在数据库层看到的结果是这样的:TABLE LOCK table xx trx id 7498948 lock mode AUTO-INC waiting属于表级别的锁自增锁的详细情况可以之前的一篇文章:http://keithlan.github.io/2017/03/03/auto_increment_lock/显示锁 vs 隐示锁显示锁(explicit lock)显示的加锁,在show engine innoDB status 中能够看到 ,会在内存中产生对象,占用内存 eg: select … for update , select … lock in share mode 隐示锁(implicit lock)implicit lock 是在索引中对记录逻辑的加锁,但是实际上不产生锁对象,不占用内存空间 哪些语句会产生implicit lock 呢? eg: insert into xx values(xx) eg: update xx set t=t+1 where id = 1 ; 会对辅助索引加implicit lockimplicit lock 在什么情况下会转换成 explicit lock eg: 只有implicit lock 产生冲突的时候,会自动转换成explicit lock,这样做的好处就是降低锁的开销 eg: 比如:我插入了一条记录10,本身这个记录加上implicit lock,如果这时候有人再去更新这条10的记录,那么就会自动转换成explicit lock数据库怎么知道implicit lock的存在呢?如何实现锁的转化呢?对于聚集索引上面的记录,有db_trx_id,如果该事务id在活跃事务列表中,那么说明还没有提交,那么implicit则存在对于非聚集索引:由于上面没有事务id,那么可以通过上面的主键id,再通过主键id上面的事务id来判断,不过算法要非常复杂,这里不做介绍记录锁,间隙锁,Next-key 锁和插入意向锁。这四种锁对应的死锁如下:记录锁(LOCK_REC_NOT_GAP): lock_mode X locks rec but not gap间隙锁(LOCK_GAP): lock_mode X locks gap before recNext-key 锁(LOCK_ORNIDARY): lock_mode X插入意向锁(LOCK_INSERT_INTENTION): lock_mode X locks gap before rec insert intention表格信息: CREATE TABLE t_bitfly ( id bigint(20) NOT NULL DEFAULT ‘0’, num int(20) DEFAULT NULL, PRIMARY KEY (id), KEY num_key (num) ) ENGINE=InnoDB DEFAULT CHARSET=gbk; 表中数据: mysql> select * from t_bitfly; +—-+——+ | id | num | +—-+——+ | 1 | 2 | | 3 | 5 | | 8 | 7 | +—-+——+ 3 rows in set (0.04 sec) 数据库隔离级别为:可重复读(REPEATABLE-READ)模拟死锁场景:结果:insert into t_bitfly values(5,5)> 1213 - Deadlock found when trying to get lock; try restarting transaction> 时间: 0.085s查询日志 :show engine innodb status ;结果如下=====================================2018-08-05 21:20:27 0x7fd40c082700 INNODB MONITOR OUTPUT=====================================Per second averages calculated from the last 4 seconds—————–BACKGROUND THREAD—————–srv_master_thread loops: 251 srv_active, 0 srv_shutdown, 22663 srv_idlesrv_master_thread log flush and writes: 22905———-SEMAPHORES———-OS WAIT ARRAY INFO: reservation count 513OS WAIT ARRAY INFO: signal count 450RW-shared spins 0, rounds 569, OS waits 286RW-excl spins 0, rounds 127, OS waits 1RW-sx spins 0, rounds 0, OS waits 0Spin rounds per wait: 569.00 RW-shared, 127.00 RW-excl, 0.00 RW-sx————————LATEST DETECTED DEADLOCK————————2018-08-05 21:15:42 0x7fd40c0b3700*** (1) TRANSACTION:TRANSACTION 1095010, ACTIVE 21 sec insertingmysql tables in use 1, locked 1LOCK WAIT 5 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2MySQL thread id 16, OS thread handle 140548578129664, query id 3052 183.6.50.229 root updateinsert into t_bitfly values(7,7)*** (1) WAITING FOR THIS LOCK TO BE GRANTED:RECORD LOCKS space id 2514 page no 4 n bits 72 index num_key of table test.t_bitfly trx id 1095010 lock_mode X locks gap before rec insert intention waitingRecord lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 32 0: len 4; hex 80000007; asc ;; 1: len 8; hex 8000000000000008; asc ;;*** (2) TRANSACTION:TRANSACTION 1095015, ACTIVE 6 sec insertingmysql tables in use 1, locked 14 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2MySQL thread id 17, OS thread handle 140548711855872, query id 3056 183.6.50.229 root updateinsert into t_bitfly values(5,5)*** (2) HOLDS THE LOCK(S):RECORD LOCKS space id 2514 page no 4 n bits 72 index num_key of table test.t_bitfly trx id 1095015 lock_mode XRecord lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0 0: len 8; hex 73757072656d756d; asc supremum;;Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 32 0: len 4; hex 80000007; asc ;; 1: len 8; hex 8000000000000008; asc ;;*** (2) WAITING FOR THIS LOCK TO BE GRANTED:RECORD LOCKS space id 2514 page no 4 n bits 72 index num_key of table test.t_bitfly trx id 1095015 lock_mode X locks gap before rec insert intention waitingRecord lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 32 0: len 4; hex 80000007; asc ;; 1: len 8; hex 8000000000000008; asc ;;省略。。。一些注释:LATEST DETECTED DEADLOCK:标示为最新发生的死锁;(1) TRANSACTION:此处表示事务1开始 ;MySQL thread id 16, OS thread handle 140548578129664, query id 3052 183.6.50.229 root update:此处为记录当前数据库线程id;insert into t_bitfly values(7,7):表示事务1在执行的sql ,不过比较悲伤的事情是show engine innodb status 是查看不到完整的事务的sql 的,通常显示当前正在等待锁的sql;(1) WAITING FOR THIS LOCK TO BE GRANTED:此处表示当前事务1等待获取行锁;(2) TRANSACTION:此处表示事务2开始 ;insert into t_bitfly values(5,5):表示事务2在执行的sql(2) HOLDS THE LOCK(S):此处表示当前事务2持有的行锁;(2) WAITING FOR THIS LOCK TO BE GRANTED:此处表示当前事务2等待获取行锁;根据死锁日志可以看出:事务一在执行insert into t_bitfly values(7,7)时,插入意向锁加锁时卡住;事务二在执行insert into t_bitfly values(5,5)时,持有next-key锁,插入意向锁加锁时卡住。结合上面执行的sql来分析:事务一执行delete from t_bitfly where num = 5 ;后,获取了 Gap Locks + Record Locks 也就是 next-key锁;事务二执行delete from t_bitfly where num = 7 ;后,获取了 Gap Locks + Record Locks 也就是 next-key锁;事务一执行insert into t_bitfly values(7,7)时,持有next-key锁,插入意向锁,等待事务二的next-key锁解锁;事务二执行insert into t_bitfly values(5,5)时,持有next-key锁,插入意向锁,等待事务二的next-key锁解锁;产生死锁。 ...

April 1, 2019 · 4 min · jiezi

记一次 React + Koa + Mysql 构建个人博客

前言由于一直在用 vue 写业务,为了熟悉下 react 开发模式,所以选择了 react。数据库一开始用的是 mongodb,后来换成 mysql 了,一套下来感觉 mysql 也挺好上手的。react-router、koa、mysql 都是从0开始接触开发的,期间遇到过很多问题,印象最深的是 react-router 参考官方文档配置的,楞是跑不起来,花费了好几个小时,最后才发现看的文档是v1.0, 而项目中是v4.3, 好在可参考的资料比较多,问题都迎刃而解了。博客介绍前端项目通过 create-react-app 构建,server端通过 koa-generator 构建前后端分离,博客页、后台管理都在 blog-admin 里,对含有 /admin 的路由进行登录拦截前端: react + antd + react-router4 + axiosserver端: koa2 + mysql + sequelize部署:server端 运行在 3000 端口,前端 80 端口,nginx设置代理预览地址web端源码server端源码喜欢或对你有帮助,欢迎 star功能[x] 登录[x] 分页[x] 查询[x] 标签列表[x] 分类列表[x] 收藏列表[x] 文章列表[x] 发布文章时间轴[x] 文章访问次数统计[x] 回到顶部[x] 博客适配移动端[ ] 后台适配移动端[ ] 对文章访问次数进行可视化[ ] 留言评论[ ] 渲染优化、打包优化效果标签分类收藏文章编辑博客页响应式运行项目前端git clone https://github.com/gzwgq222/blog-admin.gitcd blog-adminnpm installlocalhost:2019server 端本地安装 mysql,新建 dev 数据库git clone https://github.com/gzwgq222/blog-server.gitcd blog-servernpm installserver 端前端 react + antd 开发,较为平缓,在此就不再叙述。主要记录下 koa + mysql 相关事宜全局安装 koa-generatornpm install -g koa-generato创建 node-server 项目koa node-server 安装依赖cd node-server npn install运行npm dev出现 Hello Koa 2! 表示运行成功先看routes文件index.jsconst router = require(‘koa-router’)()router.get(’/’, async (ctx, next) => { await ctx.render(‘index’, { title: ‘Hello Koa 2!’ })})router.get(’/string’, async (ctx, next) => { ctx.body = ‘koa2 string’})router.get(’/json’, async (ctx, next) => { ctx.body = { title: ‘koa2 json’ }})module.exports = routerusers.jsconst router = require(‘koa-router’)()router.prefix(’/users’)router.get(’/’, function (ctx, next) { ctx.body = ’this is a users response!’})router.get(’/bar’, function (ctx, next) { ctx.body = ’this is a users/bar response’})module.exports = router分别访问下列路由localhost:3000/stringlocalhost:3000/userslocalhost:3000/bar大概你已经猜到了,koa-router 定义路由访问时返回相应的内容,那我们只需要把相应的 data 返回去就行了,只是我们的数据得从数据库查询出来。本地安装 mysql项目安裝 mysqlnpm install mysql –save项目安裝 sequelizesequelize 是 ORM node框架,对SQL查询语句的封装,让我们可以用OOP的方式操作数据库npm install –save sequelize新建 sequelize.js,建立连接池const Sequelize = require(‘sequelize’);const sequelize = new Sequelize(‘dev’, ‘root’, ‘123456’, { host: ’localhost’, dialect: ‘mysql’, operatorsAliases: false, pool: { max: 5, min: 0, acquire: 30000, idle: 10000 }})sequelize .authenticate() .then(() => { console.log(‘MYSQL 连接成功……’); }) .catch(err => { console.error(‘链接失败:’, err); });// 根据模型自动创建表sequelize.sync()module.exports = sequelize创建 model、controllers 文件夹 定义model:定义表结构;controller:定义对数据库的查询方法以 tag.js 为例model => tag.jsconst sequelize = require(’../sequelize ‘)const Sequelize = require(‘sequelize’)const moment = require(‘moment’) // 日期处理库// 定义表结构const tag = sequelize.define(’tag’, { id: { type: Sequelize.INTEGER(11), // 设置字段类型 primaryKey: true, // 设置为主建 autoIncrement: true // 自增 }, name: { type: Sequelize.STRING, unique: { // 唯一 msg: ‘已添加’ } }, createdAt: { type: Sequelize.DATE, defaultValue: Sequelize.NOW, get() { // this.getDataValue 获取当前字段value return moment(this.getDataValue(‘createdAt’)).format(‘YYYY-MM-DD HH:mm’) } }, updatedAt: { type: Sequelize.DATE, defaultValue: Sequelize.NOW, get() { return moment(this.getDataValue(‘updatedAt’)).format(‘YYYY-MM-DD HH:mm’) } }},{ // sequelize会自动使用传入的模型名(define的第一个参数)的复数做为表名 设置true取消默认设置 freezeTableName: true})module.exports = tagcontroller => tag.s 定义了 create、findAll、findAndCountAll、destroy 方法const Tag = require(’../model/tag’)const Op = require(‘sequelize’).Opconst listAll = async (ctx) => { const data = await Tag.findAll() ctx.body = { code: 1000, data }}const list = async (ctx) => { const query = ctx.query const where = { name: { [Op.like]: %${query.name}% } } const {rows:data, count: total } = await Tag.findAndCountAll({ where, offset: (+query.pageNo - 1) * +query.pageSize, limit: +query.pageSize, order: [ [‘createdAt’, ‘DESC’] ] }) ctx.body = { data, total, code: 1000, desc: ‘success’ }}const create = async (ctx) => { const params = ctx.request.body if (!params.name) { ctx.body = { code: 1003, desc: ‘标签不能为空’ } return false } try { await Tag.create(params) ctx.body = { code: 1000, data: ‘创建成功’ } } catch(err) { const msg = err.errors[0] ctx.body = { code: 300, data: msg.value + msg.message } }}const destroy = async ctx => { await Tag.destroy({where: ctx.request.body}) ctx.body = { code: 1000, desc: ‘删除成功’ }}module.exports = { list, create, listAll, destroy在 routers 文件夹 index.js 中引入定义好的 tag controller ,定义路由const router = require(‘koa-router’)()const Tag = require(’../controllers/tag’)// tagrouter.get(’/tag/list’, Tag.list)router.get(’/tag/list/all’, Tag.listAll)router.post(’/tag/create’, Tag.create)router.post(’/tag/destroy’, Tag.destroy)module.exports = router/* 如每个 route 是单独的文件,可以使用 router.prefix 定义路由前缀router.prefix(’/tag’)router.get(’/list’, Tag.list)router.get(’/list/all’, Tag.listAll)router.post(’/create’, Tag.create)router.post(’/destroy’, Tag.destroy)*/因为 app 中 已经引入 routers 中的 index.js 调用了 app.use了,所以此处不需再引入在浏览器里输入 localhost:3000/tag/list 就可以看到返回的数据结构了,只不过 data 为空数组,因为我们还没添加进去任何数据到这里,model 定义表结构、sequelize操作数据库、koa-router 定义路由 这一套流程算是完成了,其他表结构,接口 都是一样定义的总结之前没有写过 node server 和 react,算是从零搭建该博客,踩了一些坑,也学到了很多东西,譬如react 开发模式、react-router、sequelize 操作mysql的crud、koa、nginx的配置等等。麻雀虽小,也是一次完整的前后端开发体验,脱离了浏览器的限制,像海贼王一样,打开了新世界的大门,寻找 onepiece ……web端源码server端源码详细的 server 端说明后续会在个人博客中添加关于此次部署文章Linksreactreact-router4antdreact-draft-wysiwygkoa2sequelize初尝 react + Node,错误之处还望斧正,欢迎提 issue ...

April 1, 2019 · 3 min · jiezi

Django后台 + Wordpress主题快速搭建个人博客

既然学习了Python Web怎么能没有自己的一个小站呢?没有自己精心打造的一个小站怎么敢说自己学习过 Python Web呢?说的再多不如直接干,我的个人网站也已经部署上线。Django后台 + Wordpress主题,只要自己看上的主题都可以让它变成自己的为什么要选择 Wordpress 主题呢?自己在刚开始学习Python Web时最大的困惑就是:Django后台开发没什么难度,但是想搭起自己的一个站点却难的让我无从下手,什么 HTML、CSS、Jquery、JS、AJAX的前端知识太多,听起来就头大,即使学会了前端技术,你能写出一个自己满意的前端页面吗?没有一点审美和设计能力,好像并不大行。当我遇到 Wordpress 时,这一切都变得如此简单,Wordpress社区有丰富的主题,可以挑出自己喜欢的随意摆弄,只需能看懂前端代码即可,加上 Django 类似API式的视图和前端模板语法,简直完美结合。当初学习 Django 一心想搭起一个自己满意的小站,但始终未完成心愿,了解到 Wordpress 后让我打开了新思路,我的个人网站,是 Django1.11 + 崔庆才个人博客 Wordpress 欲思主题搭建:https://www.stormsha.com/初学Python web时对前端不熟练是其实是最难的点,为了让更多的人少走弯路,自己写了一下个人博客搭建的教程,这个教程我希望靠每个浏览过的人都能给出更多建议把它完善,所以希望更多的人看到,提出意见,帮助更多的人

April 1, 2019 · 1 min · jiezi

MySQL 复制 - 性能与扩展性的基石:概述及其原理

复制概述MySQL 内置的复制功能是构建基于 MySQL 的大规模、高性能应用的基础,复制解决的基本问题是让一台服务器的数据与其他服务器保持同步。接下来,我们将从复制概述及原理、复制的配置、常见的问题及解决方法来学习 MySQL 的复制功能。1.1 复制解决的问题下面是复制常见的用途:数据分布。Mysql 复制通常不会对带宽造成很大压力,但在 5.1 版本中引入的基于行的复制会比传统的基于语句的复制模式产生更大的带宽压力。你可以随意地停止或开始复制,并在不同的地理位置来分布数据备份,例如不同的数据中心。另外,即使在不稳定的网络环境下,远程复制也可以工作。但如果未来保存很低的复制延迟,最好有一个稳定、低延迟的连接。负载均衡。通过 Mysql 复制,可以将读操作分布到多个服务器上,实现对读密集型应用的优化,并且很容易实现,通过简单的代码修改就能实现基本的负载均衡。对应小规模的应用,可以简单的使用 DNS 轮询(将一个机器名指向多个 IP 地址)。备份。对于备份来说,负载是一项很有意义的技术补充。高可用性和故障切换。负载能够帮助应用避免 Mysql 单点失败,一个使用复制的设计良好的系统能够显著的缩短宕机时间。Mysql 升级测试。这是比较常见的做法,在更新 Mysql 版本前,先使用将要更新的版本作为备库,保证更新版本不会对系统造成影响。1.2 复制是如何工作的?在详细介绍如何设置复制之前,让我们先看看 Mysql 实际上是如何进行数据复制的。总的来说,复制有三个步骤:在主库上把数据更改写入到二进制日志(Binary Log)中(这些记录被称为二进制日志事件)。备库将主库上的日志复制到自己的中继日志(Relay Log)中。备库读取中继日志中的事件,将其更改同步到备库。以上是复制的简单概述,下图描述了复制的细节:整体复制过程:在主库上记录二进制日志。在每次准备提交事务完成 数据更新前,主库将数据更新的事件记录到二进制日志中。Mysql 会按事务提交的顺序而非每条语句的执行顺序来记录二进制日志。在记录二进制日志后,主库会告诉存储引擎可以提交事务了。备库将主库的二进制日志复制到其本地的中继日志中。首先,备库会启动一个工作线程,称为 I/O 线程,I/O 线程跟主库建立一个普通的客户端连接,然后在主库上启动一个特殊的二进制转储(binlog dump)线程,这个二进制转储线程会读取主库二进制日志中的事件。它不会对时间进行轮询。如果该线程“追赶”上了主库,它将进入睡眠状态,直到主库发送信号量通知它有新的事件产生才会被换新,备库 I/O 线程会将接收到的事件记录到中继日志中。备库启动 SQL 线程,执行最后一步。该线程从中继日志中读取事件并在备库执行,从而实现备库数据的更新。当 SQL 线程追赶上 I/O 线程时,中继日志通常已经在系统缓存中,所以中继日志的开销很低。SQL 线程执行的事件也可以通过配置项来决定是否写入自身的二进制日志中,这对于备库再配置备库的常见非常有用。这种复制架构实现了获取事件和重放事件的解耦,允许这两个过程异步进行。也就是说 I/O 线程能够独立于 SQL 线程之前工作。但是,这种架构也限制了复制的过程,其中最重要的一点是,在主库上并发运行的查询在备库上只能串行化执行,因为只有一个 SQL 线程来重放中继日志中的事件。不过值得高兴的是,5.7 版本已经支持从库的并行复制了。基于二进制日志的并行复制,是在日志内容中新增了 last_committed 和 sequence_number,分别 表示事务提交的时间和上次事务提交的编号。如果事务具有相同的时间,表示这些事务是在一组内,可以进行并行回放。2. 复制的原理我们已经了解了复制的一些基本概念,接下来我们要更深入的了解复制,看看复制究竟是如何工作的,有哪些优缺点。2.1 基于语句的复制在 Mysql 5.0 及之前的版本中只支持基于语句的复制(也称为逻辑复制)。基于语句的复制模式,主库会记录那些造成数据更改的 SQL 语句,当备库读取并重放这些事件时,实际上只是把主库执行过的 SQL 再执行一遍。这种方式既有优点,也有缺点。优点是:实现简单。理论上来说,只要简单地记录和执行 SQL 语句,就能够让主备保持同步。二进制日志不会对带宽产生较大影响。二进制日志里的事件更加紧凑,占用带宽较小。但事实上,基于语句的方式可能并不如其看起来那么便利,其缺点是:主库上的数据除了执行的语句外,可能还依赖其他因素。当主库使用 CURRENT_USER() 函数的语句,存储过程和触发器在使用基于语句的复制模式时就可能会出现问题。2.2 基于行的复制Mysql 5.1 开始支持基于行的复制。这种方式会将实际数据记录在二进制日志中。同样的,它也有其自身的优缺点。它的优点是可以更加准确的复制数据,而缺点,则是可能造成较大的开销。比如一个工资表中有一万个用户,我们把每个用户的工资+1000,那么基于行的复制则要复制一万行的内容,由此造成的开销比较大,而基于语句的复制仅仅一条语句就可以了。由于没有哪种模式是对所有情况都是完美的,Mysql 就使复制模式可以动态切换。默认情况下使用的是基于语句的复制方式,但如果发现语句无法被正确地复制,就切换到基于行的复制模式。还可以根据需要来设置会话级别的变量 binlog_format,控制二进制日志格式。2.3 复制文件解读复制过程中会使用到一些文件。前面已经介绍了二进制日志文件和中继日志文件,除此之外,还有其他的文件会被用到。mysql-bin.index:当在服务器上开启二进制日志时,同时会生成一个和二进制日志同名,但以 .index 作为后缀的文件,该文件用于记录磁盘上的二进制日志文件。这里的 index 并不是表的索引,而是说这个文件的每一行包含了二进制文件的文件名。Mysql 依赖这个文件识别二进制日志文件。mysql-relay-bin-index:中继日志的索引文件,和 mysql-bin.index 的作用类似。master.info:保存备库连接主库所需要的信息文件。格式为纯文本(每行一个值),不同的 Mysql 版本,记录的信息也可能不太。此文件不能删除,否则备库再重启后不能连接主库。这个文件以文本的方式记录了复制用户的密码,所以要注意此文件的权限控制。relay-log.info:记录当前备库复制的二进制日志和中继日志位置文件。使用这些文件来记录 Mysql 复制和日志状态是一种非常粗糙的方式。更不幸的是,它们不是同步写的。如果服务器断电并且文件数据没有被刷新到磁盘,在重启服务器后,文件中记录的数据可能是错误。不过好在这些问题以及在 5.5 版本里做了改进。2.4 发送复制事件到其它备库log_slave_update 选项可以让备库编程其它服务器的主库。在设置该选项后,Mysql 会将其执行过的事件记录到它自己的二进制日志中。这样它的备库就可以从其日志中检索并执行事件。下图阐述了这一过程:在这种场景下,主库将数据更新事件写入二进制日志,第一个备库提取并执行这个事件。这个时候一个事件的生命周期应该已经结束了。但由于设置了 log_slave_updates,备库会将这个事件写到它自己的二进制日志中。这样第二个备库就可以从第一个备库中,将事件提取到它的中继日志中并执行。这意味着作为源服务器的主库可以将其数据变化传递给没有与其直接相连的备库上。默认情况下,这个选项是被打开的,这样在连接到备库时就不需要重启服务器。当第一个备库把自主库获得的事件写入到其它二进制日志中时,这个事件在备库二进制日志中的位置与其主库二进制日志中的位置几乎肯定是不相同的,可能在不同的日志文件或文件内不同的位置。这意味着你不能假定所有拥有同一逻辑复制点的服务器拥有相同的日志坐标。小结复制功能是 MySQL 高扩展性的基础,常见的读写分离就使用了复制。复制使用了三个线程。master 的日志线程,将事件写入 binlog,slave 的 IO 线程获取 binlog,并将其写入 relaylog,SQL 线程重放 relaylog 日志。复制有基于语句复制和基于行的复制。参考:高性能 MySQL - 第 3 版;

April 1, 2019 · 1 min · jiezi

sql反模式(二) — 单纯的树

个人博客:http://www.80soho.com/?p=781场景:有这么个需求:设计开发一个评论系统,要求用户可以评论文章以及相互回复,无层级数限制。这个需求开发人员基本都遇到过,可以先回忆或考虑这个数据表如何设计!定义:存在递归关系的数据很常见,数据常会像树或者以层级方式组织。在树形结构中,实例被称为节点(node),每个节点有多个子节点和一个父节点,最上面的节点叫根(root)节点,它没有父节点,最底层的没有子节点的节点叫叶(leaf), 中间的节点简单地称为非叶(nonleaf)节点。评论数据就是一种树形结构数据,评论的子节点就是它的回复。其他的树形结构数据像职员与经理的关系,菜单等等很多;方案:以下所有方案中暂不考虑外键约束,数据库是MYSQL!邻接表这个可能是最常见的解决方案,直接添加parent_id字段,引用同一张表中的其他回复。表结构如下 CREATE TABLE Comments ( comment_id int(11) NOT NULL AUTO_INCREMENT COMMENT ‘评论ID’, parent_id int(11) NOT NULL DEFAULT ‘0’ COMMENT ‘评论的父ID’, article_id int(11) NOT NULL DEFAULT ‘0’ COMMENT ‘文章ID’, comment varchar(200) DEFAULT ’’ COMMENT ‘评论内容’, PRIMARY KEY (comment_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=‘简化的评论表’;邻接表总是依赖父节点,看看它的优缺点:无法完成树操作中最普通的有一项,查询一个节点的所有后代;要用一条简单的sql检索一个很长的回复分支还是很困难的;也可先获取文章的所有评论,在程序的栈内存中处理整合,但数据量,访问量都大,每次有人访问都要做一次数据处理也不切实际;增加叶子节点操作是非常方便的;删除节点会变得比较复杂:考虑数据完整性,删除一棵子树,不得不考虑处理其所有的后代节点;上述这种方案可被叫做:‘单纯的数‘ 反模式!要合理的使用反模式:邻接表设计的优势在于能快速地获取一个给定节点的直接父节点,也很容易插入新节点。如果这样的需求就是你的应用程序的需求,那使用邻接表就可以很好地工作!下面再看看其他的方案:路径枚举路径枚举的设计通过将所有的祖先的信息联合成一个字符串,并保存为每个节点的一个属性:CREATE TABLE Comments ( comment_id int(11) NOT NULL AUTO_INCREMENT COMMENT ‘评论ID’, path varchar(1000) NOT NULL DEFAULT ‘0’ COMMENT ‘路径:eg: 1/2/4’, article_id int(11) NOT NULL DEFAULT ‘0’ COMMENT ‘文章ID’, comment varchar(200) DEFAULT ’’ COMMENT ‘评论内容’, PRIMARY KEY (comment_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=‘简化的评论表’;来看看该方案有没有解决邻接表的问题,1. 通过比较每个节点的路径来查询一个节点的祖先:例如查找comment_id 为4的所有的祖先的sql: SELECT * from Comments AS c where ‘1/3/4/’ like CONCAT(c.path,’%’);2. 查询一个节点的所有后台:例如 comment_id 为 1 的所有后台: SELECT * from Comments AS c where c.path like CONCAT(‘1/’,’%’);3. 插入节点:只需一份父节点的路径即可;comment_id是自动生成,需要先插入再修改;该方案的缺点:数据库不能确保路径的格式总是正确或者路径中的节点确实存在,需应用程序的逻辑代码来维护,且验证字符串的正确性的开销很大;再者,无论将varcharde的长度设定为多大,依旧存在长度限制,因而不能支持树结构的无限扩展。闭包表闭包表记录树中所有节点间的关系,而不仅仅只有那些直接的父子关系,是一个简单而优雅的分级存储解决方案。该方案不再使用Comments表来存储树的结构,而是将树中任何具有祖先-后代关系的节点对都存储在新表中,即使两个节点不是直接的父子关系,同时,还增加一行指向节点自己,表结构如下:CREATE TABLE Comments ( comment_id int(11) NOT NULL AUTO_INCREMENT COMMENT ‘评论ID’, article_id int(11) NOT NULL DEFAULT ‘0’ COMMENT ‘文章ID’, comment varchar(200) DEFAULT ’’ COMMENT ‘评论内容’, PRIMARY KEY (comment_id)) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT=‘简化的评论表’;CREATE TABLE TreePaths ( ancestor int(11) NOT NULL DEFAULT ‘0’ COMMENT ‘祖先’, descendant int(11) NOT NULL DEFAULT ‘0’ COMMENT ‘后代’) ENGINE=InnoDB DEFAULT CHARSET=utf8;下面看看相关操作1. 搜索祖先:搜索评论5的祖先: SELECT c.* from Comments as c JOIN TreePaths as t on c.comment_id = t.ancestor WHERE t.descendant=5;2. 搜索后台:搜索评论1的后代: SELECT c.* from Comments as c JOIN TreePaths as t on c.comment_id = t.descendant WHERE t.ancestor=1;3. 插入子节点:例如评论5新增一个子节点,应首先插入一条自己到自己的关系,然后搜索TreePaths表中后代是评论5的所有节点,增加这些节点和新节点的’祖先-后代‘关系。TreePaths表可以继续优化:增加path_length字段表示祖先与后代的层级数等等;综述以上列举了三个方案,没种设计都各有优劣,如何选择设计依赖于应用程序中的哪种操作最需要性能上的优化:邻接表是最方面的设计,并且很多开发中都了解它;路径枚举能够很直观地展示出祖先与后代之间的路径,但同时由于它不能确保引用完整性,使得这个设计十分脆弱,闭包表示通用的设计,它要求一个张额外的表来存储关系,使用空间换时间的方案减少操作过程中冗余的计算所造成的消耗。当然还有其他的设计方案,没有最好的方案,只有最适合某个应用需求的方案,欢迎多多交流! ...

April 1, 2019 · 1 min · jiezi

shell中的单引号和双引号理解

问题描述: 最近在写shell脚本的时候,涉及到一个使用shell脚本发送json数据的问题,就是发送的json数据双引号不见了,导致数据格式不正确,收到了错误的响应。后来仔细查看了资料才发现自己之前对shell单引号和双引号的理解有一些问题,在此记录一些现象和结果。问题解析: 1.首先,我这边使用的是bash脚本,放一下bash脚本的手册地址; 2.然后我们看一下官方的手册里面是怎么介绍的: 2.1 单引号:Single Quotes:Enclosing characters in single quotes (‘’’) preserves the literal value of each character within the quotes. A single quote may not occur between single quotes, even when preceded by a backslash. 翻译出来就是:用单引号(’’’)括起字符可以保留引号中每个字符的字面值。单引号之间可能不会出现单引号,即使前面有反斜杠也是如此。我的理解是单引号中的值都是会直接输出字符或这字符串的字面量,不会去解析各种变量或者其他的符号,而且必须是成对出现的。如果两个单引号之间有单引号,或者两个单引号之间有反斜杆的单引号都是不会结束的情况,必须等待新的单引号出现,让它们成对了才会结束。(这里的意思是bash的解释器会对单引号去解析,只有成对的时候才会结束,否则会一直等待,所以呢单引号对号都是成对的使用,虽然我也不知道单个的单引号有什么用)。下面举几个栗子用来解释一下刚才说的:可以看到前两张图,输入了三个单引号,或者两个单引号之间是一个带反斜杆的单引号。都会出现>的符号,意思是等待继续的输入。第三张图输入了单引号以后,出现了;号,表示结束了。说明解释器对单引号都是要成对的去解析。 2.2 双引号:Double QuotesEnclosing characters in double quotes (‘"’) preserves the literal value of all characters within the quotes, with the exception of ‘$’, ‘’, ‘\’, and, when history expansion is enabled, ‘!’. When the shell is in POSIX mode (see Bash POSIX Mode), the ‘!’ has no special meaning within double quotes, even when history expansion is enabled. The characters ‘$’ and ‘’ retain their special meaning within double quotes (see Shell Expansions). The backslash retains its special meaning only when followed by one of the following characters: ‘$’, ‘’, ‘"’, ‘\’, or newline. Within double quotes, backslashes that are followed by one of these characters are removed. Backslashes preceding characters without a special meaning are left unmodified. A double quote may be quoted within double quotes by preceding it with a backslash. If enabled, history expansion will be performed unless an ‘!’ appearing in double quotes is escaped using a backslash. The backslash preceding the ‘!’ is not removed.The special parameters ‘*’ and ‘@’ have special meaning when in double quotes (see Shell Parameter Expansion).大概意思是说:双引号中的信息会保留字面量,但是同时会对$,,,这些符号做出特殊的解析。就是双引号中的变量和转义,和函数操作可以被正常解析出来。这个比较好理解,接下来我们看下单引号和双引号使用的一些栗子,加深一下我们的理解。实例:直接上图:输出1,2,应该没有什么问题都是输出的字面量的字符串。输出3,4,就是展示了单引号和双引号的区别,单引号继续输出了字符串,而双引号输出了变量a的值。输出5,6呢,其实就是我遇到的问题,脚本中需要使用到日期的变量,并且放入到json的数据中。输出5如果直接使用单引号,肯定行不通,因为不解析变量。输出6呢,虽然最外层使用了双引号,内部可以解析变量,但是发现问题没有,变量外面是没有双引号的,而json的数据格式是{“key”:“value”}。也是不符合的,原因在于shell解释器分辨不出来双引号是在第几层,仅仅查到一堆双引号就把它们结为夫妻(一对对的双引号进行解析),所以输出6的解析过程是"’{“解析出’{,第二对双引号”:“解析出:,第三对双引号”&dollar;start_date"解析出start_date的值,依次类推。得出了’{startDay:2019-03-31 00:00:00,endDay:2019-03-31 23:59:59}’。输出7相当于就是正确的输出了json格式的数据,原理也很简单在输出6已经解释清楚。总结:1.__写shell脚本的时候,如果不需要解析里面的内容,就使用单引号,反之,双引号;__2.记住shell解析单引号和双引号的规则,是就近原则,遇到一对单/双引号,就会解析出其中的内容,而不是根据什么最外层,最内层这种层级关系去解析的,这点要记住。所以在输入json或者其他的格式的数据的时候,混合使用单/双引号的时候要注意使用的顺序,否则得到的结果并不是你预想的那样 ...

March 31, 2019 · 2 min · jiezi

一文了解数据库索引:哈希、B-Tree 与 LSM

本文节选自深入浅出分布式基础架构-数据库篇 https://url.wx-coder.cn/kl3ms。数据库索引索引(Index)是帮助数据库系统高效获取数据的数据结构,数据库索引本质上是以增加额外的写操作与用于维护索引数据结构的存储空间为代价的用于提升数据库中数据检索效率的数据结构。索引可以帮助我们快速地定位到数据而不需要每次搜索的时候都遍历数据库中的每一行。典型的索引譬如在内存中维护一个二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在 O(log2n)的复杂度内获取到相应数据。左侧为数据记录的物理地址,右侧为查找树,需要注意的是,逻辑上相邻的记录在磁盘上也并不是一定物理相邻的。实际的数据库应用中我们往往使用 B+ 树或者 LSM 来替代二叉查找树或者红黑树来构建索引系统,并且充分利用 虚拟存储管理 https://url.wx-coder.cn/PeNqS 一节中介绍过的局部性原理、磁盘预读与页缓存等概念。值得一提的是,本节并未涵盖搜索引擎中常用的与文本索引相关的技术,譬如倒排索引、TF-IDF 等,如果有兴趣可以参考本篇搜索引擎 https://url.wx-coder.cn/O07eI 一章。存储管理基础本部分节选自深入浅出 Linux 操作系统 https://url.wx-coder.cn/Q0AmI 。计算机存储设备可被粗略分为内存储器(Main Memory)与外存储器(External Memory)两大类,内存存取速度快,但容量小,价格昂贵,而且不能长期保存数据,在不通电情况下数据会消失;外存储器存取速度相对较慢,却可以吃持久化存储。如果进行更加细致地划分,每个计算机系统中的存储设备都被组织成了一个存储器层次结构,在这个层次结构中,从上至下,设备变得访问速度越来越慢、容量越来越大,并且每字节的造价也越来越便宜。存储器层次结构的主要思想是一层上的存储器作为低一层存储器的高速缓存。因此,寄存器文件就是 L1 的高速缓存,L1 是 L2 的高速缓存,L2 是 L3 的高速缓存,L3 是主存的高速缓存,而主存又是磁盘的高速缓存。在某些具有分布式文件系统的网络系统中,本地磁盘就是存储在其他系统中磁盘上的数据的高速缓存。主存主存是一个临时存储设备,在处理器执行程序时,用来存放程序和程序处理的数据。从物理上来说,主存是由一组动态随机存取存储器(DRAM)芯片组成的。从逻辑上来说,存储器是一个线性的字节数组,每个字节都有其唯一的地址(即数组索引),这些地址是从零开始的。一般来说,组成程序的每条机器指令都由不同数量的字节构成。现代 DRAM 的结构和存取原理比较复杂,这里抽象出一个十分简单的存取模型来说明 DRAM 的工作原理。从抽象角度看,主存是一系列的存储单元组成的矩阵,每个存储单元存储固定大小的数据。每个存储单元有唯一的地址,现代主存的编址规则比较复杂,这里将其简化成一个二维地址:通过一个行地址和一个列地址可以唯一定位到一个存储单元。当系统需要读取主存时,则将地址信号放到地址总线上传给主存,主存读到地址信号后,解析信号并定位到指定存储单元,然后将此存储单元数据放到数据总线上,供其它部件读取。写主存的过程类似,系统将要写入单元地址和数据分别放在地址总线和数据总线上,主存读取两个总线的内容,做相应的写操作。这里可以看出,主存存取的时间仅与存取次数呈线性关系,因为不存在机械操作,两次存取的数据的“距离”不会对时间有任何影响,例如,先取 A0 再取 A1 和先取 A0 再取 D3 的时间消耗是一样的。寄存器与高速缓存寄存器文件在层次结构中位于最顶部,也就是第 0 级或记为 L0。一个典型的寄存器文件只存储几百字节的信息,而主存里可存放几十亿字节。然而,处理器从寄存器文件中读数据的速度比从主存中读取几乎要快 100 倍。针对这种处理器与主存之间的差异,系统设计者采用了更小、更快的存储设备,即高速缓存存储器(简称高速缓存),作为暂时的集结区域,用来存放处理器近期可能会需要的信息。L1 和 L2 高速缓存是用一种叫做静态随机访问存储器(SRAM)的硬件技术实现的。比较新的、处理能力更强大的系统甚至有三级高速缓存:L1、L2 和 L3。系统可以获得一个很大的存储器,同时访问速度也很快,原因是利用了高速缓存的局部性原理,即程序具有访问局部区域里的数据和代码的趋势。通过让高速缓存里存放可能经常访问的数据的方法,大部分的存储器操作都能在快速的高速缓存中完成。磁盘磁盘是一种直接存取的存储设备 (DASD)。它是以存取时间变化不大为特征的。可以直接存取任何字符组,且容量大、速度较其它外存设备更快。磁盘是一个扁平的圆盘(与电唱机的唱片类似),盘面上有许多称为磁道的圆圈,数据就记录在这些磁道上。磁盘可以是单片的,也可以是由若干盘片组成的盘组,每一盘片上有两个面。如下图中所示的 6 片盘组为例,除去最顶端和最底端的外侧面不存储数据之外,一共有 10 个面可以用来保存信息。当磁盘驱动器执行读 / 写功能时。盘片装在一个主轴上,并绕主轴高速旋转,当磁道在读 / 写头 ( 又叫磁头 ) 下通过时,就可以进行数据的读 / 写了。一般磁盘分为固定头盘 ( 磁头固定 ) 和活动头盘。固定头盘的每一个磁道上都有独立的磁头,它是固定不动的,专门负责这一磁道上数据的读 / 写。活动头盘 ( 如上图 ) 的磁头是可移动的。每一个盘面上只有一个磁头 ( 磁头是双向的,因此正反盘面都能读写 )。它可以从该面的一个磁道移动到另一个磁道。所有磁头都装 在同一个动臂上,因此不同盘面上的所有磁头都是同时移动的 ( 行动整齐划一 )。当盘片绕主轴旋转的时候,磁头与旋转的盘片形成一个圆柱体。各个盘面上半径相 同的磁道组成了一个圆柱面,我们称为柱面。因此,柱面的个数也就是盘面上的磁道数。磁盘上数据必须用一个三维地址唯一标示:柱面号、盘面号、块号 ( 磁道上的盘块 )。读 / 写磁盘上某一指定数据需要下面 3 个步骤: (1) 首先移动臂根据柱面号使磁头移动到所需要的柱面上,这一过程被称为定位或查找。 (2) 如上图 11.3 中所示的 6 盘组示意图中,所有磁头都定位到了 10 个盘面的 10 条磁道上 ( 磁头都是双向的 )。这时根据盘面号来确定指定盘面上的磁道。 (3) 盘面确定以后,盘片开始旋转,将指定块号的磁道段移动至磁头下。经过上面三个步骤,指定数据的存储位置就被找到。这时就可以开始读 / 写操作了。访问某一具体信息,由 3 部分时间组成:查找时间 (seek time) Ts: 完成上述步骤 (1) 所需要的时间。这部分时间代价最高,最大可达到 0.1s 左右。等待时间 (latency time) Tl: 完成上述步骤 (3) 所需要的时间。由于盘片绕主轴旋转速度很快,一般为 7200 转 / 分 ( 电脑硬盘的性能指标之一 , 家用的普通硬盘的转速一般有 5400rpm( 笔记本 )、7200rpm 几种 )。因此一般旋转一圈大约 0.0083s。传输时间 (transmission time) Tt: 数据通过系统总线传送到内存的时间,一般传输一个字节 (byte) 大概 0.02us=2*10^(-8)s磁盘读取数据是以盘块(block)为基本单位的。位于同一盘块中的所有数据都能被一次性全部读取出来。而磁盘 IO 代价主要花费在查找时间 Ts 上。因此我们应该尽量将相关信息存放在同一盘块,同一磁道中。或者至少放在同一柱面或相邻柱面上,以求在读/写信息时尽量减少磁头来回移动的次数,避免过多的查找时间Ts。所以,在大规模数据存储方面,大量数据存储在外存磁盘中,而在外存磁盘中读取 / 写入块 (block) 中某数据时,首先需要定位到磁盘中的某块,如何有效地查找磁盘中的数据,需要一种合理高效的外存数据结构。哈希索引哈希索引即是基于哈希技术,如上图所示,我们将一系列的最终的键值通过哈希函数转化为存储实际数据桶的地址数值。值本身存储的地址就是基于哈希函数的计算结果,而搜索的过程就是利用哈希函数从元数据中推导出桶的地址。添加新值的流程,首先会根据哈希函数计算出存储数据的地址,如果该地址已经被占用,则添加新桶并重新计算哈希函数。更新值的流程则是先搜索到目标值的地址,然后对该内存地址应用所需的操作。哈希索引会在进行相等性测试(等或者不等)时候具有非常高的性能,但是在进行比较查询、Order By 等更为复杂的场景下就无能为力。B-TreeB-Tree 与 B+Tree在数据结构与算法/查找树 https://url.wx-coder.cn/9PnzG 一节中我们介绍了 B-Tree 的基本概念与实现,这里我们继续来分析下为何 B-Tree 相较于红黑树等二叉查找树会更适合于作为数据库索引的实现。一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘 I/O 消耗,相对于内存存取,I/O 存取的消耗要高几个数量级,所以评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘 I/O 操作次数的渐进复杂度。换句话说,索引的结构组织要尽量减少查找过程中磁盘 I/O 的存取次数。根据 B-Tree 的定义,可知检索一次最多需要访问 h 个节点。数据库系统的设计者巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次 I/O 就可以完全载入。每次新建节点时,直接申请一个页的空间,这样就保证一个节点物理上也存储在一个页里,加之计算机存储分配都是按页对齐的,就实现了一个节点只需一次 I/O。而检索的时候,一次检索最多需要 h-1 次 I/O(根节点常驻内存),其渐进复杂度为 $O(h)=O(log_dN)O(h)=O(log_dN)$,实际应用中,出度 d 是非常大的数字,通常超过 100,因此 h 非常小(通常不超过 3)。而红黑树这种结构,h 明显要深的多。由于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,所以红黑树的 I/O 渐进复杂度也为 O(h),效率明显比 B-Tree 差很多。B+Tree 是 的变种,有着比 B-Tree 更高的查询性能,其相较于 B-Tree 有了如下的变化:有 m 个子树的节点包含有 m 个元素(B-Tree 中是 m-1)。根节点和分支节点中不保存数据,只用于索引,所有数据都保存在叶子节点中。所有分支节点和根节点都同时存在于子节点中,在子节点元素中是最大或者最小的元素。叶子节点会包含所有的关键字,以及指向数据记录的指针,并且叶子节点本身是根据关键字的大小从小到大顺序链接。一般在数据库系统或文件系统中使用的 B+Tree 结构都在经典 B+Tree 的基础上进行了优化,增加了顺序访问指针:如上图所示,在 B+Tree 的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的 B+Tree。做这个优化的目的是为了提高区间访问的性能,例如下图中如果要查询 key 为从 3 到 8 的所有数据记录,当找到 3 后,只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点,极大提到了区间查询效率。索引顺序B-Tree 索引可以很好地用于单行、范围或者前缀扫描,他们只有在查找使用了索引的最左前缀(Leftmost Prefix)的时候才有用。不过 B-Tree 索引存在一些限制:如果查找不从索引列的最左边开始,索引就无法使用;同样,不能查找字符串结尾;不能跳过索引中的列;不能使用任何在第一个范围条件右边的列作为条件;因此 B-Tree 的列顺序非常重要,上述使用规则都和列顺序有关。对于实际的应用,一般要根据具体的需求,创建不同列和不同列顺序的索引。假设有索引 Index(A,B,C):# 使用索引A>5 AND A<10 - 最左前缀匹配A=5 AND B>6 - 最左前缀匹配A=5 AND B=6 AND C=7 - 全列匹配A=5 AND B IN (2,3) AND C>5 - 最左前缀匹配,填坑# 不能使用索引B>5 - 没有包含最左前缀B=6 AND C=7 - 没有包含最左前缀# 使用部分索引A>5 AND B=2 - 使用索引 A 列A=5 AND B>6 AND C=2 - 使用索引的 A 和 B 列使用索引对结果进行排序,需要索引的顺序和 ORDER BY 子句中的顺序一致,并且所有列的升降序一致(ASC/DESC)。如果查询连接了多个表,只有在 ORDER BY 的列引用的是第一个表才可以(需要按序 JOIN)。# 使用索引排序ORDER BY A - 最左前缀匹配WHERE A=5 ORDER BY B,C - 最左前缀匹配WHERE A=5 ORDER BY B DESC - 最左前缀匹配WHERE A>5 ORDER BY A,B - 最左前缀匹配# 不能使用索引排序WHERE A=5 ORDER BY B DESC,C ASC - 升降序不一致WHERE A=5 ORDER BY B,D - D 不在索引中WHERE A=5 ORDER BY C - 没有包含最左前缀WHERE A>5 ORDER BY B,C - 第一列是范围条件,无法使用 BC 排序WHERE A=5 AND B IN(1, 2) ORDER BY C - B 也是范围条件,无法用 C 排序LSM TreeB-Tree 这种数据库索引方式是传统关系型数据库中主要的索引构建方式,然而 BTree 通常会存在写操作吞吐量上的瓶颈,其需要大量的磁盘随机 IO,很显然,大量的磁盘随机 IO 会严重影响索引建立的速度。对于那些索引数据大的情况(例如,两个列的联合索引),插入速度是对性能影响的重要指标,而读取相对来说就比较少。譬如在一个无缓存的情况下,B-Tree 首先需要进行一次磁盘读写将磁盘页读取到内存中,然后进行修改,最后再进行一次 IO 写回到磁盘中。LSM Tree 则采取读写分离的策略,会优先保证写操作的性能;其数据首先存储内存中,而后需要定期 Flush 到硬盘上。LSM-Tree 通过内存插入与磁盘的顺序写,来达到最优的写性能,因为这会大大降低磁盘的寻道次数,一次磁盘 IO 可以写入多个索引块。HBase, Cassandra, RockDB, LevelDB, SQLite 等都是基于 LSM Tree 来构建索引的数据库;LSM Tree 的树节点可以分为两种,保存在内存中的称之为 MemTable, 保存在磁盘上的称之为 SSTable。LSM-tree 的主要思想是划分不同等级的树。以两级树为例,可以想象一份索引数据由两个树组成,一棵树存在于内存,一棵树存在于磁盘。内存中的树可以可以是 AVL Tree 等结构;因为数据大小是不同的,没必要牺牲 CPU 来达到最小的树高度。而存在于磁盘的树是一棵 B-Tree。数据首先会插入到内存中的树。当内存中的树中的数据超过一定阈值时,会进行合并操作。合并操作会从左至右遍历内存中的树的叶子节点与磁盘中的树的叶子节点进行合并,当被合并的数据量达到磁盘的存储页的大小时,会将合并后的数据持久化到磁盘,同时更新父亲节点对叶子节点的指针。之前存在于磁盘的叶子节点被合并后,旧的数据并不会被删除,这些数据会拷贝一份和内存中的数据一起顺序写到磁盘。这会操作一些空间的浪费,但是,LSM-Tree 提供了一些机制来回收这些空间。磁盘中的树的非叶子节点数据也被缓存在内存中。数据查找会首先查找内存中树,如果没有查到结果,会转而查找磁盘中的树。有一个很显然的问题是,如果数据量过于庞大,磁盘中的树相应地也会很大,导致的后果是合并的速度会变慢。一个解决方法是建立各个层次的树,低层次的树都比 上一层次的树数据集大。假设内存中的树为 c0, 磁盘中的树按照层次一次为 c1, c2, c3, … ck-1, ck。合并的顺序是 (c0, c1), (c1, c2)…(ck-1, ck)。 ...

March 31, 2019 · 2 min · jiezi

MySQL 执行过程与查询缓存

MySQL执行一个查询过程:当我们向MySQL发送一个请求的时候,MySQL到底做了什么:1.客户端发送一条查询给服务器2.服务器先检查查询缓存,如果命中了缓存,则立刻返回存储在缓存中的结果。否则进入下一阶段。3.服务器端进行SQL解析、预处理,再由优化器生成对应的执行计划。4.MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询5.将结果返回给客户端。mysql 主要是由 server 层和存储层两部分构成的。server 层主要包括连接器、查询缓存,分析器、优化器、执行器。存储层主要是用来存储和查询数据的,常用的存储引擎有 InnoDB、MyISAM,(1) MySQL客户端/服务器通信协议MySQL客户端和服务器之的通信协议是“半双工”的,这就意味着,在任何一个时刻,要么是由服务器向客户端发送数据,要么是由客户端向服务器发送数据,这两个动作不能同时发生。所以我们无法也无须将一个消息切成小块独立来发送。优缺点:这种协议让MySQL通信简单快速,但是也从很多地方限制了 MySQL。一个明显的限制是,这意味着没法进行流量控制。一旦一端开始发送消息,另一端要接收完整个消息才能响应它。这就像采回抛球的游戏:在任何时刻,只有一个人能控制球,而且只有控制球的人才能将球抛回去(发送消息)。(2).连接器 MySQL客户端和服务端建立连接,获取当前连接用户的权限(3)查询缓存在解析一个查询语句之前,如果查询缓存是打开的,MySQL会检查这个缓存,是否命中查询缓存中的数据。这个检查是通过一个大小写敏感的哈希查找实现的。查询和缓存中的查询即使只有一个字节不同,那也不会匹配缓存结果,这种情况下查询就会进入下一阶段的处理。如果当前的查询恰好命中了查询缓存,那么在返回查询结果之前 MySQL会检查一次用户权限。这仍然是无须解析查询SQL语句的,因为在查询缓存中已经存放了当前查询需要访问的表信息。如果权限没有问题, MySQL会跳过所有其他阶段,直接从缓存中拿到结果并返回给客户端。这种情况下,查询不会被解析,不用生成执行计划,不会被执行.ps:注意在 mysql8 后已经没有查询缓存这个功能了,因为这个缓存非常容易被清空掉,命中率比较低。(3).分析器既然没有查到缓存,就需要开始执行 sql 语句了,在执行之前肯定需要先对 sql 语句进行解析。分析器主要对 sql 语句进行语法和语义分析,检查单词是否拼写错误,还有检查要查询的表或字段是否存在(4)查询优化查询的生命周期的下一步是将一个SQL转换成一个执行计划, MySQL再依照这个执行计划和存储引擎进行交互。这包括多个子阶段:解析SQL、预处理、优化SQ执行计划。这个过程中任何错误(例如语法错误)都可能终止查询。2.关于查询缓存(1)MySQL 判断缓存命中的方法很简单:缓存存放在一个引用表中,通过一个哈希值引用。MySOL查询缓存保存查询返回的完整结果。当查询命中该缓存, MySQL会立刻返回结果跳过了 解析,优化和执行阶段查询缓存系统会跟踪查迫中涉及的每个表,如果这些表发生变化,那么和这个表相关的的存数据都将失效。这种机制效率看起来比较低,因为数据表变化时很有可能对查询结果并没有变更,但是这种简单实现代价很小,而这点对于一个非常繁忙的系统来说非常重要。查询缓存系统对应用程序是完全透明的。应用程序无须关心 MySQL是通过查询缓存返回的结果还是实际执行返回的结果。事实上,这两种方式执行的结果是完全相同的。换句话说,查询缓存无须使用任何语法。无论是 MYSQL开启成关闭查询缓在,对应用程序都是透明的。(2)判断缓存命中当判断缓存是否命中时, MySQL不会解析、“正规化”或者参数化查询语句,而是直接使用SQL语句和客户端发送过来的其他原始信息,在字符上不同,例如空格、注释,在何的不同,都会导致缓存的不中。当查询语句中有一些不确定的数据时,则不会被缓存,例如包含函数NOW()或者 CURRENT_DATE()的查询不会被缓存.误区:我们常听到:“如果查询中包含一个不确定的函数, MySQL则不会检查查询缓存”。这个说法是不正确的。因为在检查查询缓存的时候,还没有解析SQL语句,所以MySQL并不知道查询语句中是否包含这类函数。在检查查询缓存之前, MySQL只做一件事情,就是通过一个大小写不敏感的检查看看SQL语句是不是以5EL开头。准确的说法应该是:“如果查询语句中包含任何的不确定函数,那么在查询缓存中是不可能找到缓存结果的”。注意点:MySQL的查询缓存在很多时候可以提升查询性能,在使用的时候,有一些问题需要特别注意。首先,打开查询缓存对读和写操作都会带来额外的消耗:1.读查询在开始之前必须先检查是否命中缓存2.如果这个读查询可以被缓存,那么当完成执行后, MySQL若发现查询缓存中没有这个查询,会将其结果存入查询缓存,这会带来额外的系统消耗。3.这对写操作也会有影响,因为当向某个表写入数据的时候, MySQL必须将对应表的所有缓存都设置失效。如果查询缓存非常大或者碎片很多,这个操作就可能会带来大系统消耗(设置了很多的内存给查询缓存用的时候)如果查询缓存使用了很大量的内存,缓存失效操作就可能成为一个非常严重的问题瓶颈如果缓存中存放了大量的查询结果,那么缓存失效操作时整个系统都可能会僵死一会因为这个操作是靠一个全局锁操作保护的,所有需要做该操作的查询都要等待这个锁,而且无论是检测是否命中缓存、还是缓存失效检测都需要等待这个全局锁。(3)什么情况下查询缓存能发挥作用理论上,可以通过观察打开或者关闭查询缓存时候的系统效率来决定是否需要开启查询。对手那些需要消耗大量资源的查询通常都是非常适合缓存的。例如一些汇总计算查询具体的如 COUNT()等。总地来说,对于复杂的 SELECT语句都可以使用查询缓存,例如多表JOIN后还需要做排序和分页,这类查询每次执行消耗都很大,但是返回的结果集却很小,非常适合查询缓存。不过需要注意的是,涉及的表上 UPDATE、 DELETE和 INSERT操作相比 SELECT来说要非常少才行。判断查询缓存是否有效的直接数据是命中率。就是使用查询缓存返回结果占总查询的比率不过缓存中率是一个很难判断的数值。命中率多大才是好的命中率。具体情况,具体分析。只要查询缓存带来的效率提升大于查询缓存带来的额外消耗,即使30%命中率对系统性能提升也有很大好处。另外,缓存了哪些查询也很重要,例如,被缓存的查询本身消耗非常巨大,那么即使缓存命中率非常低,也仍然会对系统性能提升有好处缓存未命中可能有如下几种原因:1.查询语句无法被缓存,可能是因为查询中包含一个不确定的函数(如 CURREN_DATE)或者查询结果太大而无法缓存。这都会导致状态值 Cache not cached增加。2.MySQL从未处理这个查询,所以结果也从不曾被缓存过。3.还有一种情况是虽然之前缓存了查询结果,但是由于查询缓存的内存用完了,MySQL需要将某些缓存“逐出”,或者由于数据表被修改导致缓存失效。如果你的服务器上有大量缓存未命中,但是实际上绝大数查询都被缓存了,那么一定是有如下情况发生:1.查询缓存还没有完成预热。也就是说, MySQL还没有机会将查询结果都缓存起来。2.查询语句之前从未执行过。如果你的应用程序不会重复执行一条查询语句,那么即使完成预热仍然会有很多缓存未命中3.缓存失效操作太多了。(4)如何配置 和维护查询缓存query_cache_type是否打开查询缓存。可以设置成0FN或 DEMAND。 DEMAND表示只有在查询语句中明确写明SQL_ CACHE的语句才放入查询缓存。这个变量可以是会话级别的也可以是全局级别的query_cache_size查询缓存使用的总内存空间,单位是字节。这个值必须是1024的整数倍,否则 MySQL实际分配的数据会和你指定的略有不同。query_cahce_min_res_unit在查询缓存中分配内存块时的最小单位。query_chache_limitMySQL能够缓存的最大査询结果。如果查询结果大于这个值,则不会被缓存。因为査询缓存在数据生成的时候就开始尝试缓存数据,所以只有当结果全部返回后,才知道查询结果是否超出限制如果超出, MySQL则增加状态值 Cache_not_cached,并将结果从查询缓存中删除如果你事先知道有很多这样的情况发生,那么建议在查询语句中加入(5)替代方案MySQL查询缓存工作的原则是:执行查询最快的方式就是不去执行,但是查询仍然需要发送到服务器端,服务器也还需要做一点点工作。如果对于某些查询完全不需要与服务器通信效果会如何呢?这时客户端的缓存可以很大程度上帮你分担 MySQL服务器的压力总结:完全相同的查询在重复执行的时候,查询缓存可以立即返回结果,而无须在数据库中重新执行一次。根据我们的经验,在高并发压力环境中在询缓存会导致系统性能的下降,甚至僵死。如果一定要使用查询缓存,那么不要设置太大内存,而且只有在确收益的时候才使用。那该如何判断是否应该使用查询缓存呢?建议使Percona server.,观察更细致的日志,并做一些简单的计算。还可以查看缓存命中率(并不总是有用)、“ NSERTS和 SELECT比率”(这个参数也并不直观)、或者“命中和写入比率”(这个参考意义较大)。查询缓存是一个非常方便的缓存,对应用程序完全透明,无须任何额外的编码,但是、如果希望有更高的缓存效率,我们建议使cache 或者其他类似的解决方案。ps:文章参考《高性能MySQL》一书

March 30, 2019 · 1 min · jiezi

PHP & MySQL 「数据关联一对一」的最佳实践

前言在开发过程中,通常会遇到很多 一对一 数据的处理情况。而很多时候我们会要取到的是一个列表,然后列表的单条记录的对应另外一张表,来实现业务。比如下面的商品信息 和 商品详情 两个表,这里为了演示只是使用了基础字段,实际开发中可能会复杂的多,下方演示代码中数据库连接使用 PDO 进行处理。表结构goods列类型注释idint(11) 自动增量主键IDtitlevarchar(100)商品名称pricedecimal(10,2)商品价格covervarchar(100)商品封面goods_detail列类型注释idint(11) 自动增量主键IDgoods_idint(11)商品IDcontentvarchar(5000)商品图文介绍初级坦言,无论是在公司,还是在一些开源项目上,我都看到过如下的代码。$query = $db->query(‘select * from goods’);$result = $query->fetchAll();// 方案一foreach($result as $key => $item){ $query = $db->query(‘select * from goods_detail where goods_id=’ . $item[‘id’]); $result[$key][‘goods_detail’] = $query->fetch();}var_dump($result);// 方案二foreach($result as &$item){ $query = $db->query(‘select * from goods_detail where goods_id=’ . $item[‘id’]); $item[‘goods_detail’] = $query->fetch();}unset($item);var_dump($result);// 方案三$result = array_map(function($item){ $query = $db->query(‘select * from goods_detail where goods_id=’ . $item[‘id’]); $item[‘goods_detail’] = $query->fetch(); return $item;},$result);var_dump($result);这是最暴力的方式,也是立杆见影,而且方案一看起来代码貌似还很繁琐,不是吗?如果学过 引用这一节的朋友,应该知道第二种用法,直接用引用去操作源数据,当然最后最好别忘了 unset 掉 $item,除了第二种,我们还可以用第三种方式,使用 array_map,诚然,这和第二种方式没什么区别,但是这其中有着一个非常大的问题:数据库查询的N+1 。从执行中我们就可以看到,除了查询列表的一条 SQL 外,每查询一条记录对应的都需要执行一条 SQL ,导致了额外的查询,想想一下如果查询没有 limit 限制。会是什么样子的情况?进阶看到这里,有人可能会想到了另一种方案来,先查询列表,然后取出列表里面的 goods_id 之后使用 in 查询,然后再循环分配给列表,看代码。$goods_id = array_column($result,‘id’);$goods_id_str = implode(’,’,$goods_id);$query = $db->query(sprintf(‘select * from goods_detail where goods_id in (%s)’,$goods_id_str));$goods_detail_list = $query->fetchAll();foreach($result as &$item){ $item[‘goods_detail’] = array_first($goods_detail_list,function($item1){ return $item[‘id’] == $item1[‘goods_id’]; });}unset($item);var_dump($result);/** * 来自 Laravel /if (!function_exists(‘value’)) { function value($value) { return $value instanceof Closure ? $value() : $value; }}/* * 来自 Laravel /if (!function_exists(‘array_first’)) { /* * @param $array * @param callable|null $callback * @param null $default * @return mixed */ function array_first($array, callable $callback = null, $default = null) { if (is_null($callback)) { if (empty($array)) { return value($default); } foreach ($array as $item) { return $item; } } foreach ($array as $key => $value) { if (call_user_func($callback, $value, $key)) { return $value; } } return value($default); }}在这个代码中,我们完美避开了 N+1 的窘境,使用了in查询,然后遍历数组,再使用 array_first 方法来查找后传递给 goods_detail 索引,虽然这样的效率相比第一次的要高了很多,但是并不完美,接下来来看最后一种方案。关于 array_first 可以看我的另一篇文章 『PHP 多维数组中的 array_find』。最佳实践$goods_detail_list_by_keys = array_column($goods_detail_list,null,‘goods_id’);foreach($result as &$item){ $item[‘goods_detail’] = array_key_exists($goods_detail_list_by_keys,$item[‘id’]) ? $goods_detail_list_by_keys[$item[‘id’]] : null ; // php 7.1+ // $item[‘goods_detail’] = $goods_detail_list_by_keys[$item[‘id’]] ?? null;}unset($item);var_dump($result);这一次,我们用到了其他两个函数。array_column 、 array_key_exists,接下里一一道来,其实在array_column的官方手册中的我们就能 Example #2 中就介绍了我们想要的方法。套用在这里就是重置goods_detail_list 里面元素的 key 为 单个元素下的 goods_id。在后面我们直接用 array_key_exists 判断是否存在,然后做出相应的处理就好了。在这里我们还可以做另外一个操作,那就是默认值,因为有时候,数据有可能会因对不上,如果查出来直接返回给前端,前端没有预料到这种情况没有做容错处理就会导致前端页面崩溃,下面来改写一下代码// 在 「进阶」 板块中,我们用到了 「array_first」 函数,该函数第三个参数可以直接设置默认值,我们就不多讲了,主要讲讲最后一个$goods_detail_default = [ ‘content’ => ‘默认内容’, ‘id’ => null, ‘goods_id’=> null,];foreach($result as &$item){ $tmp = array_key_exists($goods_detail_list_by_keys,$item[‘id’]) ? $goods_detail_list_by_keys[$item[‘id’]] : [] ; // php 7.1+ // $tmp = $goods_detail_list_by_keys[$item[‘id’]] ?? []; $item[‘goods_detail’] = array_merge($goods_detail_default,$tmp);}unset($item);var_dump($result);结束看到这里就算是完结了但是有的朋友会说,为什么不用 leftJoin 来处理?确实,在处理一对一关系中很多时候我们都会选择 innerJoin 或者 leftJoin 来进行处理,一条 SQL 就能搞定,很少会用到类似于这种方案,其实不然,在主流的框架中,默认的解决方案几乎都是这样处理的,比如Laravel、ThinkPHP,考虑到的场景会有很多,比如有的时候我只是需要按需取一部分的,或者我需要根据我后面的业务结果来决定是不是要加载一对一,然而这种情况下 join 似乎就不太适合。 ...

March 30, 2019 · 2 min · jiezi

MySQL 和 Redis 事务的对比

简言一般来说,事务是必须满足4个条件(ACID)::原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。从标题来看,既然都是事务,那之间有什么区别?来一一解开,先从两个数据库说去。MySQL 属于 关系型数据库 , Redis 属于 非关系型数据库,两者对事务有着不同的解释。Redis[1] Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:批量操作在发送 EXEC 命令前被放入队列缓存。收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。一个事务从开始到执行会经历以下三个阶段:开始事务。命令入队。执行事务。单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。操作错误看着有点儿绕口,那就实际执行一下 看一下结果。127.0.0.1:6379> multiOK127.0.0.1:6379> set tr_1 233QUEUED127.0.0.1:6379> lpush tr_1 666QUEUED127.0.0.1:6379> set tr_2 888QUEUED127.0.0.1:6379> exec1) OK2) (error) WRONGTYPE Operation against a key holding the wrong kind of value3) OK在上面的事务中,设置了一个 key 为 tr_1 的字符串数据,然后又通过 lpush 来添加元素,这很明显是错误的操作方式,当我们提交事务候出现了一个操作错误,这时候我们来看看 tr_1 的值是什么。127.0.0.1:6379> get tr_1"233"通过 get 命令来的tr_1 内容还是 233 ,并没有变,那再看一下其他的。127.0.0.1:6379> keys *1) “tr_2"2) “tr_1"127.0.0.1:6379> get tr_2"888"127.0.0.1:6379>这里可以看到 tr_2 存在,并打印了值,这时候我们发现,即使出现了操作错误 ,但是错误并没有致使执行停止,错误之后的语句也执行了并成功执行,似乎符合上面提到的 中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。语法错误NO~,这时候还有另外一种情况 语法错误127.0.0.1:6379> multiOK127.0.0.1:6379> set tr_1 233QUEUED127.0.0.1:6379> lpush tr_1 666QUEUED127.0.0.1:6379> set(error) ERR wrong number of arguments for ‘set’ command127.0.0.1:6379> set 233(error) ERR wrong number of arguments for ‘set’ command127.0.0.1:6379> set tr_2 888QUEUED127.0.0.1:6379> exec(error) EXECABORT Transaction discarded because of previous errors.127.0.0.1:6379> keys *(empty list or set)当我们执行到 set时没有给任何参数,第二次执行时故意少给了一个参数。可以看到报了 语法错误,最后提交事务,也告诉了我们事务因为错误被丢失了,接着用 keys *检索发现确实如此。文档释义这里可以官方文档中提到的Errors inside a transaction// 在执行过程中 可能会遇到两种错误命令错误。During a transaction it is possible to encounter two kind of command errors:// 1.命令无法进入队列 ,比如 :参数数量错误,命令名错误…,或者某些关键错误 如内存不足A command may fail to be queued, so there may be an error before EXEC is called. For instance the command may be syntactically wrong (wrong number of arguments, wrong command name, …), or there may be some critical condition like an out of memory condition (if the server is configured to have a memory limit using the maxmemorydirective).// 2. 对键进行错误的操作 如上面的 对字符串使用 lpushA command may fail after EXEC is called, for instance since we performed an operation against a key with the wrong value (like calling a list operation against a string value).// 客户端检查键入的命令,大多数时候会在调用 exec 前发现第一类错误,如果命令执行返回来 QUEUED 则表示命令正常进入队列,否则错误,大多数情况下客户端会终止放弃这个事务。Clients used to sense the first kind of errors, happening before the EXEC call, by checking the return value of the queued command: if the command replies with QUEUED it was queued correctly, otherwise Redis returns an error. If there is an error while queueing a command, most clients will abort the transaction discarding it.关于 Redis 暂时看到这里 接下来看到 MySQLMySQL众所周知,MySQL 只有 InnoDB 引擎支持 事务,在启用 MySQL 事务之前需要先停掉自动提交测试表结构 user列类型注释idint(11) 自动增量主键IDmoneyint(11) [0]金钱titlevarchar(500) NULL称呼在这里来模拟一个转账的操作:A给B转100元。步骤解析 A+100 元,B -100元,即两步虽然很简单,简单走一下流程。可以看到,没有问题,那么我们从中人为的制造一些问题呢?操作错误 列类型注释idint(11) 自动增量 moneyint(11) unsigned [0] titlevarchar(500) NULL 这里我们把 money 字段变成了无符号,即不能小于 0,并且,调整数据库中的数据如下。SELECT * FROM user LIMIT 50 (0.000 秒) 修改idmoneytitle编辑110000A编辑20B接着执行下面的 SQLselect version();SET AUTOCOMMIT=0;begin;select * from user where title in (‘A’,‘B’) for update;update user set money = money + 1000 where title = ‘A’;update user set money = money - 1000 where title = ‘B’;select * from user where title in (‘A’,‘B’);commit;问题出现了,这里报出了错误,但是可以看到 前面的 SQL 已经是已执行的了,结果已经发生了变化,从这里看,似乎和 Redis 的处理差不多,除了错误之后语句继续执行。但是 值的注意的是, 在我们实际开发中,这种情况程序会直接抛出异常,以供我们在 catch 块中执行 rollback ,以回滚操作确保数据完整,即使是单独使用 MySQL 命令行 我们也可以用存储过程来对异常进行回滚。语法错误刚刚看到 Redis 当遇到 语法错误 时会自动丢弃事务,阻止提交,那 MySQL 呢?答案:不会,MySQL 在顺序执行时,如果未对异常进行处理,总会将成功执行的的提交,而不会触发自动终止,但是我们可以在程序执行时进行放弃提交。Redis 为什么没有回滚?Redis 的官方文档给出了这样的解释 // 只有在使用错误的语法调用时才会失败Redis命令(并且在命令排队期间无法检测到问题),或者对于持有错误数据类型的键,Redis命令可能会失败:这意味着实际上失败的命令是编程错误的结果,以及在开发过程中很可能检测到的一种错误,而不是在生产中。Redis commands can fail only if called with a wrong syntax (and the problem is not detectable during the command queueing), or against keys holding the wrong data type: this means that in practical terms a failing command is the result of a programming errors, and a kind of error that is very likely to be detected during development, and not in production.// Redis内部简化且速度更快,因为它不需要回滚的能力。Redis is internally simplified and faster because it does not need the ability to roll back.总结数据库 自动回滚条件操作错误语法错误MySQL✖✖Redis✖✔但是 MySQL 支持手动回滚,实际开发过程中可以自行手动对已提交的操作进行回滚操作,更加友好。参考资料Redis 事务 - http://www.runoob.com/redis/r...Redis Transactions - https://redis.io/topics/trans...MySQL 事务 - http://www.runoob.com/mysql/m… ...

March 29, 2019 · 3 min · jiezi

技本功丨请带上纸笔刷着看:解读MySQL执行计划的type列和extra列

本萌最近被一则新闻深受鼓舞,西工大硬核“女学神”白雨桐,获6所世界顶级大学博士录取通知书货真价值的才貌双全,别人家的孩子高考失利与心仪的专业失之交臂,选择了软件工程这门自己完全不懂的专业.即便全部归零,也要证明自己,连续3年专业综合排名第一,各种获奖经历写满了5页PPT。成功始于不断的努力和拼搏,在学习和实践中不断提升自己。【技本功】愿做你成功路上的基石,赶紧来接收今日份的精神投食~一、解读type执行计划的type表示访问数据类型,有很多种访问类型。1、system表示这一步只返回一行数据,如果这一步的执行对象是一个驱动表或者主表,那么被驱动表或者子查询只是被访问一次。2、const表示这个执行步骤最多只返回一行数据。const通常出现在对主键或唯一索引的等值查询中,例如对表t主键id的查询:3、eq_refeq_ref类型一般意味着在表关联时,被关联表上的关联列走的是主键或者唯一索引。例如,表jiang关联lock_test表,关联列分别是两张表的主键列 :上面SQL执行时,jiang表是驱动表,lock_test是被驱动表,被驱动表的关联列是主键id,type类型为eq_ref。所以,对于eq_ref类型来说有一个重要的特点就是:这一步涉及到的表是被驱动表;这一步中使用到唯一索引或主键。除了system和const之外,这是效果最好的关联类型。4、ref与上面相反,如果执行计划的某一步的type是ref的话,表示这一步的关联列是非唯一索引。例如,用表jiang的主键id列关联表lock_test的num列,num列上建立了普通索引:上面SQL执行时,表jiang是驱动表,lock_test是被驱动表,被驱动表上走的是非唯一索引,type类型为ref。所以ref的特点是:表示这一步访问数据使用的索引是非唯一索引。5、Ref_or_null例如执行下面语句:表示走了索引(num列上有索引),但是也访问了空值。6、index_merge表示索引合并,一般对多个二级索引列做or操作时就会发生索引合并。例如执行下列语句:mysql> explain select * from lock_test where id=3 or num=4;id为主键,num列上建有普通索引,语句执行时,会通过两个单列索引来处理or操作。7、unique_subquery表示唯一子查询。例如有如下语句执行时:value in(select primary_key from single_table where …)对于in子句来说,当in子句里的子查询返回的是某一个表的主键时,type显示为unique subquery。8、index_subquery当有如下语句执行时:value in(select key_column from single_table where …)与上面的相似,表示对于in子句来说,当in子句里的子查询返回的是某一个表的二级索引列(非主键列)时,type显示为index_subquery。9、range:在有索引的列上取一部分数据。常见于在索引列上执行between and操作。10、index:索引全扫描,一般发生在覆盖索引的时候,也就是对有索引列发生一次全扫描。11、all:没有索引的全表扫描。一个特例:Explain select * from stu limit 1,1;二、解读extra1、using where:一般有两层意思:表示通过索引访问时,需要再回表访问所需的数据;过滤条件发生在server层而不是存储引擎层;如果执行计划中显示走了索引,但是rows值很高,extra显示为using where,那么执行效果就不会很好。因为索引访问的成本主要在回表上,这时可以采用覆盖索引来优化。通过覆盖索引也能将过滤条件下压,在存储引擎层执行过滤操作,这样效果是最好的。所以,覆盖索引是解决using where的最有效的手段。2、using index condition表示将过滤下压到存储层执行,防止server层过滤过多数据如果extra中出现了using index condition,说明对访问表数据进行了优化。3、using temporary表示语句执行过程中使用到了临时表。以下子句的出现可能会使用到临时表:order bygroup bydistinctunion等数据不能直接返回给用户,就需要缓存,数据就以临时表缓存在用户工作空间。注意,可能会出现磁盘临时表,需要关注需要缓存的数据的rows。可以使用索引消除上面的四个操作对应的临时表。4、using sort_union(indexs)比如当执行下面语句:Sname和sphone列上都有索引,这时执行计划的extra项就会显示using sort_union(i_sname,i_spone),表示索引合并。常伴随着index_merge。5、using MRR:一般通过二级索引访问表数据的过程是:先访问二级索引列,找到对应的二级索引数据后就得到对应的主键值,然后拿着这个主键值再去访问表,取出行数据。这样取出的数据是按照二级索引排序的。MRR表示:通过二级索引得到对应的主键值后,不直接访问表而是先存储起来,在得到所有的主键值后,对主键值进行排序,然后再访问表。这样可以大幅减低对表的访问次数,至少实现了顺序访问表。MRR的一个优点就是提升索引访问表的效率,也就是降低了回表的成本。但是有一个比较大的问题:取出来的数据就不按照二级索引排序了。6、using join buffer(Block Nested Loop)BNL主要发生在两个表关联时,被关联的表上没有索引。BNL表示这样的意思:A关联B,A的关联列上有索引而B的没有。这时就会从A表中取10行数据拿出来放到用户的join buffer空间中,然后再取B上的数据和join buffer中A的关联列进行关联,这时只需要对B表访问一次,也就是B表发生一次全表扫描。如果join buffer中的10行数据关联完后,就再取10行数据继续和B表关联,一直到A表的所有数据都关联完为止。从上面可以看出来,这种方式大概效率会提高约90%。7、using join buffer(Batched Key Access)一般出现BKA的情况是:表关联时,被驱动表上有索引,但是驱动表返回的行数太多。当出现上述情况时,就会将驱动表的返回结果集放到用户工作空间的join buffer中,然后取结果集的一条记录去关联被驱动表的索引关联列。得到相应的主键列后并不马上通过这个主键列去被被驱动表中取数据,而是先存放到工作空间中。等到结果集中的所有数据都关联完了,对工作空间中的所有通过关联得到主键列进行排序,然后统一访问被驱动表,从中取数据。这样的好处就是大大降低了访问的次数。从上面可以看出:BKA用到了MRR技术;BKA适合驱动表返回行数较多、被驱动表访问时走的是索引的情况。这个功能可以打开或者关闭:Set optimizer_switch=’mrr=on,batched_key_access=on’;8、using index for group by表示通过复合索引完成group by,不用回表。例如复合索引(a,b),执行语句:select a from tb group by b;时就会出现using index for group by。9、using index表示实现了覆盖索引扫描;也就是需要访问的数据都在索引中,不需要回表。在一般情况下,减少不必要的数据访问能够提升效率。例如对表lock_test取num列上的数据,num列上建立普通索引:10、using filesort说明有排序行为,但是不一定是磁盘排序。11、materialize scan对物化表的全扫描,因为物化表就是一个临时表,表上没有索引。 ...

March 28, 2019 · 1 min · jiezi

【本人秃顶程序员】Java集合框架综述

←←←←←←←←←←←← 快!点关注一、集合框架图简化图:说明:对于以上的框架图有如下几点说明所有集合类都位于java.util包下。Java的集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,这两个接口又包含了一些子接口或实现类。集合接口:6个接口(短虚线表示),表示不同集合类型,是集合框架的基础。抽象类:5个抽象类(长虚线表示),对集合接口的部分实现。可扩展为自定义集合类。实现类:8个实现类(实线表示),对接口的具体实现。Collection 接口是一组允许重复的对象。Set 接口继承 Collection,集合元素不重复。List 接口继承 Collection,允许重复,维护元素插入顺序。Map接口是键-值对象,与Collection接口没有什么关系。Set、List和Map可以看做集合的三大类:List集合是有序集合,集合中的元素可以重复,访问集合中的元素可以根据元素的索引来访问。Set集合是无序集合,集合中的元素不可以重复,访问集合中的元素只能根据元素本身来访问(也是集合里元素不允许重复的原因)。Map集合中保存Key-value对形式的元素,访问时只能根据每项元素的key来访问其value。二、总体分析大致说明:看上面的框架图,先抓住它的主干,即Collection和Map。Collection是一个接口,是高度抽象出来的集合,它包含了集合的基本操作和属性。Collection包含了List和Set两大分支。List是一个有序的队列,每一个元素都有它的索引。第一个元素的索引值是0。List的实现类有LinkedList, ArrayList, Vector, Stack。Set是一个不允许有重复元素的集合。Set的实现类有HastSet和TreeSet。HashSet依赖于HashMap,它实际上是通过HashMap实现的;TreeSet依赖于TreeMap,它实际上是通过TreeMap实现的。Map是一个映射接口,即key-value键值对。Map中的每一个元素包含“一个key”和“key对应的value”。AbstractMap是个抽象类,它实现了Map接口中的大部分API。而HashMap,TreeMap,WeakHashMap都是继承于AbstractMap。Hashtable虽然继承于Dictionary,但它实现了Map接口。接下来,再看Iterator。它是遍历集合的工具,即我们通常通过Iterator迭代器来遍历集合。我们说Collection依赖于Iterator,是因为Collection的实现类都要实现iterator()函数,返回一个Iterator对象。ListIterator是专门为遍历List而存在的。再看Enumeration,它是JDK 1.0引入的抽象类。作用和Iterator一样,也是遍历集合;但是Enumeration的功能要比Iterator少。在上面的框图中,Enumeration只能在Hashtable, Vector, Stack中使用。最后,看Arrays和Collections。它们是操作数组、集合的两个工具类。有了上面的整体框架之后,我们接下来对每个类分别进行分析。三、Collection接口Collection接口是处理对象集合的根接口,其中定义了很多对元素进行操作的方法。Collection接口有两个主要的子接口List和Set,注意Map不是Collection的子接口,这个要牢记。Collection接口中的方法如下:其中,有几个比较常用的方法,比如方法add()添加一个元素到集合中,addAll()将指定集合中的所有元素添加到集合中,contains()方法检测集合中是否包含指定的元素,toArray()方法返回一个表示集合的数组。另外,Collection中有一个iterator()函数,它的作用是返回一个Iterator接口。通常,我们通过Iterator迭代器来遍历集合。ListIterator是List接口所特有的,在List接口中,通过ListIterator()返回一个ListIterator对象。Collection接口有两个常用的子接口,下面详细介绍。1.List接口List集合代表一个有序集合,集合中每个元素都有其对应的顺序索引。List集合允许使用重复元素,可以通过索引来访问指定位置的集合元素。List接口继承于Collection接口,它可以定义一个允许重复的有序集合。因为List中的元素是有序的,所以我们可以通过使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。List接口为Collection直接接口。List所代表的是有序的Collection,即它用某种特定的插入顺序来维护元素顺序。用户可以对列表中每个元素的插入位置进行精确地控制,同时可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。实现List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。(1)ArrayListArrayList是一个动态数组,也是我们最常用的集合。它允许任何符合规则的元素插入甚至包括null。每一个ArrayList都有一个初始容量(10),该容量代表了数组的大小。随着容器中的元素不断增加,容器的大小也会随着增加。在每次向容器中增加元素的同时都会进行容量检查,当快溢出时,就会进行扩容操作。所以如果我们明确所插入元素的多少,最好指定一个初始容量值,避免过多的进行扩容操作而浪费时间、效率。size、isEmpty、get、set、iterator和 listIterator 操作都以固定时间运行。add 操作以分摊的固定时间运行,也就是说,添加 n 个元素需要 O(n) 时间(由于要考虑到扩容,所以这不只是添加元素会带来分摊固定时间开销那样简单)。ArrayList擅长于随机访问。同时ArrayList是非同步的。(2)LinkedList同样实现List接口的LinkedList与ArrayList不同,ArrayList是一个动态数组,而LinkedList是一个双向链表。所以它除了有ArrayList的基本操作方法外还额外提供了get,remove,insert方法在LinkedList的首部或尾部。由于实现的方式不同,LinkedList不能随机访问,它所有的操作都是要按照双重链表的需要执行。在列表中索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端)。这样做的好处就是可以通过较低的代价在List中进行插入和删除操作。与ArrayList一样,LinkedList也是非同步的。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:List list = Collections.synchronizedList(new LinkedList(…));(3)Vector与ArrayList相似,但是Vector是同步的。所以说Vector是线程安全的动态数组。它的操作与ArrayList几乎一样。(4)StackStack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop 方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。2.Set接口Set是一种不包括重复元素的Collection。它维持它自己的内部排序,所以随机访问没有任何意义。与List一样,它同样允许null的存在但是仅有一个。由于Set接口的特殊性,所有传入Set集合中的元素都必须不同,同时要注意任何可变对象,如果在对集合中元素进行操作时,导致e1.equals(e2)==true,则必定会产生某些问题。Set接口有三个具体实现类,分别是散列集HashSet、链式散列集LinkedHashSet和树形集TreeSet。Set是一种不包含重复的元素的Collection,无序,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。需要注意的是:虽然Set中元素没有顺序,但是元素在set中的位置是由该元素的HashCode决定的,其具体位置其实是固定的。此外需要说明一点,在set接口中的不重复是有特殊要求的。举一个例子:对象A和对象B,本来是不同的两个对象,正常情况下它们是能够放入到Set里面的,但是如果对象A和B的都重写了hashcode和equals方法,并且重写后的hashcode和equals方法是相同的话。那么A和B是不能同时放入到Set集合中去的,也就是Set集合中的去重和hashcode与equals方法直接相关。为了更好地理解,请看下面的例子:public class Test{ public static void main(String[] args) { Set<String> set=new HashSet<String>(); set.add(“Hello”); set.add(“world”); set.add(“Hello”); System.out.println(“集合的尺寸为:"+set.size()); System.out.println(“集合中的元素为:"+set.toString()); } }运行结果:集合的尺寸为:2集合中的元素为:[world, Hello]分析:由于String类中重写了hashcode和equals方法,用来比较指向的字符串对象所存储的字符串是否相等。所以这里的第二个Hello是加不进去的。再看一个例子:public class TestSet { public static void main(String[] args){ Set<String> books = new HashSet<String>(); //添加一个字符串对象 books.add(new String(“Struts2权威指南”)); //再次添加一个字符串对象, //因为两个字符串对象通过equals方法比较相等,所以添加失败,返回false boolean result = books.add(new String(“Struts2权威指南”)); System.out.println(result); //下面输出看到集合只有一个元素 System.out.println(books); }}运行结果:false[Struts2权威指南]说明:程序中,book集合两次添加的字符串对象明显不是一个对象(程序通过new关键字来创建字符串对象),当使用==运算符判断返回false,使用equals方法比较返回true,所以不能添加到Set集合中,最后只能输出一个元素。(1)HashSetHashSet 是一个没有重复元素的集合。它是由HashMap实现的,不保证元素的顺序(这里所说的没有顺序是指:元素插入的顺序与输出的顺序不一致),而且HashSet允许使用null 元素。HashSet是非同步的,如果多个线程同时访问一个哈希set,而其中至少一个线程修改了该set,那么它必须保持外部同步。 HashSet按Hash算法来存储集合的元素,因此具有很好的存取和查找性能。HashSet的实现方式大致如下,通过一个HashMap存储元素,元素是存放在HashMap的Key中,而Value统一使用一个Object对象。HashSet使用和理解中容易出现的误区:HashSet中存放null值。HashSet中是允许存入null值的,但是在HashSet中仅仅能够存入一个null值。HashSet中存储元素的位置是固定的。HashSet中存储的元素的是无序的,这个没什么好说的,但是由于HashSet底层是基于Hash算法实现的,使用了hashcode,所以HashSet中相应的元素的位置是固定的。必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。(2)LinkedHashSetLinkedHashSet继承自HashSet,其底层是基于LinkedHashMap来实现的,有序,非同步。LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。(3)TreeSetTreeSet是一个有序集合,其底层是基于TreeMap实现的,非线程安全。TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序和定制排序,其中自然排序为默认的排序方式。当我们构造TreeSet时,若使用不带参数的构造函数,则TreeSet的使用自然比较器;若用户需要使用自定义的比较器,则需要使用带比较器的参数。注意:TreeSet集合不是通过hashcode和equals函数来比较元素的.它是通过compare或者comparaeTo函数来判断元素是否相等.compare函数通过判断两个对象的id,相同的id判断为重复元素,不会被加入到集合中。四、Map接口Map与List、Set接口不同,它是由一系列键值对组成的集合,提供了key到Value的映射。同时它也没有继承Collection。在Map中它保证了key与value之间的一一对应关系。也就是说一个key对应一个value,所以它不能存在相同的key值,当然value值可以相同。1.HashMap以哈希表数据结构实现,查找对象时通过哈希函数计算其位置,它是为快速查询而设计的,其内部定义了一个hash表数组(Entry[] table),元素会通过哈希转换函数将元素的哈希地址转换成数组中存放的索引,如果有冲突,则使用散列链表的形式将所有相同哈希地址的元素串起来,可能通过查看HashMap.Entry的源码它是一个单链表结构。2.LinkedHashMapLinkedHashMap是HashMap的一个子类,它保留插入的顺序,如果需要输出的顺序和输入时的相同,那么就选用LinkedHashMap。LinkedHashMap是Map接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。LinkedHashMap实现与HashMap的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。根据链表中元素的顺序可以分为:按插入顺序的链表,和按访问顺序(调用get方法)的链表。默认是按插入顺序排序,如果指定按访问顺序排序,那么调用get方法后,会将这次访问的元素移至链表尾部,不断访问可以形成按访问顺序排序的链表。注意,此实现不是同步的。如果多个线程同时访问链接的哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。由于LinkedHashMap需要维护元素的插入顺序,因此性能略低于HashMap的性能,但在迭代访问Map里的全部元素时将有很好的性能,因为它以链表来维护内部顺序。3.TreeMapTreeMap 是一个有序的key-value集合,非同步,基于红黑树(Red-Black tree)实现,每一个key-value节点作为红黑树的一个节点。TreeMap存储时会进行排序的,会根据key来对key-value键值对进行排序,其中排序方式也是分为两种,一种是自然排序,一种是定制排序,具体取决于使用的构造方法。自然排序:TreeMap中所有的key必须实现Comparable接口,并且所有的key都应该是同一个类的对象,否则会报ClassCastException异常。定制排序:定义TreeMap时,创建一个comparator对象,该对象对所有的treeMap中所有的key值进行排序,采用定制排序的时候不需要TreeMap中所有的key必须实现Comparable接口。TreeMap判断两个元素相等的标准:两个key通过compareTo()方法返回0,则认为这两个key相等。如果使用自定义的类来作为TreeMap中的key值,且想让TreeMap能够良好的工作,则必须重写自定义类中的equals()方法,TreeMap中判断相等的标准是:两个key通过equals()方法返回为true,并且通过compareTo()方法比较应该返回为0。五、Iterator 与 ListIterator详解1.IteratorIterator的定义如下:public interface Iterator<E> {}Iterator是一个接口,它是集合的迭代器。集合可以通过Iterator去遍历集合中的元素。Iterator提供的API接口如下:boolean hasNext():判断集合里是否存在下一个元素。如果有,hasNext()方法返回 true。Object next():返回集合里下一个元素。void remove():删除集合里上一次next方法返回的元素。使用示例:public class IteratorExample { public static void main(String[] args) { ArrayList<String> a = new ArrayList<String>(); a.add(“aaa”); a.add(“bbb”); a.add(“ccc”); System.out.println(“Before iterate : " + a); Iterator<String> it = a.iterator(); while (it.hasNext()) { String t = it.next(); if (“bbb”.equals(t)) { it.remove(); } } System.out.println(“After iterate : " + a); }}输出结果如下:Before iterate : [aaa, bbb, ccc]After iterate : [aaa, ccc]注意:Iterator只能单向移动。Iterator.remove()是唯一安全的方式来在迭代过程中修改集合;如果在迭代过程中以任何其它的方式修改了基本集合将会产生未知的行为。而且每调用一次next()方法,remove()方法只能被调用一次,如果违反这个规则将抛出一个异常。2.ListIteratorListIterator是一个功能更加强大的迭代器, 它继承于Iterator接口,只能用于各种List类型的访问。可以通过调用listIterator()方法产生一个指向List开始处的ListIterator, 还可以调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素处的ListIterator。ListIterator接口定义如下:public interface ListIterator<E> extends Iterator<E> { boolean hasNext(); E next(); boolean hasPrevious(); E previous(); int nextIndex(); int previousIndex(); void remove(); void set(E e); void add(E e);}由以上定义我们可以推出ListIterator可以:双向移动(向前/向后遍历).产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素的索引.可以使用set()方法替换它访问过的最后一个元素.可以使用add()方法在next()方法返回的元素之前或previous()方法返回的元素之后插入一个元素.使用示例:public class ListIteratorExample { public static void main(String[] args) { ArrayList<String> a = new ArrayList<String>(); a.add(“aaa”); a.add(“bbb”); a.add(“ccc”); System.out.println(“Before iterate : " + a); ListIterator<String> it = a.listIterator(); while (it.hasNext()) { System.out.println(it.next() + “, " + it.previousIndex() + “, " + it.nextIndex()); } while (it.hasPrevious()) { System.out.print(it.previous() + " “); } System.out.println(); it = a.listIterator(1); while (it.hasNext()) { String t = it.next(); System.out.println(t); if (“ccc”.equals(t)) { it.set(“nnn”); } else { it.add(“kkk”); } } System.out.println(“After iterate : " + a); }}输出结果如下:Before iterate : [aaa, bbb, ccc]aaa, 0, 1bbb, 1, 2ccc, 2, 3ccc bbb aaa bbbcccAfter iterate : [aaa, bbb, kkk, nnn]六、异同点1.ArrayList和LinkedListArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。对于随机访问get和set,ArrayList绝对优于LinkedList,因为LinkedList要移动指针。对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。这一点要看实际情况的。若只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数据,LinkedList的速度大大优于ArrayList. 因为ArrayList每插入一条数据,要移动插入点及之后的所有数据。2.HashTable与HashMap相同点:都实现了Map、Cloneable、java.io.Serializable接口。都是存储"键值对(key-value)“的散列表,而且都是采用拉链法实现的。不同点:历史原因:HashTable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现 。同步性:HashTable是线程安全的,也就是说是同步的,而HashMap是线程序不安全的,不是同步的 。对null值的处理:HashMap的key、value都可为null,HashTable的key、value都不可为null 。基类不同:HashMap继承于AbstractMap,而Hashtable继承于Dictionary。Dictionary是一个抽象类,它直接继承于Object类,没有实现任何接口。Dictionary类是JDK 1.0的引入的。虽然Dictionary也支持“添加key-value键值对”、“获取value”、“获取大小”等基本操作,但它的API函数比Map少;而且Dictionary一般是通过Enumeration(枚举类)去遍历,Map则是通过Iterator(迭代M器)去遍历。 然而由于Hashtable也实现了Map接口,所以,它即支持Enumeration遍历,也支持Iterator遍历。AbstractMap是一个抽象类,它实现了Map接口的绝大部分API函数;为Map的具体实现类提供了极大的便利。它是JDK 1.2新增的类。支持的遍历种类不同:HashMap只支持Iterator(迭代器)遍历。而Hashtable支持Iterator(迭代器)和Enumeration(枚举器)两种方式遍历。3.HashMap、Hashtable、LinkedHashMap和TreeMap比较Hashmap 是一个最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度。遍历时,取得数据的顺序是完全随机的。HashMap最多只允许一条记录的键为Null;允许多条记录的值为Null;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致。如果需要同步,可以用Collections的synchronizedMap方法使HashMap具有同步的能力。Hashtable 与 HashMap类似,不同的是:它不允许记录的键或者值为空;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢。LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,也可以在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现,它还可以按读取顺序来排列,像连接池中可以应用。LinkedHashMap实现与HashMap的不同之处在于,后者维护着一个运行于所有条目的双重链表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。对于LinkedHashMap而言,它继承与HashMap、底层使用哈希表与双向链表来保存所有元素。其基本操作与父类HashMap相似,它通过重写父类相关的方法,来实现自己的链接列表特性。TreeMap实现SortMap接口,内部实现是红黑树。能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。TreeMap不允许key的值为null。非同步的。一般情况下,我们用的最多的是HashMap,HashMap里面存入的键值对在取出的时候是随机的,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。在Map 中插入、删除和定位元素,HashMap 是最好的选择。TreeMap取出来的是排序后的键值对。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。LinkedHashMap 是HashMap的一个子类,如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现,它还可以按读取顺序来排列,像连接池中可以应用。import java.util.HashMap;import java.util.Iterator;import java.util.LinkedHashMap;import java.util.TreeMap;public class MapTest { public static void main(String[] args) { //HashMap HashMap<String,String> hashMap = new HashMap(); hashMap.put(“4”, “d”); hashMap.put(“3”, “c”); hashMap.put(“2”, “b”); hashMap.put(“1”, “a”); Iterator<String> iteratorHashMap = hashMap.keySet().iterator(); System.out.println(“HashMap–>”); while (iteratorHashMap.hasNext()){ Object key1 = iteratorHashMap.next(); System.out.println(key1 + “–” + hashMap.get(key1)); } //LinkedHashMap LinkedHashMap<String,String> linkedHashMap = new LinkedHashMap(); linkedHashMap.put(“4”, “d”); linkedHashMap.put(“3”, “c”); linkedHashMap.put(“2”, “b”); linkedHashMap.put(“1”, “a”); Iterator<String> iteratorLinkedHashMap = linkedHashMap.keySet().iterator(); System.out.println(“LinkedHashMap–>”); while (iteratorLinkedHashMap.hasNext()){ Object key2 = iteratorLinkedHashMap.next(); System.out.println(key2 + “–” + linkedHashMap.get(key2)); } //TreeMap TreeMap<String,String> treeMap = new TreeMap(); treeMap.put(“4”, “d”); treeMap.put(“3”, “c”); treeMap.put(“2”, “b”); treeMap.put(“1”, “a”); Iterator<String> iteratorTreeMap = treeMap.keySet().iterator(); System.out.println(“TreeMap–>”); while (iteratorTreeMap.hasNext()){ Object key3 = iteratorTreeMap.next(); System.out.println(key3 + “–” + treeMap.get(key3)); } }}输出结果:HashMap–>3–c2–b1–a4–dLinkedHashMap–>4–d3–c2–b1–aTreeMap–>1–a2–b3–c4–d4.HashSet、LinkedHashSet、TreeSet比较Set接口Set不允许包含相同的元素,如果试图把两个相同元素加入同一个集合中,add方法返回false。Set判断两个对象相同不是使用==运算符,而是根据equals方法。也就是说,只要两个对象用equals方法比较返回true,Set就不会接受这两个对象。HashSetHashSet有以下特点:不能保证元素的排列顺序,顺序有可能发生变化。不是同步的。集合元素可以是null,但只能放入一个null。当向HashSet结合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值也相等。注意,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写其hashCode()方法。其规则是如果两个对象通过equals方法比较返回true时,其hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算 hashCode的值。LinkedHashSetLinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。TreeSet类TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0。自然排序自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该接口的对象就可以比较大小。obj1.compareTo(obj2)方法如果返回0,则说明被比较的两个对象相等,如果返回一个正数,则表明obj1大于obj2,如果是负数,则表明obj1小于obj2。如果我们将两个对象的equals方法总是返回true,则这两个对象的compareTo方法返回应该返回0。定制排序自然排序是根据集合元素的大小,以升序排列,如果要定制排序,应该使用Comparator接口,实现 int compare(T o1,T o2)方法。package com.test; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.TreeSet; /** * @description 几个set的比较 * HashSet:哈希表是通过使用称为散列法的机制来存储信息的,元素并没有以某种特定顺序来存放; * LinkedHashSet:以元素插入的顺序来维护集合的链接表,允许以插入的顺序在集合中迭代; * TreeSet:提供一个使用树结构存储Set接口的实现,对象以升序顺序存储,访问和遍历的时间很快。 * @author Zhou-Jingxian * */ public class SetDemo { public static void main(String[] args) { HashSet<String> hs = new HashSet<String>(); hs.add(“B”); hs.add(“A”); hs.add(“D”); hs.add(“E”); hs.add(“C”); hs.add(“F”); System.out.println(“HashSet 顺序:\n”+hs); LinkedHashSet<String> lhs = new LinkedHashSet<String>(); lhs.add(“B”); lhs.add(“A”); lhs.add(“D”); lhs.add(“E”); lhs.add(“C”); lhs.add(“F”); System.out.println(“LinkedHashSet 顺序:\n”+lhs); TreeSet<String> ts = new TreeSet<String>(); ts.add(“B”); ts.add(“A”); ts.add(“D”); ts.add(“E”); ts.add(“C”); ts.add(“F”); System.out.println(“TreeSet 顺序:\n”+ts); } }输出结果:HashSet 顺序:[D, E, F, A, B, C]LinkedHashSet 顺序:[B, A, D, E, C, F]TreeSet 顺序:[A, B, C, D, E, F]5、Iterator和ListIterator区别我们在使用List,Set的时候,为了实现对其数据的遍历,我们经常使用到了Iterator(迭代器)。使用迭代器,你不需要干涉其遍历的过程,只需要每次取出一个你想要的数据进行处理就可以了。但是在使用的时候也是有不同的。List和Set都有iterator()来取得其迭代器。对List来说,你也可以通过listIterator()取得其迭代器,两种迭代器在有些时候是不能通用的,Iterator和ListIterator主要区别在以下方面:ListIterator有add()方法,可以向List中添加对象,而Iterator不能ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。因为ListIterator的这些功能,可以实现对LinkedList等List数据结构的操作。其实,数组对象也可以用迭代器来实现。6、Collection 和 Collections区别(1)java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。 Collection ├List │├LinkedList │├ArrayList │└Vector │ └Stack └Set (2)java.util.Collections 是一个包装类(工具类/帮助类)。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,用于对集合中元素进行排序、搜索以及线程安全等各种操作,服务于Java的Collection框架。代码示例:import java.util.ArrayList; import java.util.Collections; import java.util.List; public class TestCollections { public static void main(String args[]) { //注意List是实现Collection接口的 List list = new ArrayList(); double array[] = { 112, 111, 23, 456, 231 }; for (int i = 0; i < array.length; i++) { list.add(new Double(array[i])); } Collections.sort(list); for (int i = 0; i < array.length; i++) { System.out.println(list.get(i)); } // 结果:23.0 111.0 112.0 231.0 456.0 } } ...

March 28, 2019 · 3 min · jiezi

让你彻底了解SQL注入、XSS和CSRF

相信大家在各种技术文章都看到过SQL注入、XSS和CSRF这三个名词,但是我觉得有一部分人可能并不清楚这三个词的真正含义。接下来,我就说下这三个名词的含义,希望对大家能有所帮助。SQL注入SQL注入是属于注入式攻击,这种攻击是因为在项目中没有将代码与数据(比如用户敏感数据)隔离,在读取数据的时候,错误的将数据作为代码的一部分执行而导致的。典型的例子就是当对SQL语句进行字符串拼接的时候,直接使用未转义的用户输入内容作为变量。这时,只要在sql语句的中间做修改,比如加上drop、delete等关键字,执行之后后果不堪设想。说到这里,那么该怎么处理这种情况呢?三个方面:1、过滤用户输入参数中的特殊字符,降低风险。2、禁止通过字符串拼接sql语句,要严格使用参数绑定来传入参数。3、合理使用数据库框架提供的机制。就比如Mybatis提供的传入参数的方式 #{},禁止使用${},后者相当于是字符串拼接sql,要使用参数化的语句。总结下,就是要正确使用参数化绑定sql变量。XSSXSS:跨站脚本攻击,Cross-Site Scripting,为了和前端的css避免重名,简称为XSS,是指通过技术手段,向正常用户请求的HTML页面中插入恶意脚本,执行。这种攻击主要是用于信息窃取和破坏等目的。比如2011年的微博XSS攻击事件,攻击者利用了微博发布功能中未对action-data漏洞做有效的过滤,在发布微博信息的时候带上了包含攻击脚本的URL,用户访问就会加载恶意脚本,导致大量用户被攻击。关于防范XSS上,主要就是通过对用户输入的数据做过滤或者是转义,可以使用框架提供的工具类HtmlUtil。另外前端在浏览器展示数据的时候,要使用安全的API展示数据。比如使用innerText而不是innerHTML。CSRF跨站请求伪造,在用户并不知情的情况下,冒充用户发送请求,在当前已经登录的web网站上执行恶意操作,比如恶意发帖,修改密码等。大致来看,与XSS有重合的地方,前者是黑客盗用用户浏览器中的登录信息,冒充用户去执行操作。后者是在正常用户请求的HTML中放入恶意代码,XSS问题出在用户数据没有转义,过滤;CSRF问题出现在HTTP接口没有防范不守信用的调用。防范CSRF的漏洞方式:1、CSRF Token验证,利用浏览器的同源限制,在HTTP接口执行前验证Cookie中的Token,验证通过才会继续执行请求。2、人机交互,例如短信验证码、界面的滑块。之前在会议上也有一个思考,在人机验证这块,如果不用验证码的方式,用界面上的滑块这种方式,而且滑块还是第三方的。在APP的注册、登录使用这种人机验证的方式的话,如果第三方出现了问题,那么自己的APP就完全崩掉了,发版之后的APP什么也改不了。这个问题,你们能想到解决方案吗?在下一篇文章中,我会说一下讨论的结果。希望这点分享会对每个读者有所帮助,如果有什么好的建议,可以在留言区告诉我。这样的分享我会一直持续,你的关注、点赞、转发和收藏是对我最大的支持,感谢。如果这篇文章让你有所收获,欢迎关注公众号:java技术情报局

March 27, 2019 · 1 min · jiezi

你能说说你理解的数据库规范吗?

上一篇简单的说了下,自己公司的接口规范和上线规范,这次说下关于数据库方面的规范,希望能够帮助到大家。1、首先是工具,我们用的是 Mysql数据库,工具是Workbench,这个工具是专门为Mysql设计的数据库建模工具,你可以用它进行设计和创建新的数据库图示,在windows上面我们大多用的是PowerDesiger,在mac上用workbench目前来看还是挺好用的,无论是建立数据库文档,ER图,数据库的迁移都还是很贴心的。2、数据库字符集UTF-8,统一字符集,也就避免了麻烦的乱码转码问题,UTF-8几乎是好多工具的通用码,特殊的列使用扩展字符集。3、每个文件都有一个script文件,这个script文件里面有的应该是这个项目的数据库设计ER图,还有该项目数据库初始的sql语句,任何有关于该项目的数据库更改,都应该首先更改该script文件,并且标注日期,确保该script文件是正确的据库设计,因为我们在上线的时候要给运维上传的sql语句就是从这个ER图导出来的。4、相关联的业务数据表的名字要使用相同的前缀,确保一个业务的多个表在大量表中处于相邻位置。这点觉得还是很贴心的。5、数据经常变化的放到一张表;数据不变化的放到一张表;数据不经常变化的一张表。6、存储引擎必须使用InnoDB,InnoDB支持事务、行级锁、并发性能更好。7、不可使用存储过程、视图、触发器,高并发大数据的互联网业务,如果并发量太大,使用这些会把数据库拖垮,把业务逻辑放到服务层具备更好的扩展性,需求有所变化的时候也更好修改,而不用去修改繁琐的sql语句,并且能够轻易简单的 实现增加机器就增加性能,数据库的专长在于存储和索引。专业的事情就让专业的人来做。8、禁止使用Blob和Clob类型的字段,可以使用单独的库、单独的表来存储大文本的数据,这样做的话,当运维想要处理、备份数据的时候,这样的表可以针对性的优化,不用在意一些别的因素。9、表名使用下划线分隔,id主键统一命名为id BIGINT(20),数据写入时可以提高插入性能。10、每个表中必须包含字段createTime字段,创建时间,确保未来默认排序。11、外键禁止关联。外键会导致表和表之间的耦合,update和delete都会涉及到相关联的表,十分影响sql性能,还有可能会造成死锁。12、保存时间相关的数据,要使用时间戳的格式,因为在未来可能会涉及到时区的问题,而时间戳可以完全避免该问题;保存货币相关的数据,要保存最小货币的数量,避免有小数点,因为可能会引起数据精度问题,导致数据不准确。13、索引命名定义规则,其中一定要包括表名+列名,来确保该索引在整个数据库中是唯一的。14、建立组合索引的同时要把区分度较高的放在前面,这样能够更加有效的过滤数据。以上都是自己开发过程中遵守的,并且感觉很高效的数据库规范,简单整理下,沉淀下来,希望能帮助到这个公众号的读者。这样的分享会一直持续,你的关注,转发、点赞和收藏是对我最大的支持,感谢。关注公众号,最新文章会先在那里出现哦

March 26, 2019 · 1 min · jiezi

MySQL乱码的原因和设置UTF8数据格式

MySQL使用时,有一件很痛苦的事情肯定是结果乱码。将编码格式都设置为UTF8可以解决这个问题,我们今天来说下为什么要这么设置,以及怎么设置。MySQL字符格式字符集在编程语言中,我们为了防止中文乱码,会使用unicode对中文字符做处理,而为了降低网络带宽和节省存储空间,我们使用UTF8进行编码。对这两者有什么不同不够了解的同学,可以参考Unicode字符集和UTF8编码编码的前世今生这篇文章。同样在MySQL中,我们也会有这样的处理,我们可以查看当前数据库设置的编码方式(字符集):mysql> show variables like ‘%char%’;+————————–+———————————-+| Variable_name | Value |+————————–+———————————-+| character_set_client | latin1 | | character_set_connection | latin1 | | character_set_database | latin1 | | character_set_filesystem | binary | | character_set_results | latin1 | | character_set_server | latin1 | | character_set_system | utf8 | | character_sets_dir | /usr/local/mysql/share/charsets/ | +————————–+———————————-+8 rows in set (0.00 sec)表中就是当前设置的字符集,先看不用关注的几个值:character_set_filesystem | binary:文件系统上的存储格式,默认为binary(二进制)character_set_system | utf8:系统的存储格式,默认为utf8character_sets_dir | /usr/local/mysql/share/charsets/:可以使用的字符集的文件路径剩下的几个就是日常影响读写乱码的参数了:- character_set_client:客户端请求数据的字符集- character_set_connection:从客户端接收到数据,然后传输的字符集- character_set_database:默认数据库的字符集;如果没有默认数据库,使用character_set_server字段- character_set_results:结果集的字符集- character_set_server:数据库服务器的默认字符集字符集的转换流程分为3步:客户端请求数据库数据,发送的数据使用character_set_client字符集MySQL实例收到客户端发送的数据后,将其转换为character_set_connection字符集进行内部操作时,将数据字符集转换为内部操作字符集:使用每个数据字段的character set设定值若不存在,使用对应数据表的default character set设定值若不存在,使用对应数据库的default character set设定值若不存在,使用character_set_server设定值将操作结果值从内部操作字符集转换为character_set_results字符序说字符序之前,我们需要了解一点基础知识:字符(Character)是指人类语言中最小的表义符号。例如’A’、’B’等;给定一系列字符,对每个字符赋予一个数值,用数值来代表对应的字符,这一数值就是字符的编码(Encoding)。例如,我们给字符’A’赋予数值0,给字符’B’赋予数值1,则0就是字符’A’的编码;给定一系列字符并赋予对应的编码后,所有这些字符和编码对组成的集合就是字符集(Character Set)。例如,给定字符列表为{‘A’,’B’}时,{‘A’=>0, ‘B’=>1}就是一个字符集;字符序(Collation)是指在同一字符集内字符之间的比较规则;确定字符序后,才能在一个字符集上定义什么是等价的字符,以及字符之间的大小关系;每个字符序唯一对应一种字符集,但一个字符集可以对应多种字符序,其中有一个是默认字符序(Default Collation);MySQL中的字符序名称遵从命名惯例:以字符序对应的字符集名称开头;以_ci(表示大小写不敏感,case insensitive)、_cs(表示大小写敏感,case sensitive)或_bin(表示按编码值比较,binary)结尾。例如:在字符序“utf8_general_ci”下,字符“a”和“A”是等价的;因此字符序不同于字符集,用于数据库字段的相等或大小比较。我们查看MySQL实例设置的字符序:mysql> show variables like ‘collation%’;+———————-+——————-+| Variable_name | Value |+———————-+——————-+| collation_connection | latin1_swedish_ci | | collation_database | latin1_swedish_ci | | collation_server | latin1_swedish_ci | +———————-+——————-+3 rows in set (0.00 sec)跟utf8对应的常用字符序是:utf8_unicode_ci/utf8_general_ci和utf8_bin等,那么他们的区别是什么呢?_bin是用二进制存储并比较,区别大小写,存储二进制内容时使用utf8_general_ci:校对速度快,但准确度稍差,使用中英文时使用utf8_unicode_ci:准确度高,但校对速度稍慢,使用德法俄等外语时使用详细的区别可以参考 Mysql中的排序规则utf8_unicode_ci、utf8_general_ci的区别总结。修改字符集和字符序如果在MySQL连接时,出现了乱码的问题,那么基本可以确定是各个字符集/序设置不统一的原因。MySQL默认的latin1格式不支持中文,由于我们在中国,所以选择对中文和各语言支持都非常完善的utf8格式。所以,我们需要将需要关注的字符集和字符序都修改为utf8格式。你也可以选择utf8mb4格式,这个格式支持保存emoji????表情。我们需要修改Mysql的配置文件,查看配置文件的位置有两种方式:1. $ mysql –help | grep ‘my.cnf’ /etc/mysql/my.cnf /etc/my.cnf ~/.my.cnf 2. ps aux | grep mysql在(1)中,/etc/my.cnf, /etc/mysql/my.cnf, ~/.my.cnf 这些就是mysql默认会搜寻my.cnf的目录,优先级依次升高。可以在各个配置文件里都使用long_query_time 来测试一下。然后,我们修改或新增下面的配置项:# 下面注释的几行可以不设置,但如果你的没有生效,也可以试试看[mysqld]character_set_server=utf8collation-server=utf8_general_ciskip-character-set-client-handshake#init_connect=‘SET NAMES utf8’#[client]#default-character-set=utf8这三行配置就可以解决问题,最关键的是最后一行,参考mysql文档,使用该参数会忽略客户端传递的字符集信息,而直接使用服务端的设定;再加上我们设定服务端的字符集和字符序均为utf8,这样就保证了字符格式的统一,解决乱码的问题。重启mysql服务,如果提示找不到服务,请参考用service命令管理mysql启停:$ service mysqld restartShutting down MySQL.. [ OK ]Starting MySQL. [ OK ]连接到mysql,查看当前编码:mysql> show variables like ‘%char%’;+————————–+———————————-+| 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/local/mysql/share/charsets/ | +————————–+———————————-+8 rows in set (0.01 sec)mysql> show variables like ‘collation%’;+———————-+—————–+| Variable_name | Value |+———————-+—————–+| collation_connection | utf8_general_ci | | collation_database | utf8_general_ci | | collation_server | utf8_general_ci | +———————-+—————–+3 rows in set (0.01 sec)可以看到一切都符合预期,请求和存储的数据也不再是乱码了。遇到的问题unknown variable ‘default-character-set=utf8’参考官方文档,该参数自5.5.3版本废弃,改为了character-set-server,改为这个参数即可。但这个是在[mysqld]下的配置,在[client]下的配置依然使用default-character-set参数。参考资料MySQL连接校对:utf8_general_ci与utf8_unicode_ci有什么区别呢 :https://segmentfault.com/q/10…Unicode 和 UTF-8 有什么区别?:https://www.zhihu.com/questio…深入Mysql字符集设置:http://www.laruence.com/2008/…10.4 Connection Character Sets and Collations:https://dev.mysql.com/doc/ref…ISO/IEC 8859-1:https://zh.wikipedia.org/wiki…5.1.6 Server Command Options:https://dev.mysql.com/doc/ref…用service命令管理mysql启停:https://segmentfault.com/a/11…Unicode字符集和UTF8编码编码的前世今生:https://segmentfault.com/a/11…Mysql中的排序规则utf8_unicode_ci、utf8_general_ci的区别总结:https://www.jb51.net/article/… ...

March 26, 2019 · 2 min · jiezi