MySQL和SqlAlchemy配置指北

近期做了一个项目+库存的管理应用,开发时偷懒用的SQLite,上线后再调整时有时候要直接进数据库改数据,开始想念navicat的好处,动了上MySQL的念头,折腾一番后把一些安装要点写在这里。

安装

安装最新版本的MySQL在Ubuntu上是十分容易的。只要执行

$ sudo apt-get install mysql-server mysql-client

安装过程中会要求设置用户的root密码。

其中, mysql-server相当于mysql数据库的核心系统,mysql-client则提供交互管理的组件,比如`mysql`命令进入的命令行管理程序,就是mysql-client提供的。

配置

值得一提的是我安装好的mysql版本(5.7.20),在/etc/mysql/my.cnf文件的内容为:

!includedir /etc/mysql/conf.d/
!includedir /etc/mysql/mysql.conf.d/

也就是说,按这个版本的实践,最好去这两个目录里修改对应的配置文件。

为了能够远程直接访问数据库,需要更改数据库的bind-address。

本版本中,是前往` /etc/mysql/mysql.conf.d/`目录,修改mysqld.cnf文件,如下:

[mysqld]
# log文件配置
bind-address = 0.0.0.0

重启数据库的方式:

$ sudo /etc/init.d/mysql restart

账户和权限

安装完的Mysql提供了一个只能从本地访问的root账户。通过root账户登录数据库,可以查看`mysql database`中的数据。查看mysql中用户列表的方法:

mysql > use mysql; # 进入mysql数据库
mysql > select host, user from mysql.user; # 查看用户列表

直接能远程登录root账户显然是不安全的,目前我采用的办法是用root账户创建项目的数据库和开发数据库,然后创建使用账户,授权给这个账户对于这两个数据库的所有权限。

命令如下:

mysql> create database project_name;
mysql> create database project_name_dev;

mysql> create user ‘username’@’%’ identified by ‘password’;

mysql> grant all on project_name.* to ‘username’@’%’;
mysql> grant all on project_name_dev.* to ‘username’@’%’;

文字编码问题

在实际使用MySQL时,我遇到第一个头痛的问题是存入的中文变成了乱码,或者报错。

首先是乱码问题,默认安装完的MySQL(我的版本是5.7.20)采用的是latin1的数据库字符集,如果要更改以后创建的数据库的字符集可以去/etc目录修改配置文件。

MySQL的字符集和校对规则有4个级别的默认设置:服务器级、数据库级、表级和字段级。

服务器字符集和校对规则可以在MySQL服务启动的时候确定。

服务器级的默认字符集和校对规则可以通过my.cnf中设置: 

[mysqld]
character-set-server = utf8mb4
collation_server = utf8mb4_unicode_ci

连接字符集和校对规则

在配置文件/etc/mysql/conf.d/mysql.cnf修改默认编码:

[mysql]
default-character-set = utf8mb4
[client]
default-character-set = utf8mb4
[mysqld]
# init_connect: A string to be executed by the server for each client that connects. The string consists of one or more SQL statements, separated by semicolon characters.
init_connect = 'SET NAMES utf8mb4'

关于这个utf8mb4多出来的mb4,mb4就是most bytes 4的意思。MySQL在5.5.3之后增加了这个utf8mb4的编码,专门用来兼容四字节的unicode。好在utf8mb4是utf8的超集,除了将编码改为utf8mb4外不需要做其他转换。我一开始采用utf8编码时,导入数据就会遇到“超过3个字节”的错误,采用utf8mb4能解决这个问题。有人说utf8mb4的会占用更多内存是不对的,因为utf-8本来就是变长编码,是mysql一开始对utf-8的支持就不正确,所有字符都需要四个bytes来存储就是UTF-32了。

 所有涉及的配置变量的官方文档见链接。按本文设置完在mysql命令行中结果如下所示:

$ mysql> show variables like '%char%';

+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8mb4                    |
| character_set_connection | utf8mb4                    |
| character_set_database   | utf8mb4                    |
| character_set_filesystem | binary                     |
| character_set_results    | utf8mb4                    |
| character_set_server     | utf8mb4                    |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)

如何在SqlAlchemy中指定显式表的引擎和字符集

class User(Base):
    __table_args__ = {
        'mysql_engine': 'InnoDB',
        'mysql_charset': 'utf8mb4'
}

也可以在基类上直接修改:

class Base (Base):
    __abstract__ = True
    __table_args__ = { # 可以省掉子类的 __table_args__ 了
        'mysql_engine': 'InnoDB',
        'mysql_charset': 'utf8mb4'
}

使用基类来创建所有表都需要的字段的方法:

 

链接:

https://stackoverflow.com/questions/22976445/how-do-i-declare-a-base-model-class-in-flask-sqlalchemy

ORM中计数器的”atomic increment”

在这篇Django博主介绍自己如何给网站做uv和pv统计的博文中,提到了可能存在的计数器竞争的问题。https://www.the5fire.com/pv-statistics-in-django-blog.html

具体而言,假设我们有个`post`表,对应有一个叫Post的class:

class Post:
    __tablename__ = ‘post’
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.Text)
    uv = db.Column(db.Integer)
    pv = db.Column(db.Integer)

然后我们在视图函数中每次访问执行以下语句是会有问题的:

# 错误示范1
p.uv = p.uv + 1
# 错误示范2
p.uv += 1

我们暂时不考虑缓存设施,先让视图函数直接访问操作数据库。

假设一篇id为2的文章在下午3点的uv是5。然后恰好有两个人在3点00分01秒同时点击这篇文章,可能就会出现竞争问题。线程一接待了客户1,从数据库读到了uv = 5。然后线程被调度挂起了,线程2开始接待客户2,再次从数据库中读到了uv = 5。然后两个线程在python运行环境中计算出5 + 1 => 6,先后执行SQL语句

 UPDATE `post` SET `pv` = 6 WHERE `id` = 2

语句被执行了两遍,uv没有被加到7,GG。

正确的做法应该是视图函数要去传入SQL语句

 UPDATE `post` SET (`pv` = `pv` + 1) WHERE `id` = 2

那么SqlAlchemy中如何做呢?很简单:

p.uv = Post.uv + 1

如此自然,SqlAlchemy棒棒的。

原文地址:https://www.cnblogs.com/aweffr/p/7890700.html