Ubuntu/Debian 安装 nginx

Nginx 是非常流行的 HTTP/HTTPS 服务器软件,它也可以作为反向代理服务器,邮件代理服务器,可以用于负载均衡,缓存等等。

基本的Nginx 由 master 进程和 worker 进程组成, master 读取配置文件,并维护 worker 进程,而 worker 会对请求进行处理。

Nginx 有两个主要的分支可供安装,stable 和 mainline 。这两个分支的主要区别可以从下图看出:

nginx two branchs

stable 分支并不意味着比 mainline 更加稳定可靠,事实上 mainline 更加稳定,因为 Nginx 开发人员会把所有的 bugfixes 都提交到该分支,而只会把 major bugfixes 提交到 stable 分支。然而另一方面,在 stable 分支的提交很少会影响到第三方模块,而在 mainline 上面的开发可能更快所有的新特性,更新,bugs,都会可能对第三方模块造成影响。

Nginx 官方 建议可以在任何时候使用 mainline 分支。而在生产环境使用 stable 分支。

安装

Use following command to install:

sudo apt-get install nginx
nginx -V

all config file is under /etc/nginx/nginx.conf

all vhost is under /etc/nginx/sites-available

program file is under /usr/sbin/nginx

log file is under /var/log/nginx , name of log file is access.log and error.log

init script has been created under /etc/init.d/

start from nginx 1.4.1, the default vhost direcotory is under /usr/share/nginx/html/

apt-get install nginx the config file is under /etc/nginx/site-available/default/

user data can be found in conf file.

sudo nginx -t to test and print log.

管理 nginx

start nginx

sudo service nginx start

stop nginx

sudo service nginx stop

other parameters:

reload        restart       start         status        stop

nginx 的配置文件及路径

托管网站内容 content

/usr/share/nginx/html/: actual web content, this path can be changed by altering Nginx configuration file.

默认 Ubuntu 16.04 会将 nginx 托管的地址指向 /var/www/html/ 目录。

服务配置 server configuration

Nginx 的主要配置都集中在 /etc/nginx 目录下:

/etc/nginx: The nginx configuration directory. All of the configuration files reside here.

  • /etc/nginx/sites-available/: The directory where per-site “server blocks” can be stored. Nginx will not use the configuration files found in this directory unless they are linked to the sites-enabled directory (see below). Typically, all server block configuration is done in this directory, and then enabled by linking to the other directory.

  • /etc/nginx/sites-enabled/: The directory where enabled per-site “server blocks” are stored. Typically, these are created by linking to configuration files found in the sites-available directory.

日志文件 log

/var/log/nginx/access.log: Every request to your web server is recorded in this log file unless Nginx is configured to do otherwise.

/var/log/nginx/error.log: Any Nginx errors will be recorded in this log.

nginx conf

nginx conf

user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections 768;
    # multi_accept on;
}

user
Defines which Linux user will own and run the nginx. Most Debian-based distributions use www-data.

worker_process
Defines how many threads, or simultaneous instances, of nginx to run. Learn more here

pid
Defines where nginx will write its master process ID, or PID.

设置 Nginx Server Blocks

Server Blocks 类似 Apache Virtual Hosts(虚拟主机) 概念,作用就是通过配置让同一台机器同时托管多个域名。

首先创建目录

sudo mkdir -p /var/www/www.einverne.info/html
sudo chmod -R 755 /var/www/

如果组和用户不是 www-data ,可以用 sudo chown -R www-data:www-data /var/www/www.einverne.info/html 来改变

默认情况下 nginx 包含一个默认的 server block 叫做 default , 创建其他 server block 的时候可以以它作为模板:

sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/www.einverne.info

然后修改该配置

server {
    listen 80;
    listen [::]:80;

    root /var/www/www.einverne.info/html;
    index index.html index.htm index.nginx-debian.html;

    server_name www.einverne.info;

    location / {
        try_files $uri $uri/ =404;
    }
}

修改 vim /etc/nginx/nginx.conf

http {
    . . .

    server_names_hash_bucket_size 64;

    . . .
}

最后需要 ln 启用新的虚拟主机

sudo ln -s /etc/nginx/sites-available/www.einverne.info /etc/nginx/sites-enabled/

使用 sudo nginx -t 来测试配置。

重启 sudo /etc/init.d/nginx reload 启用新配置。

更多的 Nginx 配置相关内容可以查看新文章 Nginx conf

从源代码编译 Nginx 安装

获取 Nginx 最新版本 http://nginx.org/en/download.html 使用最新 mainline 版本即可

下载最新源代码,解压

wget http://nginx.org/download/nginx-1.13.6.tar.gz && tar zxvf nginx-1.13.6.tar.gz

下载安装依赖

以下内容都使用 root 安装 su -

apt-get install -y gcc g++ make automake build-essential

安装 PCRE 库,Nginx Core 和 Rewrite 模块提供正则支持

apt-get install libpcre3 libpcre3-dev

OpenSSL

sudo apt-get install openssl libssl-dev libperl-dev

zlib 库,提供 Gzip 模块支持,压缩headers

apt-get install -y zlib1g zlib1g-dev 

XML xslt

apt-get install libxslt-dev

GD Library

apt-get install libgd2-dev

GeoIP Library

apt-get install libgeoip-dev

使用 APT 源安装 Nginx,并查看版本 nginx -V

nginx -V
nginx version: nginx/1.10.3 (Ubuntu)
built with OpenSSL 1.0.2g  1 Mar 2016
TLS SNI support enabled
configure arguments: --with-cc-opt='-g -O2 -fPIE -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -fPIE -pie -Wl,-z,relro -Wl,-z,now' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-ipv6 --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_addition_module --with-http_dav_module --with-http_geoip_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module --with-http_v2_module --with-http_sub_module --with-http_xslt_module --with-stream --with-stream_ssl_module --with-mail --with-mail_ssl_module --with-threads

Configure 后面的参数在编译时会需要用到

在反向代理中替换原网页内容,需要在编译时加入第三方模块 substitution

git clone https://github.com/yaoweibin/ngx_http_substitutions_filter_module

另一个方便快捷配置 Google 反代的模块

git clone https://github.com/cuber/ngx_http_google_filter_module

然后进入 Nginx 源代码目录

cd nginx-1.13.6/
./configure \
> --with-cc-opt='-g -O2 -fPIE -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -fPIE -pie -Wl,-z,relro -Wl,-z,now' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-ipv6 --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_addition_module --with-http_dav_module --with-http_geoip_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module --with-http_v2_module --with-http_sub_module --with-http_xslt_module --with-stream --with-stream_ssl_module --with-mail --with-mail_ssl_module --with-threads \
> --add-module=../ngx_http_substitutions_filter_module \
> --add-module=../ngx_http_google_filter_module

设置后,开始检查编译参数和环境,如果少了某些安装包,或者需要特定版本的lib就会报错,Google 一下需要的依赖包安装即可。安装之后再次 ./configure

检查通过显示

Configuration summary
  + using threads
  + using system PCRE library
  + using system OpenSSL library
  + using system zlib library

  nginx path prefix: "/usr/share/nginx"
  nginx binary file: "/usr/sbin/nginx"
  nginx modules path: "/usr/share/nginx/modules"
  nginx configuration prefix: "/etc/nginx"
  nginx configuration file: "/etc/nginx/nginx.conf"
  nginx pid file: "/run/nginx.pid"
  nginx error log file: "/var/log/nginx/error.log"
  nginx http access log file: "/var/log/nginx/access.log"
  nginx http client request body temporary files: "/var/lib/nginx/body"
  nginx http proxy temporary files: "/var/lib/nginx/proxy"
  nginx http fastcgi temporary files: "/var/lib/nginx/fastcgi"
  nginx http uwsgi temporary files: "/var/lib/nginx/uwsgi"
  nginx http scgi temporary files: "/var/lib/nginx/scgi"

./configure: warning: the "--with-ipv6" option is deprecated

然后编译

make
make install

然后将编译后的文件替换到发行版的安装目录

cp -rf objs/nginx /usr/sbin/nginx

检查 nginx -V 即可看到新编译的版本。

常用配置

Options Explanation
--prefix=<path> 安装的根目录,默认为 /usr/local/nginx
--sbin-path=<path> nginx 二进制文件路劲,如果没有设定,则使用 prefix 作为相对路劲
--conf-path=<path> 配置路径
--error-log-path=<path> 错误log
--pid-path=<path> nginx 写pid文件,通常在 /var/run
--lock-path=<path> 共享内存锁文件
--user=<user> 在哪个用户下运行 worker processes
--group=<group>
--with-debug 开启debug log 生产环境不要启用
--with-http_ssl_module 开启 HTTP SSL 模块,支持 HTTPS
--with-http_realip_module 开启真实来源IP
--with-http_flv_module 开启 flash 视频流
--with-http_mp4_module 开启 H.264/AAC 文件视频流
--with-http_gzip_static_module 开启预压缩文件传前检查,防止文件被重复压缩
--with-http_gunzip_module 开启为不支持 gzip 的客户端提前解压内容
--with-http_stub_status_module 开启 nginx 运行状态
--with_http_substitutions_filter_module 开启替换原网页内容

reference


2017-06-06 linux , nginx , ubuntu , debian , web

MySQL 中 utf8 和 utf8mb4 区别

今天在插入 MySQL 时遇到如下错误

Incorrect string value: ‘\xF0\x9F\x98\x81…’ for column ‘data’ at row 1

查证之后发现是因为插入的时候字符串中有 emoji,而 emoji 是 unicode 编码,MySQL 当时在建表时选择了 utf8 编码,导致了上述错误。mysql 支持的 utf8 编码最大字符长度为 3 字节,如果遇到 4 字节的宽字符就会插入异常了,因此引出了 utf8mb4 编码。MySQL 在 5.5.3 之后增加了这个 utf8mb4 的编码,mb4 就是 most bytes 4 的意思,专门用来兼容四字节的 unicode。好在 utf8mb4 是 utf8 的超集,除了将编码改为 utf8mb4 外不需要做其他转换。当然,为了节省空间,一般情况下使用 utf8 也就够了。

为了获取更好的兼容性,应该总是使用 utf8mb4 而非 utf8,对于一般性要求建议普通表使用 utf8, 如果这个表需要支持 emoji 就使用 utf8mb4。

深入 Mysql 字符集设置

字符 (Character) 是指人类语言中最小的表义符号。例如’A’、’B’等;给定一系列字符,对每个字符赋予一个数值,用数值来代表对应的字符,这一数值就是字符的编码 (Encoding)。例如,我们给字符’A’赋予数值 0,给字符’B’赋予数值 1,则 0 就是字符’A’的编码;给定一系列字符并赋予对应的编码后,所有这些字符和编码对组成的集合就是字符集 (Character Set)。例如,给定字符列表为{‘A’,’B’}时,{‘A’=>0, ‘B’=>1}就是一个字符集;

字符序 (Collation) 是指在同一字符集内字符之间的比较规则;确定字符序后,才能在一个字符集上定义什么是等价的字符,以及字符之间的大小关系;每个字符序唯一对应一种字符集,但一个字符集可以对应多种字符序,其中有一个是默认字符序 (Default Collation);

MySQL 中的字符序名称遵从命名惯例:以字符序对应的字符集名称开头;以 _ci(表示大小写不敏感)、_cs(表示大小写敏感)或 _bin(表示按编码值比较)结尾。例如:在字符序 utf8_general_ci 下,字符 “a” 和“A”是等价的;

字符集相关命令

检测字符集问题的一些手段

  • SHOW CHARACTER SET;
  • SHOW COLLATION;
  • SHOW VARIABLES LIKE ‘character%’;
  • SHOW VARIABLES LIKE ‘collation%’;

其他一些修改语句

# 修改数据库:
ALTER DATABASE database_name CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
# 修改表:
ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
# 修改表字段:
ALTER TABLE table_name CHANGE column_name column_name VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

2017-06-02 mysql , encoding , utf8 , unicode

Celery 最佳实践

尽量不要使用数据库作为 AMQP Broker

随着worker 的不断增多可能给数据库IO和连接造成很大压力。更具体来说不要把 Celery 的task数据和应用数据放到同一个数据库中。 Docker 上很多 相关的镜像。

使用多个队列

对于不同的 task ,尽量使用不同的队列来处理。

@app.task()
def my_taskA(a, b, c):
	print("doing something here...")
@app.task()
def my_taskB(x, y):
	print("doing something here...")

celery_config.py 中定义

task_queues=(
    Queue('default', routing_key='default'),
    Queue('other', routing_key='other'),

在 task 上定义

@app.task(queue='other')
def parse_something():
	pass

定义具有优先级的 workers

假如有一个 taskA 去处理一个队列 A 中的信息,一个 taskB 去处理队列 B 中的数据,然后起了 x 个 worker 去处理队列 A ,其他的 worker 去处理队列 B。而这时也可能会出现队列 B 中一些 task 急需处理,而此时堆积在队列 B 中的 tasks 很多,需要耗费很长时间来处理队列 B 中的 task。此时就需要定义优先队列来处理紧急的task。

celery 中可以在定义 Queue 时,指定 routing_key

Queue('other', routing_key='other_high'),
Queue('other', routing_key='other_low'),

然后定义

task_routes={
	# see
	# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-task_routes
	# http://docs.celeryproject.org/en/latest/userguide/routing.html#routing-basics
	'path.to.task' : {
		'queue': 'other',
		'routing_key': 'other_high'
	},
	'path.to.task' : {
		'queue': 'other',
		'routing_key': 'other_low'
	},
}

在启动 worker 时指定 routing_key

celery worker -E -l INFO -n workerA -Q other_high
celery worker -E -l INFO -n workerB -Q other_low

使用 celery 的错误处理机制

一般情况下可能因为网络问题,或者第三方服务暂时性错误而导致 task 执行出错。这时可以使用 celery task 的重试机制。

@app.task(bind=True, default_retry_delay=300, max_retries=5)
def my_task_A():
	try:
		print("doing stuff here...")
	except SomeNetworkException as e:
		print("maybe do some clenup here....")
		self.retry(e)

一般添加 default_retry_delay 重试等待时间和 max_retries 重试次数来限定,防止任务无限重试。

使用Flower

Flower 项目 为监控 celery tasks 和 workers 提供了一系列的便利。他使用 Web 界面提供 worker 当前状态, task 执行进度,各个worker 详细信息,甚至可以在网页上动态更行执行速率。

只有在真正需要时才去追踪 celery 的 result

任务的状态存储任务在退出时成功或者失败的信息,这些信息有些时候很重要,尤其是在后期分析数据时,但是大部分情况下更加关心task执行过程中真正想要保存的数据,而不是任务的状态。

所以,可以使用 task_ignore_result = True 来忽略任务结果。

不要将 Database/ORM 对象传入 tasks

不应该讲 Database objects 比如一个 User Model 传入在后台执行的任务,因为这些 object 可能包含过期的数据。相反应该传入一个 user id ,让 task 在执行过程中向数据库请求全新的 User Object。

以上七条来自:https://denibertovic.com/posts/celery-best-practices/

尽量简化 tasks

task 应该简洁(concise):

  • 将主要task逻辑包含在对象方法或者方法中
  • 确保方法抛出明确的异常 (identified exceptions)
  • 只有在切当的时机再实现重试机制

假设需要实现一个发送邮件的task

import requests
from myproject.tasks import app  # app is your celery application
from myproject.exceptions import InvalidUserInput
from utils.mail import api_send_mail
@app.task(bind=True, max_retries=3)
def send_mail(self, recipients, sender_email, subject, body):
	"""Send a plaintext email with argument subject, sender and body to a list of recipients."""
	try:
		data = api_send_mail(recipients, sender_email, subject, body)
	except InvalidUserInput:
		# No need to retry as the user provided an invalid input
		raise
	except Exception as exc:
		# Any other exception. Log the exception to sentry and retry in 10s.
		sentrycli.captureException()
		self.retry(countdown=10, exc=exc)
	return data

通常任务真实的实现只有一层,而剩余的其他部分都是错误处理。而通常这么处理会更加容易维护。

设置task 超时

设置一个全局的任务超时时间

task_soft_time_limit = 600   # 600 seconds

超时之后会抛出 SoftTimeLimitExceeded 异常

from celery.exceptions import SoftTimeLimitExceeded
@app.task
def mytask():
	try:
		return do_work()
	except SoftTimeLimitExceeded:
		cleanup_in_a_hurry()

同样,定义任务时也能够指定超时时间,如果任务block尽快让其失败,尽量配置 task 的超时时间。不让长时间 block task 的进程。

@app.task(
	bind=True,
	max_retries=3,
	soft_time_limit=5 # time limit is in seconds.
)
def send_mail(self, recipients, sender_email, subject, body):
	...

将task重复部分抽象出来

使用task的基类来复用部分 task 逻辑

from myproject.tasks import app
class BaseTask(app.Task):
	"""Abstract base class for all tasks in my app."""
	abstract = True
	def on_retry(self, exc, task_id, args, kwargs, einfo):
		"""Log the exceptions to sentry at retry."""
		sentrycli.captureException(exc)
		super(BaseTask, self).on_retry(exc, task_id, args, kwargs, einfo)
	def on_failure(self, exc, task_id, args, kwargs, einfo):
		"""Log the exceptions to sentry."""
		sentrycli.captureException(exc)
		super(BaseTask, self).on_failure(exc, task_id, args, kwargs, einfo)

@app.task(
	bind=True,
	max_retries=3,
	soft_time_limit=5,
	base=BaseTask)
def send_mail(self, recipients, sender_email, subject, body):
	"""Send a plaintext email with argument subject, sender and body to a list of recipients."""
	try:
		data = api_send_mail(recipients, sender_email, subject, body)
	except InvalidUserInput:
		raise
	except Exception as exc:
		self.retry(countdown=backoff(self.request.retries), exc=exc)
	return data

将大型task作为类

一般情况下将使用方法作为 task 就已经足够,如果遇到大型 task ,可以将其写成

class handle_event(BaseTask):   # BaseTask inherits from app.Task
	def validate_input(self, event):
		...
	def get_or_create_model(self, event):
		...
	def stream_event(self, event):
		...
	def run(self, event):
		if not self.validate_intput(event):
			raise InvalidInput(event)
		try:
			model = self.get_or_create_model(event)
			self.call_hooks(event)
			self.persist_model(event)
		except Exception as exc:
			self.retry(countdown=backoff(self.request.retries), exc=exc)
		else:
			self.stream_event(event)

单元测试

直接调用 worker task 中的方法,不要使用 task.delay() 。 或者使用 Eager Mode,使用 task_always_eager 设置来启用,当启用该选项之后,task会立即被调用。而 这两种方式都只能测试task worker 中的内容,官方1并不建议这么做。

对于执行时间长短不一的任务建议开启 -Ofair

celery 中默认都会有 prefork pool 会异步将尽量多的任务发送给 worker 执行,这也意味着 worker 会预加载一些任务。这对于通常的任务会有性能提升,但这也容易导致因为某一个长任务处理时间长二导致其他任务处于长时间等待状态。

对于执行时间长短不一的任务可以开启 -Ofair

celery -A proj worker -l info -Ofair

设置worker的数量

Celery 默认会开启和CPU core一样数量的 worker,如果想要不想开启多个 worker ,可以通过启动时指定 --concurrency 选项

--concurrency=1

reference


2017-05-21 celery , python , web , database , redis , queue

每天学习一个命令:sudo 来管理Linux下权限

sudo 表示 “superuser do”。 它允许已验证的用户以其他用户的身份来运行命令。其他用户可以是普通用户或者超级用户。然而,大部分时候我们用它来以提升的权限来运行命令。

sudo 命令与安全策略配合使用,默认安全策略是 sudoers,可以通过文件 /etc/sudoers 来配置。其安全策略具有高度可拓展性。人们可以开发和分发他们自己的安全策略作为插件。

sudo 与 su 的区别

在 GNU/Linux 中,有两种方式可以用提升的权限来运行命令:

  • su 命令
  • sudo 命令

su 表示 “switch user”。使用 su,我们可以切换到 root 用户并且执行命令。但是这种方式存在一些缺点:

  • 需要与他人共享 root 的密码
  • 无法审查用户执行的命令
  • 使用 root 用户不能授予有限的访问权限

sudo 以独特的方式解决了这些问题

  • 不需要共享 root 用户的密码,普通用户可以使用自己的密码提升权限来执行命令
  • sudo 用户的所有操作都会被记录下来,管理员可以随时审查sudo用户执行了哪些操作
  • 可控的 sudo 用户的访问,我们可以限制用户只能执行某些命令

在基于 Debian 的 GNU/Linux 中,所有活动都记录在 /var/log/auth.log 文件中

使用 sudo

为普通用户添加sudo权限

添加普通用户为 sudo 用户

  1. 编辑 /etc/sudoers 文件:

     sudo visudo
    
  2. 添加以下行来允许用户 einverne 有 sudo 权限:

     einverne ALL=(ALL) ALL
    

上述命令中:

  • einverne 表示用户名
  • 第一个 ALL 指示允许从任何终端、机器访问 sudo
  • 第二个 (ALL) 指示 sudo 命令被允许以任何用户身份执行
  • 第三个 ALL 表示所有命令都可以作为 root 执行

注:/etc/sudoers 文件必须以 visudo 命令来修改,该命令可以防止因为文件格式错误而导致问题,如果visudo默认的编辑器不是你常用的编辑,可以通过如下方法来修改

sudo update-alternatives --config editor

将用户添加到sudo组

上面提到编辑 /etc/sudoers 文件给用户添加 sudo 权限,不过还有一种更加简单的方法就是将用户添加到sudo或者admin组,修改 /etc/group 文件,然后在其中添加

sudo:x:27:username,username1

然后该 username 用户提升到 sudo 组(默认sudo组应该在/etc/sudoers中配置好权限)即可使用 sudo 命令。

使用sudo提升权限执行命令

要用提升的权限执行命令,只需要在命令前加上 sudo,如下所示:

sudo cat /etc/passwd

当你执行这个命令时,它会询问用户 einverne 的密码,而不是 root 用户的密码。

使用sudo以其他用户身份执行命令

除此之外,我们可以使用 sudo 以另一个用户身份执行命令。例如,在下面的命令中,用户 einverne 以用户 demo 的身份执行命令:

sudo -u demo whoami
[sudo] password for einverne:
demo

内置命令行为

sudo 的一个限制是 —— 它无法使用 Shell 的内置命令。例如, history 记录是内置命令,如果你试图用 sudo 执行这个命令,那么会提示如下的未找到命令的错误:

sudo history
[sudo] password for einverne:
sudo: history: command not found

为了克服上述问题,我们可以访问 root shell,并在那里执行任何命令,包括 Shell 的内置命令。

要访问 root shell, 执行下面的命令:

sudo bash

执行完这个命令后——您将观察到提示符变为井号(#)。

技巧

这里将列举一些常用的sudo小技巧,可以用于日常任务。

以 sudo 用户执行之前的命令

让我们假设你想用提升的权限执行之前的命令,那么下面的技巧将会很有用:

sudo !4

上面的命令将使用提升的权限执行历史记录中的第 4 条命令。

在 Vim 里面使用 sudo 命令

很多时候,我们编辑系统的配置文件时,在保存时才意识到我们需要 root 访问权限来执行此操作。因为这个可能让我们丢失我们对文件的改动,我们可以在 Vim 中使用下面的命令来解决这种情况:

:w !sudo tee %

上述命令中:

  • 冒号 (:) 表明我们处于 Vim 的退出模式
  • 感叹号 (!) 表明我们正在运行 shell 命令
  • sudo 和 tee 都是 shell 命令
  • 百分号 (%) 表明从当前行开始的所有行

使用 sudo 执行多个命令

至今我们用 sudo 只执行了单个命令,但我们可以用它执行多个命令。只需要用分号 (;) 隔开命令,如下所示:

sudo -- bash -c 'pwd; id;'

上述命令中

  • 双连字符 (–) 停止命令行切换
  • bash 表示要用于执行命令的 shell 名称
  • -c 选项后面跟着要执行的命令

运行 sudo 命令时免去输入密码

当第一次执行 sudo 命令时,它会提示输入密码,默认情形下密码会被缓存 15 分钟。可以使用 NOPASSWD 关键字来禁用密码认证:

# User privilege specification
username ALL=(ALL) NOPASSWD: ALL 单独配置一个用户

# Allow members of group sudo to execute any command
%sudo ALL=(ALL) NOPASSWD:ALL 配置一组用户

限制 sudo 用户执行某些命令

为了提供受控访问,我们可以限制 sudo 用户只能执行某些命令。例如,下面的行只允许执行 echo 和 ls 命令 。

einverne ALL=(ALL) NOPASSWD: /bin/echo /bin/ls

深入了解 sudo

让我们进一步深入了解 sudo 命令。

ls -l /usr/bin/sudo
-rwsr-xr-x 1 root root 145040 Jun 13  2017 /usr/bin/sudo

如果仔细观察文件权限,则发现 sudo 上启用了 setuid 位。当任何用户运行这个二进制文件时,它将以拥有该文件的用户权限运行。在所示情形下,它是 root 用户。

为了显示这一点,我们可以使用 id 命令,如下所示:

id
uid=1002(einverne) gid=1002(einverne) groups=1002(einverne)

当我们不使用 sudo 执行 id 命令时,将显示用户 einverne 的 id。

sudo id
uid=0(root) gid=0(root) groups=0(root)

但是,如果我们使用 sudo 执行 id 命令时,则会显示 root 用户的 id。

reference


2017-05-16 linux , sudo , security , permission , privilege

Git 使用过程中遇到的小技巧

Git 使用过程中遇到的小技巧,平时没有 commit, merge, branch 用的那么勤快,但是需要时也需要查看一下,因此记录一下,以免忘记。

将其他分之中多次提交合并到 master 的一次提交

开发中经常使用分支开发,因此不可避免的在开发中向 dev,或者 bugfix 分支进行多次提交,而有些提交可能仅仅为了测试,commit message 也没有认认真真写,所以当开发完成,或者 bug 修复完成想要合并到 master 分支时,不希望保留中间糟糕的提交信息,有一种方法是使用 merge 的 --squash

而在之前我可能会用 soft reset 掉一些提交,然后重新合并为一次提交,而得知 merge 的 squash 之后,可以轻松将其他分之中的多次提交内容一次性合并到工作区中,然后使用 commit 作为提交。

git merge --squash <branch name>
# after
git commit -s
# then write your commit message

这里是 --squash 的解释:

Produce the working tree and index state as if a real merge happened (except for the merge information), but do not actually make a commit or move the HEAD, nor record $GIT_DIR/MERGE_HEAD to cause the next git commit command to create a merge commit. This allows you to create a single commit on top of the current branch whose effect is the same as merging another branch (or more in case of an octopus).

恢复 hard reset 丢失的 commit

有的时候会做了一些提交,但经过 review 或者中途发现变化需要丢弃的时候经常用 git reset 来丢掉一些 commit,一般情况下我都会使用 git reset --soft HEAD~1 来丢掉上一个提交,给自己重新检查一下上一次提交的内容。而有时可能不注意直接 git reset --hard <commit-id> 直接丢弃了好几个提交。等敲完回车才追悔莫及,此时就凸显了 git 的强大之处。其实在 Git 中做过的所有提交记录,都是有保存的,每一次修改 HEAD 的操作都被记录到了本地。

git 有一个命令 git reflog 可以查看所有对 HEAD 的变更操作,使用 reflog 命令找到需要恢复的 commit id 然后使用 git reset --hard <commit-id> 来恢复到那一次提交就可以了。

关联本地分支和远程分支

关联本地分支和远程分支,一般情况下使用 git push 时,直接将本地分支推送到远程同名分支,但是如果新项目不是 clone 远程,或者中途曾经更改了 remote,那么有可能 git 就不知道本地分支对应的远程分支,这时候使用 push 或者 pull 的时候就有可能会出错。

使用

git branch --set-upstream-to=origin/master master

来将本地 master 分支关联到 origin/master 分支。

或者也可以在 push 时自动关联上

git push -u origin master

删除本地某一次提交

本地做了很多修改,而想要放弃其中某一次提交可以使用 git rebase -i , 对于最后一次提交可以使用 git reset --hard HEAD~1 来撤销

对于之前的提交,如果想要删除,可以使用

git rebase -i HEAD~N

来查看本地前 N 次提交,然后编辑文件删除某一次 commit 即可。更多的信息可以参考 Git book

PS. 不要用来改变已经 push 到远端的提交,除非明确的知道想要做的事情,可以使用 force push.


2017-05-14 git , linux , 经验总结,

URL 短域名

逛博客看到别人在讨论短URL的设计实现,然后偶然间发现了 GitHub 曾经推出1过的短域名服务 Git.io

创建短域名

curl -i https://git.io -F "url=https://github.com/einverne"                  
HTTP/1.1 100 Continue

HTTP/1.1 201 Created
Server: Cowboy
Connection: keep-alive
Date: Sun, 14 May 2017 03:05:40 GMT
Status: 201 Created
Content-Type: text/html;charset=utf-8
Location: https://git.io/v97cY
Content-Length: 27
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Runtime: 0.312051
X-Node: 09a65813-05e0-40a2-a9bf-6dd88da1cdbc
X-Revision: 392798d237fc1aa5cd55cada10d2945773e741a8
Strict-Transport-Security: max-age=31536000; includeSubDomains
Via: 1.1 vegur

使用短域名 302 跳转

curl -i https://git.io/v97cY

HTTP/1.1 302 Found
Server: Cowboy
Connection: keep-alive
Date: Sun, 14 May 2017 03:06:58 GMT
Status: 302 Found
Content-Type: text/html;charset=utf-8
Location: https://github.com/einverne
Content-Length: 0
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Runtime: 0.005605
X-Node: d567f758-ba0e-4e8b-95ba-b6a80730cc20
X-Revision: 392798d237fc1aa5cd55cada10d2945773e741a8
Strict-Transport-Security: max-age=31536000; includeSubDomains
Via: 1.1 vegur

还可以使用 code 参数来指定生成的短链接名字,比如

curl -i https://git.io -F "url=https://github.com/...." -F "code=abcd"

git.io 缩短的域名必须是 github 站相关的域名,其他网站的地址它是不会缩短的。并且每个链接只能被缩短一次,如果第二次再请求会返回和上一次缩短一样的结果。

所以无奈啦,

goo.gl

Google 的短域名服务其实已经用很久了 https://goo.gl/ ,相比来说,有几个好处

  • 在登录状态下生成的短域名能够统计跳转数量
  • 在生成之后也与控制面板可以查看曾经生成的短链接
  • 直接在生成的短域名后加上 .qr 可以查看二维码,比如 https://goo.gl/xEeWKp 添加 qr https://goo.gl/xEeWKp.qr

开源版本

YOURLS 项目,使用 PHP 实现短域名 https://github.com/YOURLS/YOURLS 项目到目前已经非常完善了。

更多的项目可以参考: https://github.com/topics/url-shortener

学习版本

PHP 版本 https://github.com/takashiki/Ourls

一个比较好玩的JS纯前端实现,将跳转信息保存到浏览器 Local Storage 中2, 可以学习一下项目中对本地 Storage 操作的部分3,应该挺有意思。


2017-05-14 github , google , short-url , url

目录 /usr/local vs /opt 的区别及 JDK 安装

今天看 JDK 的路径突然发现我在两台机子上,一台装在了 /usr/local/ 目录下,而我自己的 Mint 装在了 /opt/ 目录下。感觉对 Linux 目录结构还需要增加了解,就Google了一下。

/usr/local/opt 目录设计为存放非系统级命令,而 /usr/local 目录一般用来防止管理员通过本地编译安装的程序,比如通过 ./configure; make; make install 等命令安装的程序,该目录的目的就是为了使用户产生的命令不和系统命令产生冲突。

/opt 目录一般用来安装非捆绑的软件程序,每个应用都有其自己的子目录,比如在安装Chrome 之后,Chrome 完整的程序和其资源文件都会存在 /opt/google/chrome 下。

安装JDK 的两种方式

因此在 Linux 下如果手工安装 JDK 7/8 时,可以将安装路径手动指定到 /opt 目录下,方便管理。

安装 JDK 的两种方式,一种是直接通过 apt 包管理来安装

sudo add-apt-repository -y ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java8-installer

然后使用 java -version 来验证。

或者直接从官网下载压缩包,将文件内容解压到 /opt/java/ 目录下。然后配置环境变量 JAVA_HOME 指向 Java bin 的目录。

wget http://..../jdk-8u91-linux-x64.tar.gz
tar -zxvf jdk-8u91-linux-x64.tar.gz -C /opt/java/
vim ~/.zshrc # or ~/.bashrc

添加

export JAVA_HOME=/opt/java/jdk1.8.0_91/
export PATH="$PATH:$JAVA_HOME/bin/"

使环境变量生效

source ~/.zshrc

更新提供JDK

运行命令会得到目前系统安装的 JDK 或者 JRE,选择序号确定即可。

$ sudo update-alternatives --config java
There are 3 choices for the alternative java (providing /usr/bin/java).
  Selection    Path                                            Priority   Status
------------------------------------------------------------
* 0            /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java   1071      auto mode
  1            /usr/lib/jvm/java-6-openjdk-amd64/jre/bin/java   1061      manual mode
  2            /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java   1071      manual mode
  3            /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java   1081      manual mode
Press enter to keep the current choice[*], or type selection number:

reference


2017-05-13 Linux , FHS , Java

SQLAlchemy session 使用问题

在更改 SQLAlchemy Session 从每次请求都创建到共享同一个 Session 之后遇到了如下问题:

StatementError: (sqlalchemy.exc.InvalidRequestError) Can’t reconnect until invalid transaction is rolled back [SQL: ]

或者是

raised unexpected: OperationalError(“(_mysql_exceptions.OperationalError) (2006, ‘MySQL server has gone away’)”,)

错误是 SQLAlchemy 抛出。原因是你从 pool 拿的 connection 没有以 session.commit 或 session.rollback 或者 session.close 放回 pool 里。这时 connection 的 transaction 没有完结(rollback or commit)。 而不知什么原因(recyle 了,timeout 了)你的 connection 又死掉了,你的 sqlalchemy 尝试重新连接。由于 transaction 还没完结,无法重连。

正确用法确保 session 在使用完成后用 session.close, session.commit 或者 session.rollback 把连接还回 pool

SQLAlchemy 数据库连接池使用

sessions 和 connections 不是相同的东西, session 使用连接来操作数据库,一旦任务完成 session 会将数据库 connection 交还给 pool。

在使用 create_engine 创建引擎时,如果默认不指定连接池设置的话,一般情况下,SQLAlchemy 会使用一个 QueuePool 绑定在新创建的引擎上。并附上合适的连接池参数。

在以默认的方法 create_engine 时(如下),就会创建一个带连接池的引擎。

engine = create_engine('mysql+mysqldb://root:password@127.0.0.1:3306/dbname')

在这种情况下,当你使用了 session 后就算显式地调用 session.close(),也不能把连接关闭。连接会由 QueuePool 连接池进行管理并复用。

这种特性在一般情况下并不会有问题,不过当数据库服务器因为一些原因进行了重启的话。最初保持的数据库连接就失效了。随后进行的 session.query() 等方法就会抛出异常导致程序出错。

如果想禁用 SQLAlchemy 提供的数据库连接池,只需要在调用 create_engine 是指定连接池为 NullPool,SQLAlchemy 就会在执行 session.close() 后立刻断开数据库连接。当然,如果 session 对象被析构但是没有被调用 session.close(),则数据库连接不会被断开,直到程序终止。

下面的代码就可以避免 SQLAlchemy 使用连接池:

#!/usr/bin/env python
#-*- coding: utf-8 -*-

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import NullPool

engine = create_engine('mysql+mysqldb://root:password@127.0.0.1:3306/dbname', poolclass=NullPool)
Session = sessionmaker(bind=engine)
session = Session()
usr_obj_list = session.query(UsrObj).all()
print usr_obj_list[0].id
session.close()

create_engine() 函数和连接池相关的参数有:

  • -pool_recycle, 默认为 -1, 推荐设置为 7200, 即如果 connection 空闲了 7200 秒,自动重新获取,以防止 connection 被 db server 关闭。
  • -pool_size=5, 连接数大小,默认为 5,正式环境该数值太小,需根据实际情况调大
  • -max_overflow=10, 超出 pool_size 后可允许的最大连接数,默认为 10, 这 10 个连接在使用过后,不放在 pool 中,而是被真正关闭的。
  • -pool_timeout=30, 获取连接的超时阈值,默认为 30 秒

直接只用 create_engine 时,就会创建一个带连接池的引擎

engine = create_engine('postgresql://postgres@127.0.0.1/dbname')

当使用 session 后就显示地调用 session.close(),也不能把连接关闭,连接由 QueuePool 连接池管理并复用。

引发问题

当数据库重启,最初保持的连接就会失败,随后进行 session.query() 就会失败抛出异常 mysql 数据 ,interactive_timeout 等参数处理连接的空闲时间超过(配置时间),断开

何时定义 session,何时提交,何时关闭

基本

  • 通常来说,将 session 的生命周期和访问操作数据库的方法对象隔离和独立
  • 确保 transaction 有非常清晰的开始和结束,保持 transaction 简短,也就意味着让 transaction 能在一系列操作之后终止,而不是一直开放着。

    from contextlib import contextmanager

    @contextmanager def session_scope(): “"”Provide a transactional scope around a series of operations.””” session = Session() try: yield session session.commit() except: session.rollback() raise finally: session.close()

是否线程安全

Session 不是为了线程安全而设计的,因此确保只在同一个线程中使用。

如果实际上有多个线程参与同一任务,那么您考虑在这些线程之间共享 Session 及其对象;但是在这种极不寻常的情况下,应用程序需要确保实现正确的 locking scheme,以便不会同时访问 Session 或其状态。处理这种情况的一种更常见的方法是为每个并发线程维护一个 Session,而是将对象从一个 Session 复制到另一个 Session,通常使用 Session.merge() 方法将对象的状态复制到本地的新对象中。

scoped session

想要线程安全时使用 scoped_session() ,文档解释

the scoped_session() function is provided which produces a thread-managed registry of Session objects. It is commonly used in web applications so that a single global variable can be used to safely represent transactional sessions with sets of objects, localized to a single thread.

using transactional=False is one solution, but a better one is to simply rollback(), commit(), or close() the Session when operations are complete - transactional mode (which is called “autocommit=False” in 0.5) has the advantage that a series of select operations will all share the same isolated transactional context..this can be more or less important depending on the isolation mode in effect and the kind of application.

DBAPI has no implicit “autocommit” mode so there is always a transaction implicitly in progress when queries are made.

This would be a fairly late answer. This is what happens: While using the session, a sqlalchemy Error is raised (anything which would also throw an error when be used as pure SQL: syntax errors, unique constraints, key collisions etc.).

You would have to find this error, wrap it into a try/except-block and perform a session.rollback().

After this you can reinstate your session.

flush 和 commit 区别

  • flush 预提交,等于提交到数据库内存,还未写入数据库文件;
  • commit 就是把内存里面的东西直接写入,可以提供查询了;

reference


2017-05-12 python , sqlalchemy , mysql , orm , sql , session , flask

保持SSH连接

记录一些 SSH 相关的内容,经常使用。

SSH是Secure Shell的缩写, 是一个应用层的加密网络协议, 它不只可以用于远程登录, 远程命令执行,还可用于数据传输. 当然它由ssh Client和ssh Server端组成, 有很多实现, Ubuntu上就默认安装的OpenSSH, Client端叫做ssh, Server端叫做sshd. OpenSSH只用来做远程登录和命令执行.

免密登录

查看本地 ~/.ssh/ 目录是否有 id_rsa.pub,如果没有,在本地创建公钥

ssh-keygen -t rsa

一路回车到底

然后把本地公钥复制到远程机器的 ~/.ssh/ 目录下,并命名为 authorized_keys

scp ~/.ssh/id_rsa.pub username@hostname:~/.ssh/authorized_keys
# or
ssh-copy-id -i ~/.ssh/id_rsa.pub username@hostname

如果远程主机配置了多台机器免密登录,最好将 id_ras.pub 追加而不是覆盖到 authorized_keys

cat id_rsa.pub >> .ssh/authorized_keys 

保持连接

配置服务端

SSH总是被强行中断,导致效率低下,可以在服务端配置,让 server 每隔30秒向 client 发送一个 keep-alive 包来保持连接:

vim /etc/ssh/sshd_config

添加

ClientAliveInterval 30
ClientAliveCountMax 60

第二行配置表示如果发送 keep-alive 包数量达到 60 次,客户端依然没有反应,则服务端 sshd 断开连接。如果什么都不操作,该配置可以让连接保持 30s*60 , 30 min

重启本地 ssh

sudo service ssh restart

如果找不到 ssh,”Failed to restart ssh.service: Unit ssh.service not found.” ,需要安装

sudo apt-get install openssh-server

配置客户端

如果服务端没有权限配置,或者无法配置,可以配置客户端 ssh,使客户端发起的所有会话都保持连接:

vim /etc/ssh/ssh_config

添加

ServerAliveInterval 30
ServerAliveCountMax 60

本地 ssh 每隔30s向 server 端 sshd 发送 keep-alive 包,如果发送 60 次,server 无回应断开连接。

下面是 man ssh_config 的内容

ServerAliveCountMax Sets the number of server alive messages (see below) which may be sent without ssh(1) receiving any messages back from the server. If this threshold is reached while server alive messages are being sent, ssh will disconnect from the server, terminating the session. It is important to note that the use of server alive messages is very different from TCPKeepAlive (below). The server alive messages are sent through the encrypted channel and therefore will not be spoofable. The TCP keepalive option enabled by TCPKeepAlive is spoofable. The server alive mechanism is valuable when the client or server depend on knowing when a connection has become inactive.

The default value is 3. If, for example, ServerAliveInterval (see below) is set to 15 and ServerAliveCountMax is left at the default, if the server becomes unresponsive, ssh will disconnect after approximately 45 seconds. This option applies to protocol version 2 only; in protocol version 1 there is no mechanism to request a response from the server to the server alive messages, so disconnection is the responsibility of the TCP stack.

ServerAliveInterval Sets a timeout interval in seconds after which if no data has been received from the server, ssh(1) will send a message through the encrypted channel to request a response from the server. The default is 0, indicating that these messages will not be sent to the server, or 300 if the BatchMode option is set. This option applies to protocol version 2 only. ProtocolKeepAlives and SetupTimeOut are Debian-specific compatibility aliases for this option.

共享SSH连接

如果需要在多个窗口中打开同一个服务器连接,可以尝试添加 ~/.ssh/config,添加两行

ControlMaster auto
ControlPath ~/.ssh/connection-%r@%h:%p

配置之后,第二条连接共享第一次建立的连接,加快速度。

添加长连接配置

ControlPersist 4h

每次SSH连接建立之后,此条连接会被保持 4 小时,退出服务器之后依然可以重用。

配置连接中转

ForwardAgent yes

当需要从一台服务器连接另外一个服务器,而在两台服务器中传输数据时,可以不用通过本地电脑中转,直接配置以上 ForwardAgent 即可。

最终, ~/.ssh/config 下的配置:

Host *
	ForwardAgent yes
	ServerAliveInterval 3
	ServerAliveCountMax 20
	TCPKeepAlive no
	ControlMaster auto
	ControlPath ~/.ssh/connection-%r@%h:%p
	ControlPersist 4h
	Compression yes

同一台机器配置多个key

之前写过一篇文章总结,在同一台机器上同时使用 GitHub,和 GitLab 的key,可以具体参考这里


2017-05-07 ssh , linux , git , github , gitlab

MySQL 命令记录

mysql 命令行操作相关内容,防止遗忘。mysql 常用命令记录,总结。

install

Under Ubuntu

sudo apt-get install mysql-server

启动停止 mysql 服务

可以使用如下命令启动,停止,重启 MySQL 服务

sudo /etc/init.d/mysql {start | stop | status | restart}

sudo service mysql {start | stop | status | restart}

Windows 下可以使用 net 命令

net start mysql

同理,启动其他比如微软自己的 SQL Server 可以使用

net start mssqlserver
# 或者重启 tomcat
net start tomcat6

mysql_commands

Access mysql shell

终端下输入

mysql -u [root] -p

之后输入 root 的密码

需要注意:

  • 所有的 mysql 命令以分号结束,如果没有分号结束,命令不会被执行
  • 不是必须的,但是通常 MySQL 命令大写,数据库,表,用户名或者其他 text 小写。 MySQL 命令并不区分大小写。

常用命令

常用命令中也大致可以分成几类,一类是通用命令,包括查看 MySQL 数据库,及查看基本表结构的。还有就是创建修改表结构,最后最常用的就是增删改查数据的命令。

通用命令

查看数据库

SHOW DATABASES;

输出

mysql> show DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| parker             |
| performance_schema |
| sys                |
| test               |
| youku              |
+--------------------+
7 rows in set (0.00 sec)

创建数据库

CREATE DATABASE dbname;

删除数据库

DROP DATABASE dbname;

使用数据库

USE dbname;

显示数据库中表

SHOW tables;

定义修改表结构

创建表

CREATE TABLE table_name (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20) , signup_date DATE);

查看表结构

DESCRIBE table_name;

增加新列

ALTER TABLE table_name **ADD COLUMN** [column] VARCHAR(40);

比如新增一列自增 id

ALTER TABLE [table] ADD COLUMN [column] int NOT NULL AUTO_INCREMENT PRIMARY KEY;

如果想要自定义新列的位置,可以使用 AFTER

ALTER TABLE table_name **ADD** email VARCHAR(40) AFTER name;

删除列

ALTER TABLE table_name **DROP** column

修改列,或者修改列类型

ALTER TABLE tablename **MODIFY COLUMN** column_name VARCHAR(20);
ALTER TABLE tablename **ALTER COLUMN** column_name VARCHAR(20);

修改表结构,添加组合 Primary Key,将两列数据作为 PK

如果 PRIMARY KEY 不存在

ALTER TABLE [table]  ADD primary key(column1, column2);

如果 PRIMARY KEY 存在

ALTER TABLE [table]  DROP PRIMARY KEY, ADD primary key(column1, column2);

修改表名

ALTER TABLE origin_table_name **RENAME TO** new_table_name

增删改查

Like 通配符

  • % 表示任意数量的未知字符串
  • _ 一个未知字符串

插入记录

INSERT INTO `table_name` (`name`, `signup_date`) VALUES ("Verne", "2017-05-01");

更新记录

UPDATE [table] SET [column] = 'Y' WHERE `potluck`.`name` ='Sandy';

删除一行记录

DELETE from table_name where column_name = "value";

获取记录条数

SELECT COUNT([column]) FROM [table];

模糊查询

SELECT * FROM [table] WHERE [column] LIKE '%value%';

排序

SELECT * FROM [table] WHERE [column] ORDER BY [column] ASC LIMIT [value];

Order 可以使用 DESC, ASC

删除表中所有记录

TRUNCATE table [table]

其他命令

查看创建表 DDL

show create table [table_name];

导出数据

mysqldump -u [username] -p [database] > db_backup.sql
mysqldump -u [username] -p [database] [table_name] > db_backup.sql

导入还原数据

mysql -u [username] -p [database] < db_backup.sql
mysql -u [username] -p -h localhost [database] < db_backup.sql

查看数据库中所有用户

SELECT User,Host FROM mysql.user;

创建新用户

CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';

创建新用户时,如果密码选择太简单可能会导致密码安全检查无法过去,这时可以设置 MySQL 的 validate_password_policy 来使用简单密码:

mysql> set global validate_password_policy=0;

在 0 或者 LOW 下,密码只验证长度。在 1 或者 MEDIUM 下,密码会验证长度,必须包含数字,大小写,特殊字符。在 2 或者 STRONG 下,还会验证是否在字典中。

其中

mysql> select @@validate_password_length;

指定了使用密码的长度,默认为 8 位。

授予用户某个数据库全部权限

GRANT ALL ON [database].* TO 'user'@'localhost';

修改密码

mysqladmin -u root -p old_password password new_password

删除用户

DROP USER ‘user1’@‘localhost';

如果在创建新用户时提醒密码 weak,则可以使用如下命令来禁用密码校验

uninstall plugin validate_password;
// MySQL 8.0.4 以上
UNINSTALL COMPONENT 'file://component_validate_password';

远程连接

如果想要远程通过 root 连接 MySQL,先查看一下 MySQL 配置 /etc/mysql.my.cnf,需要注释其中

#bind-address = 127.0.0.1

默认 3306 端口只允许本地访问,然后重启 /etc/init.d/mysql restart

修改 MySQL 数据库中的 user 表,使得 root 能够远程登录

mysql -u root –p
mysql>use mysql;
mysql>update user set host = '%' where user = 'root';
mysql>select host, user from user;

Python 连接操作 MySQL

Python 2.x 中使用 MySQLdb 来连接 MySQL 数据库。在 Python 3.x 中使用 P 有 MySQL,使用方式 import pymysql,而其他操作几乎一致。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import MySQLdb

"""
pip install MySQL-python
MySQLdb 是用于 Python 链接 Mysql 数据库的接口,它实现了 Python 数据库 API 规范 V2.0,基于 MySQL C API 上建立的。

在使用 Python 连接之前确保已经有数据表建立

> mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 25
Server version: 5.7.18-0ubuntu0.16.04.1 (Ubuntu)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| spy                |
| sys                |
+--------------------+
5 rows in set (0.00 sec)

mysql> CREATE DATABASE testdb;
Query OK, 1 row affected (0.00 sec)

mysql> CREATE USER 'testuser'@'localhost' IDENTIFIED BY '12345678';
Query OK, 0 rows affected (0.01 sec)

mysql> USE testdb;
Database changed
mysql> GRANT ALL ON testdb.* TO 'testuser'@'localhost';
Query OK, 0 rows affected (0.00 sec)

"""

con = MySQLdb.connect('localhost', 'testuser', '12345678', 'testdb', charset='utf8')


def create_table():
    with con:
        cur = con.cursor()
        cur.execute("DROP TABLE IF EXISTS Users")
        cur.execute("CREATE TABLE Users(id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50))")


def insert(name):
    with con:
        cur = con.cursor()
        cur.execute("INSERT INTO Users(name) VALUES('" + name + "')")


def get():
    with con:
        cur = con.cursor()
        cur.execute("SELECT * FROM Users")

        rows = cur.fetchall()
        for row in rows:
            print row


def update(old_name, new_name):
    with con:
        cur = con.cursor()
        cur.execute("UPDATE Users SET name = %s WHERE name = %s", (new_name, old_name))


def transaction():
    try:
        con.cursor()
        insert("CC")
        insert("David")
        insert("Einstein")
        con.commit()
    except MySQLdb.Error, e:
        if con:
            con.rollback()
        print "Error %d: %s" % (e.args[0],e.args[1])


if __name__ == '__main__':

    try:

        cur = con.cursor()
        sql = "SELECT VERSION()"
        cur.execute(sql)

        version = cur.fetchone()
        print "MySQL version : %s " % version
    except MySQLdb.Error, e:
        print "Error %d: %s" % (e.args[0], e.args[1])
        if con:
            con.close()

    create_table()
    insert("Alex")
    insert("Verne")
    get()
    update("Alex", "Bob")
    get()
    transaction()
    get()
    if con:
        con.close()

reference

一个更加详细的 Cheatsheet

https://gist.github.com/einverne/0c256fe6351a89c7815b75f0d9964bfe

推荐命令行

mysql 自带的命令行工具不会自动补全,这里推荐 mycli 可以实现 MySQL 命令行的自动补全和语法高亮。

安装

使用 pip 安装

pip install mycli

使用

其基本使用和 mysql 命令行基本一致

mycli [OPTIONS] [DATABASE]

Options:
  -h, --host TEXT               Host address of the database.
  -P, --port INTEGER            Port number to use for connection. Honors
                                $MYSQL_TCP_PORT
  -u, --user TEXT               User name to connect to the database.
  -S, --socket TEXT             The socket file to use for connection.
  -p, --password TEXT           Password to connect to the database
  --pass TEXT                   Password to connect to the database
  --ssl-ca PATH                 CA file in PEM format
  --ssl-capath TEXT             CA directory
  --ssl-cert PATH               X509 cert in PEM format
  --ssl-key PATH                X509 key in PEM format
  --ssl-cipher TEXT             SSL cipher to use
  --ssl-verify-server-cert      Verify server's "Common Name" in its cert
                                against hostname used when connecting. This
                                option is disabled by default
  -v, --version                 Version of mycli.
  -D, --database TEXT           Database to use.
  -R, --prompt TEXT             Prompt format (Default: "\t \u@\h:\d> ")
  -l, --logfile FILENAME        Log every query and its results to a file.
  --defaults-group-suffix TEXT  Read config group with the specified suffix.
  --defaults-file PATH          Only read default options from the given file
  --myclirc PATH                Location of myclirc file.
  --auto-vertical-output        Automatically switch to vertical output mode
                                if the result is wider than the terminal
                                width.
  -t, --table                   Display batch output in table format.
  --csv                         Display batch output in CSV format.
  --warn / --no-warn            Warn before running a destructive query.
  --local-infile BOOLEAN        Enable/disable LOAD DATA LOCAL INFILE.
  --login-path TEXT             Read this path from the login file.
  -e, --execute TEXT            Execute query to the database.
  --help                        Show this message and exit.

reference


2017-05-04 mysql , database , linux

电子书

Google+

最近文章

  • 使用 alembic 迁移数据库结构 Alembic 是一个处理数据库更改的工具,它利用 SQLAlchemy 来实现形成迁移。 因为 SQLAlchemy 只会在我们使用时根据 metadata create_all 方法来创建缺少的表 ,它不会根据我们对代码的修改而更新数据库表中的列。它也不会自动帮助我们删除表。 Alembic 提供了一种更新 / 删除表,更改列名和添加新约束的方法。因为 Alembic 使用 SQLAlchemy 执行迁移,它们可用于各种后端数据库。
  • 每天学习一个命令:iotop 查看 Linux 下每个进程 IO 占用 iotop 是一个用来监控磁盘 I/O 的类似 top 的工具,Linux 下 IO 统计工具,比如 iostat, nmon 等只能统计到每个设备的读写情况,如果想要知道哪一个进程占用比较高的 IO 就要使用 iotop。 iotop 使用 Python 语言编写,要求 Python >= 2.5,Linux Kernel >= 2.6.20.
  • 修正关于 HTTP Header 的错误认识 HTTP 请求的 Header 是不区分大小写的!,一直以为 HTTP 请求的请求头是有区分大小的,知道今天调试发现 Spring 将 header 全部处理成小写,然后有人提了 Bug 58464 然后看到 Stackoverflow 上面有人回答。
  • 解决 failed to create bus connection no such file or directory 错误 今天在修改 hostname 使用 sudo hostnamectl set-hostname ds 命令时遇到问题:
  • Vim 的颜色主题 Retro groove color scheme for Vim