raspberry pi 折腾

关于树莓派的装机,配置,系统安装,网络配置等等网上有太多的叫教程,就不在一一介绍。这里主要想要整理一下在折腾过程中遇到的几个问题。一些细节很琐碎,记录下来备忘。我安装的是 Raspberry Pi 官方的系统,也就是 Debian 的衍生系统,所以绝大多数下面的内容在其他 Debian/Ubuntu/Linux Mint 系统上都可以操作。很多内容我在 Mint 下也都已经实现过。

raspberry_pi_2

树莓派的型号

树莓派到今天已经发布了很多代了,当时买的比较早,稍微和新一代比较一下

ras

安装风扇

一图胜过千言

raspberry_pi_fans

安装完成之后

raspberry_pi_fans_install

更新系统

在安装完成之后可以使用国内的 sources.list 源,比如说 清华大学的

deb http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ jessie main non-free contrib
deb-src http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ jessie main non-free contrib

然后更新软件包,一般 update 用来同步本地 package 和 源的 package 索引, update 一定要在 upgrade 或者 dist-upgrade 之前。update 只是用来同步 package 的状态,只是相当于检查更新,而需要手动触发更新。

sudo apt-get update

更新系统, upgrade 用来更新本地安装过的所有 package 的新版本。

sudo apt-get upgrade

更加智能的更新系统,会用更加智能的方式解决包冲突

sudo apt-get dist-upgrade

最后更新 kernel 和 firmware:

sudo rpi-update

中文支持

sudo apt-get instal ttf-wqy-microhei
sudo apt-get install scim-pinyin
sudo dpkg-reconfigure locales

挂载 NTFS

不同系统有不同的文件系统,在 Windows 下绝大部分的文件系统都是 NTFS,当然我一直在用的移动硬盘也格式化成了 NTFS,那么在 Linux 下挂载 NTFS 格式的磁盘,需要借助 ntfs-3g 这个 Package。

NTFS-3G 是微软 NTFS 文件系统的一个开源实现,包括读写支持。

使用如下命令安装 NTFS-3G

sudo apt-get install ntfs-3g

然后使用 sudo fdisk -l 来查看当前系统能够识别的磁盘及分区。一般来说额外的硬盘应该会是类似 /dev/sda1 这样的标示。

使用 mount 命令来挂载 NTFS 磁盘。

sudo mount -t ntfs-3g /dev/sda1 /media/sda1

mount 命令默认会调用 /sbin/mount.ntfs ,它在安装了 ntfs-3g 之后被符号连接到 /bin/ntfs-3g。 确保本地挂载点文件夹存在,否则自己手工新建 mkdir -p /media/sda1

或者可以直接使用 ntfs-3g 命令

ntfs-3g /dev/sda1 /media/sda1

具体参考: Arch Linux Wiki

外网连接

使用 frp , 在外网 VPS 上搞一个 server 。

共享 Samba

在之前的文章 就曾说到使用 Samba 来在局域网共享文件,搭配 Android 上 ES Explorer,电视盒子基本上可以做到类似家庭共享的作用,所有的一切都看路由器能够不能带动了。这里再说一下基本配置。

使用如下命令安装

sudo apt-get install samba

修改配置文件smb.conf

[Public]
   comment = Public Storage  # 共享文件夹说明
   path = /home/pi/Public # 共享文件夹目录
   read only = no # 不只读
   create mask = 0777 # 创建文件的权限
   directory mask = 0777 # 创建文件夹的权限
   guest ok = yes # guest访问,无需密码
   browseable = yes # 可见

设置文件夹权限

sudo chmod -R 777 /home/pi/Public/

重启 Samba 服务

sudo samba restart

完成后局域网中的其他设备就可以通过,\\IP\ 来访问共享的内容

安装 Resilio Sync

很早之前,我也写过 Resilio Sync 的文章,那个时候还叫 BTSync。在笔记本上一直跑着,这些天我看树莓派负载也不高,跑一个 Resilio Sync 应该也还可以。

不过过程却有点繁复,国内似乎屏蔽了 Resilio Sync 的官网,连他的 Key 都无法下载下来,可以使用 proxychains 代理的方法(可以参考之前的文章),不过毕竟也稍微麻烦一点,不过后来发现,在官网下载一个可运行的 rslsync ,再配置一些 conf 文件就可以直接开跑。

在文件 /etc/apt/sources.list.d/resilio-sync.list 中写入:

deb [arch=armhf] http://linux-packages.resilio.com/resilio-sync/deb resilio-sync non-free

添加公钥

wget -qO - https://linux-packages.resilio.com/resilio-sync/key.asc | sudo apt-key add -

更新 index

sudo dpkg --add-architecture armhf
sudo apt-get update

安装

sudo apt-get install resilio-sync

安装完之后,如果想要修改 conf 文件,可以去相应的位置 /etc 下找,然后使用树莓派的IP:8888 来访问WEB 管理界面。

然后就是添加相应的 KEY 来同步文件了。具体可以参考我之前那篇文章。

raspi_2_b

reference


2017-07-29 RaspberryPi , Linux

flask admin

Flask Admin 是 Flask 的一个管理插件,类似于 Django Admin 一样的存在,可以很方便的帮助开发者实现管理界面,并且能够提供一套和数据库对应的界面。

Flask-Admin 提供一个现成的 SQLAlchemy 模型接口。它以类执行并接受 2 个参数:模型类和数据库会话。

初始化 Flask Admin

from flask import Flask
from flask.ext.admin import Admin

app = Flask(__name__)

admin = Admin(app)

Add administrative views here

app.run()

运行这个程序并访问 http://localhost:5000/admin/ ,可以看到基础模板。

增加一个管理视图

from flask import Flask
from flask.ext.admin import Admin, BaseView, expose

class MyView(BaseView):
	@expose('/')
	def index(self):
		return self.render('index.html')

app = Flask(__name__)

admin = Admin(app)
admin.add_view(MyView(name='Hello'))

app.run()

模型视图

模型视图允许你为数据库中的每个模型增加专用的管理页面。可以自动为数据库中每一张表生成一个管理页面。

from flask.ext.admin.contrib.sqla import ModelView

Flask and Flask-SQLAlchemy initialization here

admin = Admin(app)
admin.add_view(ModelView(User, db.session))

构造自己的 ModelView,要定制这些模型视图,有两个选择:一是覆盖 ModelView 类的公有属性,二是覆盖它的方法。

from flask.ext.admin.contrib.sqla import ModelView

# Flask and Flask-SQLAlchemy initialization here

class MyView(ModelView):
	# Disable model creation
	can_create = False

	# Override displayed fields
	column_list = ('login', 'email')

	def __init__(self, session, **kwargs):
		# You can pass name and other parameters if you want to
		super(MyView, self).__init__(User, session, **kwargs)

admin = Admin(app)
admin.add_view(MyView(db.session))

文件管理

from flask.ext.admin.contrib.fileadmin import FileAdmin

import os.path as op

# Flask setup here

admin = Admin(app)

path = op.join(op.dirname(__file__), 'static')
admin.add_view(FileAdmin(path, '/static/', name='Static Files'))

2017-07-28 flask , web , python , linux

Flask 上传文件

Flask 处理上传的文件非常简单,总结归纳可以分为三步:

  • 使用 <form> 标签被标记有 enctype=multipart/form-data ,并且在里面包含一个 <input type=file> 标签
  • 服务端通过请求对象上的 files 字典访问文件
  • 使用文件的 save() 方法将文件永久地保存在文件系统上的某处

假设将上传的文件存放在 static/uploads 目录中。

werkzeug 库可以判断文件名是否安全,例如防止文件名是 /../test.png, 安装

pip install werkzeug

具体代码:

from flask import Flask, request
from werkzeug.utils import secure_filename
import os

app = Flask(__name__)

app.config['UPLOAD_FOLDER'] = 'static/uploads/'
app.config['ALLOWED_EXTENSIONS'] = set(['png', 'jpg', 'jpeg', 'gif'])

# For a given file, return whether it's an allowed type or not
def allowed_file(filename):
	return '.' in filename and \
		   filename.rsplit('.', 1)[1] in app.config['ALLOWED_EXTENSIONS']

@app.route('/')
def hello_world():
	return 'hello world'

@app.route('/upload', methods=['POST'])
def upload():
	upload_file = request.files['file']
	if upload_file and allowed_file(upload_file.filename):
		filename = secure_filename(upload_file.filename)
		upload_file.save(os.path.join(app.root_path, app.config['UPLOAD_FOLDER'], filename))
		return 'hello, '+request.form.get('name', 'little apple')+'. success'
	else:
		return 'hello, '+request.form.get('name', 'little apple')+'. failed'

if __name__ == '__main__':
	app.run(debug=True)

app.config中的config是字典的子类,可以用来设置自有的配置信息,也可以设置自己的配置信息。函数 allowed_file(filename) 用来判断filename是否有后缀以及后缀是否在app.config['ALLOWED_EXTENSIONS']中。

过滤文件名

这里使用了 werkzeug 自带的 secure_filename 方法,该方法会过滤所有非ASCII码,对于中文文件名处理就不怎么友好了,所以我们可以定义自己的 secure_filename 方法

def secure_filename(filename):
    """
    确保文件名不包含 / -
    :param filename:
    :return:
    """
    filename = re.sub('[" "\/\--]+', '-', filename)
    filename = re.sub(r':-', ':', filename)
    filename = re.sub(r'^-|-$', '', filename)
    return filename

使用正则将文件名中的非法字符去除掉。

上传多个文件

Flask 提供了 getlist 方法

for upload in request.files.getlist("file"):
    filename = os.path.splitext(upload.filename)[0]
    filename = secure_filename(filename)
    save_name = hashlib.md5('video'+ str(time.time())).hexdigest()[:16]
    dest = '/'.join([dest_folder, save_name])
    print("Accept incoming file:", filename)
    upload.save(dest)

模拟上传文件

import requests
files = {'file': open('01.jpg', 'rb')}
user_info = {'name': 'einverne'}
r = requests.post("http://127.0.0.1:5000/upload", data=user_info, files=files)
print r.text

要控制上产文件的大小,可以设置请求实体的大小,例如:

app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 #16MB

如果要获取上传文件的内容可以:

file_content = request.files['image01'].stream.read()

使用template

在 template 目录下新建 upload.html ,确保在 HTML 表单中设置 enctype=”multipart/form-data” 属性

<form action="" method=post enctype=multipart/form-data>
	<div class="form-group">
	  <label for="exampleInputFile">上传文件</label>
	  <input type="file" name="file">
	  <p class="help-block">上传数据</p>
	  <button type="submit" class="btn btn-default">Submit</button>
	</div>
</form>

在 views.py 中写 upload_file 方法处理上传逻辑

@crawler_control.route('/upload', methods=['POST'])
def upload_file():
	if request.method == 'POST':
		f = request.files['file']
		if f and '.' in f.filename and f.filename.rsplit('.', 1)[1] in app.config['ALLOWED_EXTENSIONS']:
			filename = secure_filename(f.filename)
			path = op.join('/tmp', filename)
			f.save(path)
		return "Success"
	return render_template_string("only support POST")

2017-07-27 flask , web , python , linux

Flask restful

使用 JSON 作为交换格式

处理 JSON 时,请求和响应头的 Content-Type 设置为 application/json

from flask import Flask, request, Response
import json
app = Flask(__name__)

@app.route('/json', methods=['POST'])
def my_json():
	print request.headers
	print request.json
	rt = {'info':'hello '+request.json['name']}
	# add custom headers
	response.headers.add('Server', 'python flask')
	return Response(json.dumps(rt),  mimetype='application/json')

if __name__ == '__main__':
	app.run(debug=True)

模拟客户端请求

import requests, json

user_info = {'name': 'einverne'}
headers = {'content-type': 'application/json'}
r = requests.post("http://127.0.0.1:5000/json", data=json.dumps(user_info), headers=headers)
print r.headers
print r.json()

模拟 RESTful 接口

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
	return 'hello world'

@app.route('/user/<username>')
def user(username):
	print username
	print type(username)
	return 'hello world'

@app.route('/user/<username>/friends')
def user_friends(username):
	print username
	print type(username)
	return 'hello world'

if __name__ == '__main__':
	app.run(debug=True)

使用内置类型装换

@app.route('/page/<int:num>')
def page(num):
	print num
	print type(num)
	return 'hello world'

自动将 num 转换成 int 类型,目前 Flask 支持的转换有:

类别 解释
string 任何不带 slash(/) 的字符串
int 整数
float float
path 接受 slash(/)
any 所有
uuid uuid string

复杂的用法,比如定义一个范围

@app.route('/page/<int:num1>-<int:num2>')
def page(num1, num2):
	print num1
	print num2
	return 'hello world'

自定义转换器

自定义的转换器是一个继承 werkzeug.routing.BaseConverter 的类,修改 to_pythonto_url 方法即可。to_python 方法用于将url中的变量转换后供被 @app.route 包装的函数使用,to_url 方法用于 flask.url_for 中的参数转换。

下面是一个示例,将 HelloWorld/index.py 修改如下:

from flask import Flask, url_for
from werkzeug.routing import BaseConverter

class MyIntConverter(BaseConverter):
	def __init__(self, url_map):
		super(MyIntConverter, self).__init__(url_map)

	def to_python(self, value):
		return int(value)

	def to_url(self, value):
		return 'hi'
app = Flask(__name__)
app.url_map.converters['my_int'] = MyIntConverter


@app.route('/page/<my_int:num>')
def page(num):
	print num
	print url_for('page', num='123')
	return 'hello world'

if __name__ == '__main__':
	app.run(debug=True)

浏览器访问 http://127.0.0.1:5000/page/123 后,HelloWorld/index.py 的输出信息是:

123 
/page/hi

url for

上面例子只能提到的 url_for 方法用于构建URL,他的使用方法如下

from flask import Flask, url_for
app = Flask(__name__)

@app.route('/')
def hello_world():
	pass

@app.route('/user/<name>')
def user(name):
	pass

@app.route('/page/<int:num>')
def page(num):
	pass

@app.route('/test')
def test():
	print url_for('hello_world')
	print url_for('user', name='einverne')
	print url_for('page', num=1, q='hadoop mapreduce 10%3')
	print url_for('static', filename='uploads/01.jpg')
	return ''

if __name__ == '__main__':
	app.run(debug=True)

输出

/
/user/einverne
/page/1?q=hadoop+mapreduce+10%253
/static//uploads/01.jpg

url for 后接方法名


2017-07-27 linux , flask , json , restful , web , python

Flask 使用介绍

Flask 是一个轻量级的基于 python 的 web 框架。

安装运行

一般情况下,只要通过 pip 安装 Flask 即可:

pip install Flask

进入 python shell

>>> import flask
>>> print flask.__doc__
	flask
	~~~~~
	A microframework based on Werkzeug.  It's extensively documented
	and follows best practice patterns.
	:copyright: (c) 2015 by Armin Ronacher.
	:license: BSD, see LICENSE for more details.
>>> print flask.__version__
0.12.2

创建 index.py 文件,在文件中放入以下内容:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
	return 'Hello World!'

if __name__ == '__main__':
	app.run()

运行 python index.py

python index.py
 * Running on http://127.0.0.1:5000/

变量 app 是一个 Flask 实例,当客户端访问根目录 / 时,将响应 hello_world() 函数返回的内容。这不是返回 Hello World! 这么简单,Hello World! 只是 HTTP 响应报文的实体部分,状态码等信息既可以由 Flask 自动处理,也可以通过编程来制定。

Tips

更改静态资源地址

在创建 Flask 时使用额外的参数,具体可参考 __doc__

显示调试信息

添加 run 函数参数:

app.run(debug=True)

绑定网络接口和端口

默认情况下,Flask 使用 127.0.0.1 ,端口为 5000,通过如下方式更改:

app.run(host='0.0.0.0', port=80, debug=True)

绑定 80 端口需要 root 权限运行 index.py

获取请求参数

from flask import Flask, request
app = Flask(__name__)

@app.route('/')
def hello_world():
	params = request.args.items()
	return params.__str__()

if __name__ == '__main__':
	app.run()

另外可以通过 request.full_pathrequest.path 来获取客户端请求 url

获取 POST 请求参数

from flask import Flask, request

app = Flask(__name__)

@app.route('/')
def hello_world():
	return 'hello world'

@app.route('/register', methods=['POST'])
def register():
	print request.headers
	print request.form
	print request.form['name']
	print request.form.get('name')
	print request.form.getlist('name')
	print request.form.get('nickname', default='little apple')
	return 'welcome'

if __name__ == '__main__':
	app.run(debug=True)

使用 requests 模拟客户端请求:

import requests
user_info = {'name': 'einverne', 'password': '123'}
r = requests.post("http://127.0.0.1:5000/register", data=user_info)
print r.text

唯一 URL

构造访问 URL 时可能会遇到如下两种情况:

@app.route('/projects/')
def projects():
    return 'The project page'

@app.route('/about')
def about():
    return 'The about page'

解释:

  • 第一种类似文件系统中访问文件夹,访问一个结尾不带斜线的 URL 会被 Flask 重定向到带斜线的规范 URL 去
  • 第二种类似 Unix-like 系统中的路径名,访问结尾带斜线的 URL 会产生一个 404 “Not Found” 错误。

RESTful

Flask 下有 Flask-Restless 结合 SQLAlchemy 轻松实现 RESTful 。

reference


2017-07-27 python , web , flask , django

Flask cookie and session

因为HTTP协议是无状态的,即服务器不知道用户上一次做了什么,这严重阻碍了交互式Web应用程序的实现,所以引入了 Cookie 和 Session。

session

from flask import Flask, render_template_string, \
	session, request, redirect, url_for
app = Flask(__name__)
app.secret_key = 'F12Zr47j\3yX R~X@H!jLwf/T'

@app.route('/')
def hello_world():
	return 'hello world'

@app.route('/login')
def login():
	page = '''
	<form action="" method="post">
		<p>name: <input type="text" name="user_name" /></p>
		<input type="submit" value="Submit" />
	</form>
	'''
	return render_template_string(page)
	
@app.route('/do_login', methods=['POST'])
def do_login():
	name = request.form.get('user_name')
	session['user_name'] = name
	return 'success'

@app.route('/show')
def show():
	return session['user_name']

@app.route('/logout')
def logout():
	session.pop('user_name', None)
	return redirect(url_for('login'))

if __name__ == '__main__':
	app.run(debug=True)

设置 session 过期时间

from datetime import timedelta
from flask import session, app
session.permanent = True
app.permanent_session_lifetime = timedelta(minutes=5)
from flask import Flask, request, Response, make_response
import time
app = Flask(__name__)

@app.route('/')
def hello_world():
	return 'hello world'

@app.route('/add')
def login():
	res = Response('add cookies')
	res.set_cookie(key='name', value='letian', expires=time.time()+6*60)
	return res

@app.route('/show')
def show():
	return request.cookies.__str__()

@app.route('/del')
def del_cookie():
	res = Response('delete cookies')
	res.set_cookie('name', '', expires=0)
	# print res.headers
	# print res.data
	return res
if __name__ == '__main__':
	app.run(host='0.0.0.0', debug=True)

reference


2017-07-27 flask , web , python

北京租房所思所想

毕业面临人生第一次租房,而到现在从学校搬出来也已经有近一个月时间,期间工作有些繁忙,而有一些租房相关的笔记一直躺在WizNote中,直到今天回到家,才想到应该把我的经历记下来,提醒自己以后租房过程中避坑。

信息来源

现在的社会是一个信息为主的社会,我们经常挂在嘴边说的一句话就是信息不对称能够产生利益,现在社会很多的金钱交易都是基于信息的不对称。而租房信息获取的途径也会直接导致以后一年的住房条件和心情。下面就讲讲我参考的几个主要站点和App。

自如

首先就是自如,也是我周围同学使用最多的,大多数周围的同学都通过自如找到了房子,我也看过一些自如的房子,自如有如下特点

  • 自如的房子装修一般还不错,该有的家具设施都有,一般也比较干净
  • 自如需要多交一个月的服务费,和中介多交一个月也无太大差别,另水电自理
  • 双周的保洁还是让公共区域非常的干净,至少我看过的两个房子进门都非常干净也没有异味

缺点:

  • 但是自如的房子一般都比较”贵”,相对来讲
  • 住自如的房子就要抱着和室友相处的”危险”,当然我相信这个世界是美好的,同在一个屋檐下,在同一个城市打拼,本就应该相互尊重相互谦让一下,然而事实就是我身边确实发生了非常不愉快的事情,甚至严重到了报警,所以如果租自如的房子,请做好合租的思想准备。

107间

在同学QQ群中第一次听说,立马下载安装注册之后,需要缴纳100元作为认证费用,只有缴纳费用之后才能看到发布者联系方式。可是缴纳认证费用之后连续联系的两家都已经将房屋出租出去。不及时更新房屋信息,并且在缴纳认证费用的界面没有任何提示。如果30天内没有签约,费用不退。

总结下:

  • 房源相对较少
  • 房屋信息更新不及时,打电话联系客服竟然还是人工和房东进行确认,这都什么时候还用人工确认
  • 签约流程不明确,107间确实过滤了大部分中介,可带来的却是中间确认流程的缺失

强烈建议将该App 拉入黑名单。白白让我交了100块的认证。

链家

房源多,整租单间价格和市场几乎就是市场均价,但是环境看机会。

我爱我家

类似链家。

安居客

同样也是一个找租房的应用,不过看官网和他应用的主要内容似乎是一家经营房产,买卖房屋为主业的公司,租房只是他们其中的一个副业,当然也可以在上面找找周围合适的房屋信息,个人感觉比那个鬼 107 间要靠谱一些,和自如相同也是有“房管”来管理租赁一个范围内的房屋,和自如类似。可以参考,毕竟最后没有在安居客租房因此也不能下结论。

内网

如果你公司内网有租房信息,请一定要利用好,租房那天在外面跑了很多地方,顶着大太阳看了一个有一个房子,却只有在公司内网同事分享的一些房子靠谱一些。个人最后也在同学的推荐下找到了相对比较合适的房子。

58

其他 58,链家 ,豆瓣 之类,只是寥寥看了一下,并没有真正却联系,在看房的时候也了解到不乏有之前的房东在 58 上找到很好的房子,进而想要帮房东更快的租出房子而内网发布信息的。当然如果个人贸然出租请还是注意防骗。

关于合同的一切

这个社会的复杂性都在一直合同中变的扁平和简洁,而着又同样带来了合同的复杂性,合同越复杂带来的后续处理细节就越方便。我们找到房东直租,但房东是一位上了年龄的阿姨,合同也让我们自己带,网上,同学问了一圈,最后在住房和城市建设委员会的网站上找了一份租房合同模板 。但网站上给出的版本还是 08 年的版本,我们又找了一份 14 年的修订版 —- 北京市房屋租赁合同(自行成交版2014)

有了合同模板就好很多,按照上面的填空,填完也能够整个合同流程有一个大概的印象,应该注意的地方合同中大多都有提到,当然除了合同中提到的内容,涉及到金钱的地方要额外的注意。

  • 押一付三,每月金额,押金金额,开始日期结束日期,交房租日期。
  • 家具、空调、洗衣机等等电器的使用情况和损坏状况。
  • 关键的一点,水电费的读数,取暖费谁交。

2017-07-25 租房 , 合租 , 生活 , living

MySQL 中 KEY vs PRIMARY KEY vs UNIQUE KEY vs INDEX 的区别

对于标题中提出的问题,可以拆分来一步步解决。先来区分 MySQL 中的 KEYINDEX ,之后问题就可以简化为 PRIMARY KEY,UNIQUE KEY 和 INDEX 的区别。而这三者也正好是索引的划分,主键索引,唯一索引和普通索引(INDEX)。

INDEX 可以用来提高数据库中查询数据的速度。INDEX 通常加在那些 JOIN, WHERE,和 ORDER BY 子句的列上。创建索引时,需要确保该索引是应用在 SQL 查询语句的条件(一般作为 WHERE 子句的条件)。实际上,索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录。

索引也有它的缺点:虽然索引提高了查询速度,却会降低更新表的速度,如对表进行 INSERT、UPDATE 和 DELETE。因为更新表时,MySQL 不仅要保存数据,还要保存一下索引文件。

MySQL 中 KEY 与 INDEX 区别

KEY 通常是 INDEX 同义词,但实际上还是有区别的。如果关键字属性 PRIMARY KEY 在列定义中已给定,则 PRIMARY KEY 也可以只指定为 KEY。这么做的目的是与其它数据库系统兼容。PRIMARY KEY 是一个唯一 KEY,此时,所有的关键字列必须定义为 NOT NULL。如果这些列没有被明确地定义为 NOT NULL,MySQL 应隐含地定义这些列。

KEY 即键值,是关系模型理论中的一部份,比如有主键(PRIMARY KEY),外键(Foreign KEY)等,用于数据完整性检验和唯一性约束等。而 INDEX 则处于实现层面,可以对表的任意列建立索引,那么当建立索引的列处于 SQL 语句中的 Where 条件中时,就可以得到快速的数据定位,从而提高检索效率。至于 UNIQUE INDEX,则只是属于 INDEX 中的一种而已,建立了 UNIQUE INDEX 表示此列数据不可重复。

于是,在设计表的时候,KEY 只是要处于模型层面的,而当需要进行查询优化,则对相关列建立索引即可。

KEY

KEY 是数据库的物理结构,一般理解为,包含两层含义,一是约束,偏重于约束和规范数据库的结构完整性,二是索引,辅助查询。

  • primary key 有两个作用,一是约束作用(constraint),用来规范一个存储主键和唯一性,但同时也在此 key 上建立了一个 index;
  • unique key 也有两个作用,一是约束作用(constraint),规范数据的唯一性,但同时也在这个 key 上建立了一个 index;
  • foreign key 也有两个作用,一是约束作用(constraint),规范数据的引用完整性,但同时也在这个 key 上建立了一个 index;

可见,key 是同时具有 constraint 和 index 的意义。

INDEX

INDEX 是数据库的物理结构,一般翻译为索引,但只辅助查询,会在创建时占用另外的空间。索引分为前缀索引、全文索引等。索引只是索引,不会去约束索引字段的行为。

PRIMARY KEY 和 UNIQUE KEY 的区别

PRIMARY KEYs(主键) 和 UNIQUE KEYs(唯一键约束) 是类似的, PRIMARY KEY 通常是一列,也有可能多列,通常由他来决定一行数据 (row)。 一张表只能有一个 PRIMARY KEY,但可以有很多 UNIQUE KEY。 当给一列设置为 UNIQUE KEY 之后,不能有两行在该列上有相同的数据。 PRIMARY KEY 不允许有 NULL 值,但是 UNIQUE KEY 可以。

修改表 ALTER TABLE table_name ADD PRIMARY KEY(column_name, ...)

总结,相同点:

  • PRIMARY KEY 和 UNIQUE KEY 都是用来保证列上数据的为原型
  • 都可以在一列或者多列上加

差异点:

  • 同一张表 PRIMARY KEY 只能有一个, UNIQUE KEY 可以有多个
  • PRIMARY KEY 不能有空值, UNIQUE KEY 可以有。如果 PRIMARY KEY 的 1 个或多个列为 NULL,在增加 PRIMARY KEY 时,列自动更改为 NOT NULL 。而 UNIQUE KEY 对列没有要求是通过参考索引实施的,如果插入的值均为 NULL,则根据索引的原理,全 NULL 值不被记录在索引上,所以插入全 NULL 值时,可以有重复的,而其他的则不能插入重复值。

    alter table t add constraint uk_t_1 UNIQUE (a,b); insert into t (a ,b ) values (null,1); # 不能重复 insert into t (a ,b ) values (null,null);#可以重复

在 MySQL 中,对于一个 PRIMARY KEY 的列,MySQL 已经自动对其建立了 UNIQUE INDEX,无需重复再在上面建立索引了。

网上关于 PRIMARY KEY 和 UNIQUE INDEX 的一段解释:

Note that “PRIMARY” is called PRIMARY KEY not INDEX.
KEY is something on the logical level, describes your table and database design (i.e. enforces referential integrity …)
INDEX is something on the physical level, helps improve access time for table operations.
Behind every PK there is (usually) UNIQUE INDEX created (automatically).

操作索引

建立索引会占用磁盘空间的索引文件。

CREATE INDEX IndexName ON mytable(username(length));

如果是 CHAR,VARCHAR 类型,length 可以小于字段实际长度;如果是 BLOB 和 TEXT 类型,必须指定 length。

在创建表时创建索引:

CREATE TABLE mytable(
	ID INT NOT NULL,
	username VARCHAR(15) NOT NULL,
	INDEX [INDEXName] (username(length))
);

删除索引

DROP INDEX [INDEXName] ON mytable;

2017-07-24 mysql , linux , key , index

树莓派中安装MySQL 5.7

最近用到 MySQL 5.7 把所有设备上的 MySQL 版本都升级到了最新,在 Ubuntu/Debian 上升级MySQL 5.7 的内容可以在之前的文章看到。现在记录一下树莓派中升级 MySQL 的步骤。使用到 MySQL 5.7 主要也是因为其支持的新数据类型,之前项目用到了,迁移的时候会遇到问题。在网上寻找解决方案的时候遇到了一个和我遭遇差不多的,需要使用到 MySQL 5.7+ 才支持的 JSON data-type。

在 respberry pi 官方的源中,只有稳定版的 5.5 MySQL,如果要用到最新的版本只能够自己手动编译更新安装,幸而 Debian 提供了编译好的安装包 ,根据以下步骤就能够安装上。

下载依赖

apt-get 安装

sudo apt-get install libaio1 libaio-dev libhtml-template-perl libevent-core-2.0-5

需要安装新版本的 gcc,所以要从 stretch 源中拉取

sudo vim /etc/apt/sources.list
# change jessie to stretch

sudo apt-get update
sudo apt-get install gcc-6 g++-6

# change stretch back to jessie 
sudo apt-get update

然后安装两个依赖

wget http://ftp.debian.org/debian/pool/main/m/mecab/libmecab2_0.996-3.1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/l/lz4/liblz4-1_0.0~r131-2+b1_armhf.deb
sudo dpkg -i libmecab2_0.996-3.1_armhf.deb
sudo dpkg -i liblz4-1_0.0~r131-2+b1_armhf.deb

移除原来的版本

sudo apt-get --purge remove mysql-server # and a lot of like client, common etc

安装最新版

下载 deb 并安装

wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/libmysqlclient-dev_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/libmysqlclient20_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/libmysqld-dev_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/mysql-client-5.7_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/mysql-client-core-5.7_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/mysql-server-5.7_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/mysql-server-core-5.7_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-defaults/mysql-common_5.8+1.0.2_all.deb

sudo dpkg -i mysql-common_5.8+1.0.2_all.deb
sudo dpkg -i mysql-client-core-5.7_5.7.18-1_armhf.deb
sudo dpkg -i mysql-client-5.7_5.7.18-1_armhf.deb
sudo dpkg -i mysql-server-core-5.7_5.7.18-1_armhf.deb
sudo dpkg -i mysql-server-5.7_5.7.18-1_armhf.deb

注意这里的版本号,可能之后会更新,去 ftp 中获取最新的版本在这里替换即可。

安装完成之后 sudo reboot

在启动之后

mysql_upgrade -u root -p --force
# 然后输入密码
sudo service mysql restart

安装完毕。

reference


2017-07-23 MySQL , Linux , RespberryPi

每天学习一个命令:使用 rsync 增量同步备份文件

rsync 全名 Remote Sync,是类 unix 系统下的数据镜像备份工具。

rsync - a fast, versatile, remote (and local) file-copying tool

rsync 是一个功能非常强大的工具,其命令也有很多功能特色选项

它的特性如下:

  • 可以从远程或者本地镜像保存整个目录树和文件系统。
  • 可以保持文件原来的权限、时间、所有者、组信息、软硬链接等等。
  • 无须特殊权限即可安装。
  • 快速:他要比 scp (Secure Copy) 要快;第一次同步时 rsync 会复制全部内容,但在下一次只传输修改过的文件。rsync 在传输数据的过程中可以实行压缩及解压缩操作,可以使用更少的带宽。
  • 安全:可以使用 scp、ssh 等方式来传输文件,当然也可以通过直接的 socket 连接。
  • 支持匿名传输,以方便进行网站镜像。

rysnc 的官方网站:http://rsync.samba.org/ ,可以从上面得到最新的版本。

rsync 的使用

Rsync 的命令格式可以为以下六种:

rsync [OPTION]... SRC DEST
rsync [OPTION]... SRC [USER@]HOST:DEST
rsync [OPTION]... [USER@]HOST:SRC DEST
rsync [OPTION]... [USER@]HOST::SRC DEST
rsync [OPTION]... SRC [USER@]HOST::DEST
rsync [OPTION]... rsync://[USER@]HOST[:PORT]/SRC [DEST]

rsync 有六种不同的工作模式:

  1. 拷贝本地文件;当 SRC 和 DEST 路径信息都不包含有单个冒号”:”分隔符时就启动这种工作模式。
  2. 使用一个远程 shell 程序(如 rsh、ssh)来实现将本地机器的内容拷贝到远程机器。当 DEST 路径地址包含单个冒号”:”分隔符时启动该模式。
  3. 使用一个远程 shell 程序(如 rsh、ssh)来实现将远程机器的内容拷贝到本地机器。当 SRC 地址路径包含单个冒号”:”分隔符时启动该模式。
  4. 从远程 rsync 服务器中拷贝文件到本地机。当 SRC 路径信息包含”::”分隔符时启动该模式。
  5. 从本地机器拷贝文件到远程 rsync 服务器中。当 DEST 路径信息包含”::”分隔符时启动该模式。
  6. 列远程机的文件列表。这类似于 rsync 传输,不过只要在命令中省略掉本地机信息即可。

这 6 种方式看似复杂,其实只要记住一些常用参数,然后记住一些常用方法就能够将 rsync 利用起来。

可以 man rsync 参考 rsync 文档,了解详细的使用方法,下面解析一些参数的使用

常用的几个参数

-v  verbose 详细输出
-a 	归档模式,递归方式传输文件,并保持连接,权限,用户和组,时间信息
-z  压缩文件传输
-h  human-readable, 输出友好

rsync 参数的具体解释如下:

-v, --verbose 详细模式输出
-q, --quiet 精简输出模式
-c, --checksum 打开校验开关,强制对文件传输进行校验
-a, --archive 归档模式,表示以递归方式传输文件,并保持所有文件属性,等于 -rlptgoD
-r, --recursive 对子目录以递归模式处理
-R, --relative 使用相对路径信息
-b, --backup 创建备份,也就是对于目的已经存在有同样的文件名时,将老的文件重新命名为~filename。可以使用 --suffix 选项来指定不同的备份文件前缀。
--backup-dir 将备份文件(如~filename) 存放在在目录下。
-suffix=SUFFIX 定义备份文件前缀
-u, --update 仅仅进行更新,也就是跳过所有已经存在于 DST,并且文件时间晚于要备份的文件。(不覆盖更新的文件)
-l, --links 保留软链结
-L, --copy-links 想对待常规文件一样处理软链结
--copy-unsafe-links 仅仅拷贝指向 SRC 路径目录树以外的链结
--safe-links 忽略指向 SRC 路径目录树以外的链结
-H, --hard-links 保留硬链结
-p, --perms 保持文件权限
-o, --owner 保持文件属主信息
-g, --group 保持文件属组信息
-D, --devices 保持设备文件信息
-t, --times 保持文件时间信息
-S, --sparse 对稀疏文件进行特殊处理以节省 DST 的空间
-n, --dry-run 现实哪些文件将被传输
-W, --whole-file 拷贝文件,不进行增量检测
-x, --one-file-system 不要跨越文件系统边界
-B, --block-size=SIZE 检验算法使用的块尺寸,默认是 700 字节
-e, --rsh=COMMAND 指定使用 rsh、ssh 方式进行数据同步
--rsync-path=PATH 指定远程服务器上的 rsync 命令所在路径信息
-C, --cvs-exclude 使用和 CVS 一样的方法自动忽略文件,用来排除那些不希望传输的文件
--existing 仅仅更新那些已经存在于 DST 的文件,而不备份那些新创建的文件
--delete 删除那些 DST 中 SRC 没有的文件
--delete-excluded 同样删除接收端那些被该选项指定排除的文件
--delete-after 传输结束以后再删除
--ignore-errors 及时出现 IO 错误也进行删除
--max-delete=NUM 最多删除 NUM 个文件
--partial 保留那些因故没有完全传输的文件,以是加快随后的再次传输
--force 强制删除目录,即使不为空
--numeric-ids 不将数字的用户和组 ID 匹配为用户名和组名
--timeout=TIME IP 超时时间,单位为秒
-I, --ignore-times 不跳过那些有同样的时间和长度的文件
--size-only 当决定是否要备份文件时,仅仅察看文件大小而不考虑文件时间
--modify-window=NUM 决定文件是否时间相同时使用的时间戳窗口,默认为 0
-T --temp-dir=DIR 在 DIR 中创建临时文件
--compare-dest=DIR 同样比较 DIR 中的文件来决定是否需要备份
-P 等同于 --partial
--progress 显示备份过程
-z, --compress 对备份的文件在传输时进行压缩处理
--exclude=PATTERN 指定排除不需要传输的文件模式
--include=PATTERN 指定不排除而需要传输的文件模式
--exclude-from=FILE 排除 FILE 中指定模式的文件
--include-from=FILE 不排除 FILE 指定模式匹配的文件
--version 打印版本信息

Example

下面举例说明 rsync 的六种不同工作模式:

拷贝本地文件

当 SRC 和 DES 路径信息都不包含有单个冒号 “:” 分隔符时就启动这种工作模式。

同步文件

rsync -ahvz backup.tar.gz  /backups/  # DESC 不存在时自动创建

将备份文件同步到 /backups/ 目录下。

同步目录

rsync -avzh /home/src /backups/files/

/home/src 目录下的文件同步发送到 /backups/files 目录下。记住如果目标地址没有 src 目录,rsync 会自动创建改文件夹。

rsync -avz /home/src/ /backups/files/

SRC 路径末尾的 / 表示不自动创建 DEST 文件夹,在 man rsync 中的解释就是末尾的 / 表示”拷贝当前目录下的文件” ,而不是”拷贝当前的目录“,

远程 shell 拷贝到远程

使用一个远程 shell 程序(如 rsh、ssh) 来实现将本地机器的内容拷贝到远程机器。当 DES 路径地址包含单个冒号”:”分隔符时启动该模式。

rsync -avz /local/path/  user@remoteip:/path/to/files/

将本地 /local/path/ 中的文件同步备份到远程 /path/to/files/ 目录。

远程 shell 拷贝到本地

使用一个远程 shell 程序(如 rsh、ssh) 来实现将远程机器的内容拷贝到本地机器。当 SRC 地址路径包含单个冒号”:”分隔符时启动该模式。

rsync -avz user@remoteip:/home/user/src  ./src

远程 rsync 服务器拷贝到本地

从远程 rsync 服务器中拷贝文件到本地机。当 SRC 路径信息包含”::”分隔符时启动该模式。

rsync -av user@remoteip::www  /databack

拷贝本地文件到远程

从本地机器拷贝文件到远程 rsync 服务器中。当 DES 路径信息包含”::”分隔符时启动该模式。

rsync -av /databack user@remoteip::www

文件列表

列远程机的文件列表。这类似于 rsync 传输,不过只要在命令中省略掉本地机信息即可。

rsync -v rsync://remoteip /www

rsync 使用更改端口

经常遇见的一种情况就是 ssh 更改了默认 22 端口,这个时候使用 -e 参数即可。

rsync 有两种常用的认证方式,一种为 rsync-daemon 方式,另外一种则是 ssh。

ssh 一般为首选,但当远端服务器的 ssh 默认端口被修改后,rsync 时找不到一个合适的方法来输入对方 ssh 服务端口号。

比如现在向机器 remoteip 传送文件,但此时 remoteip 的 ssh 端口已经不是默认的 22 端口。

键入命令

rsync /local/path user@remoteip:/path/to/files/ # 出现错误

rsync 中的命令 参数 -e, --rsh=COMMAND 指定使用 rsh、ssh 方式进行数据同步。

参数的作用是可以使用户自由选择想要使用的 shell 程序来连接远端服务器,当然也可以设置成使用默认的 ssh 来连接,但是这样我们就可以加入 ssh 的参数了。

现在命令可以这样写了:

rsync -avz -e "ssh -p $port" /local/path/ user@remoteip:/path/to/files/

显示备份进度

可以使用 --progress 选项来显示进度

rsync -avzhe ssh --progress /home/files/ root@remoteip:/path/to/files/

限制备份文件最大值

设置 Max size 备份文件

rsync -avzhe ssh --max-size='2000k' /var/lib/rpm/ root@remoteip:/root/tmprpm

备份结束后自动删除本地文件

rsync --remove-source-files -zvh backup.tar /tmp/backups/

设置备份带宽

rsync --bwlimit=100 -avzhe ssh /var/lib/rpm/ root@remoteip:/root/tmprpm/

参考


2017-07-22 linux , rsync , scp , sync

电子书

最近文章

  • MySQL 中的日志配置和管理 MySQL 中默认是没有开启日志记录的,所以需要手动修改配置文件开启日志。而在 MySQL 中我们需要关心的有三类日志:
  • Java 查漏补缺之:ThreadLocal 使用 ThreadLocal 线程本地变量,每个线程保存变量的副本,对副本的改动,对其他的线程而言是透明的。
  • 为知笔记导出和备份 WizNote 已经用了好几年,虽然也一直在续费,但总感觉将死不死,基于整理这几年近 4000 条的笔记的目的,也一方面为迁移出 WizNote 的目的,研究一下 WizNote 笔记导出和备份的方法。
  • Nginx location 匹配规则 之前的关于 Nginx Config 的文章是当时看 Nginx 书记录下来的笔记,很大略,没有实际操作,等终究用到 location 的时候发现还是有很多需要注意的问题,比如匹配的优先顺序,比如 root 和 alias 的区别等等,所以单独拿一篇文章来记录一下目前遇到的问题,并来解决一下。
  • koajs 简单使用 Koa 是一个背靠 Express 的团队设计的全新的 Web 框架,旨在使之成为一个更轻量,更丰富,更加 robust 的基础框架。通过促进异步方法的使用,Koa 允许使用者抛弃 callback 调用,并且大大简化了错误处理。Koa 并没有将中间件绑定到核心代码,而是提供了一组优雅的方法让编写服务更加快速,通过很多第三方的扩展给 Koa 提供服务,从而实现更加丰富完整的 HTTP server。