linux環境下python怎樣操作mysql資料庫呢?方法如下:
首先在Linux環境下安裝mysql-python
1、下載mysql-python
打開終端:
cd /usr/local
sudo wgethttp://nchc.dl.sourceforge.net/sourceforge/mysql-python/MySQL-python-1.2.2.tar.gz
官網地址:http://sourceforge.net/projects/mysql-python/
2、解壓
sudo tar -zxvf MySQL-python-1.2.2.tar.gz
cd MySQL-python-1.2.2
3、在安裝前需進行配置
a、修改setup_posix.py中的mysql_config.path為你mysql安裝目錄的mysql_config路徑
b、修改site.cfg中的threadsafe = False,去掉mysql_config前的注釋,並改為mysql_config = /usr/local/mysql/bin/mysql_config
c、執行命令:
export LD_LIBRARY_PATH=/usr/local/mysql/lib/mysql
sudo ln -s /usr/local/mysql/lib/mysql/libmysqlclient.so /usr/lib/libmysqlclient.so.14
sudo ldconfig (這個很重要,否則後面會報錯ImportError: libmysqlclient.so.14: cannot open shared object file)
4、編譯安裝
1)python setup.py build
若未成功的話,需手動安裝setuptools:
sudo apt-get install python-setuptools
2)sudo python setup.py install
5、測試
python
>>> import MySQLdb
沒有錯誤,則表示安裝成功了。
使用python操作MySQL
使用python連接MySQL,創建資料庫,創建表格,插入/查詢數據。python_mysql.py代碼如下:
若出現類似於此的警告:
/usr/local/lib/python2.6/dist-packages/MySQL_python-1.2.2-py2.6-linux-i686.egg/MySQLdb/__init__.py:34: DeprecationWarning: the sets mole is deprecated from sets import ImmutableSet
解決辦法如下:
找到上面路徑MySQLdb下的__init__.py文件
1) 在文件中 "__init__.py"中, 注釋掉:
from sets import ImmutableSet
class DBAPISet(ImmutableSet):
新增:
class DBAPISet(frozenset):
2) 在文件"converters.py"中,注釋掉 from sets import BaseSet, Set 這一句話。
3) 在文件"converters.py"中, 修改 其中的"Set" 成為 "set" ( 只有兩個地方需要修改,即大寫改小寫)
大概 line 45: return Set([ i for i in s.split(',') if i ]) 改為 return set([ i for i in s.split(',') if i ])
大概 line 129: Set: Set2Str, 改為 set: Set2Str,
1.引入MySQLdb庫
import MySQLdb
2.和資料庫建立連接
conn=MySQLdb.connect(host="localhost",user="root",passwd="sa",db="mytable",charset="utf8")
提供的connect方法用來和資料庫建立連接,接收數個參數,返回連接對象.
比較常用的參數包括
host:資料庫主機名.默認是用本地主機.
user:資料庫登陸名.默認是當前用戶.
passwd:資料庫登陸的秘密.默認為空.
db:要使用的資料庫名.沒有默認值.
port:MySQL服務使用的TCP埠.默認是3306.
charset:資料庫編碼.
然後,這個連接對象也提供了對事務操作的支持,標準的方法
commit() 提交
rollback() 回滾
3.執行sql語句和接收返回值
cursor=conn.cursor()
n=cursor.execute(sql,param)
首先,我們用使用連接對象獲得一個cursor對象,接下來,我們會使用cursor提供的方法來進行工作.這些方法包括兩大類:1.執行命令,2.接收返回值
cursor用來執行命令的方法:
callproc(self, procname, args):用來執行存儲過程,接收的參數為存儲過程名和參數列表,返回值為受影響的行數
execute(self, query, args):執行單條sql語句,接收的參數為sql語句本身和使用的參數列表,返回值為受影響的行數
executemany(self, query, args):執行單條sql語句,但是重復執行參數列表裡的參數,返回值為受影響的行數
nextset(self):移動到下一個結果集
cursor用來接收返回值的方法:
fetchall(self):接收全部的返回結果行.
fetchmany(self, size=None):接收size條返回結果行.如果size的值大於返回的結果行的數量,則會返回cursor.arraysize條數據.
fetchone(self):返回一條結果行.
scroll(self, value, mode='relative'):移動指針到某一行.如果mode='relative',則表示從當前所在行移動value條,如果mode='absolute',則表示從結果集的第一行移動value條.
下面的代碼是一個完整的例子.
#使用sql語句,這里要接收的參數都用%s佔位符.要注意的是,無論你要插入的數據是什麼類型,佔位符永遠都要用%s
sql="insert into cdinfo values(%s,%s,%s,%s,%s)"
#param應該為tuple或者list
param=(title,singer,imgurl,url,alpha)
#執行,如果成功,n的值為1
n=cursor.execute(sql,param)
#再來執行一個查詢的操作
cursor.execute("select * from cdinfo")
#我們使用了fetchall這個方法.這樣,cds里保存的將會是查詢返回的全部結果.每條結果都是一個tuple類型的數據,這些tuple組成了一個tuple
cds=cursor.fetchall()
#因為是tuple,所以可以這樣使用結果集
print cds[0][3]
#或者直接顯示出來,看看結果集的真實樣子
print cds
#如果需要批量的插入數據,就這樣做
sql="insert into cdinfo values(0,%s,%s,%s,%s,%s)"
#每個值的集合為一個tuple,整個參數集組成一個tuple,或者list
param=((title,singer,imgurl,url,alpha),(title2,singer2,imgurl2,url2,alpha2))
#使用executemany方法來批量的插入數據.這真是一個很酷的方法!
n=cursor.executemany(sql,param)
4.關閉資料庫連接
需要分別的關閉指針對象和連接對象.他們有名字相同的方法
cursor.close()
conn.close()
以上方法就可以了。
另外,附MySQLdb的相關資料
更多關於MySQLdb的信息可以查這里:http://mysql-python.sourceforge.net/MySQLdb.html
Ⅱ python3.6下如何用Django1.9創建資料庫報錯
錯誤描述
python==3.5
django==1.7
django創建項目時報錯如下:
Traceback (most recent call last):
File "/root/envs/django-test/bin/django-admin", line 11, in <mole>
sys.exit(execute_from_command_line())
File "/root/envs/django-test/lib/python3.5/site-packages/django/core/management/__init__.py", line 385, in execute_from_command_line
utility.execute()
File "/root/envs/django-test/lib/python3.5/site-packages/django/core/management/__init__.py", line 354, in execute
django.setup()
File "/root/envs/django-test/lib/python3.5/site-packages/旅弊django/__init__.py", line 18, in setup
from django.utils.log import configure_logging
File "/root/envs/django-test/lib/python3.5/site-packages/django/utils/log.py", line 10, in <mole>
from django.views.debug import ExceptionReporter, get_exception_reporter_filter
File "/root/envs/django-test/lib/python3.5/site-packages/django/views/debug.py", line 10, in <mole>
from django.http import (HttpResponse, HttpResponseServerError,
File "/root/envs/django-test/lib/python3.5/site-packages/django/http/__init__.py", line 4, in <mole>
from django.http.response import (HttpResponse, StreamingHttpResponse,
File "/root/envs/django-test/lib/python3.5/site-packages/django/http/response.py", line 13, in <mole>
from django.core.serializers.json import DjangoJSONEncoder
File "/root/envs/django-test/lib/python3.5/site-packages/django/core/serializers/__init__.py", line 23, in <mole>
from django.core.serializers.base import SerializerDoesNotExist
File "/root/envs/django-test/lib/python3.5/site-packages/django/core/serializers/base.py", line 6, in <mole>
from django.db import models
File "/root/envs/django-test/lib/python3.5/site-packages/django/db/models/__init__.py", line 6, in <mole>
from django.db.models.query import Q, QuerySet, Prefetch # NOQA
File "/root/envs/django-test/lib/python3.5/site-packages/django/db/models/query.py", line 13, in <姿冊mole>
from django.db.models.fields import AutoField, Empty
File "跡鎮宏/root/envs/django-test/lib/python3.5/site-packages/django/db/models/fields/__init__.py", line 18, in <mole>
from django import forms
File "/root/envs/django-test/lib/python3.5/site-packages/django/forms/__init__.py", line 6, in <mole>
from django.forms.fields import * # NOQA
File "/root/envs/django-test/lib/python3.5/site-packages/django/forms/fields.py", line 18, in <mole>
from django.forms.utils import from_current_timezone, to_current_timezone
File "/root/envs/django-test/lib/python3.5/site-packages/django/forms/utils.py", line 15, in <mole>
from django.utils.html import format_html, format_html_join, escape
File "/root/envs/django-test/lib/python3.5/site-packages/django/utils/html.py", line 16, in <mole>
from .html_parser import HTMLParser, HTMLParseError
File "/root/envs/django-test/lib/python3.5/site-packages/django/utils/html_parser.py", line 12, in <mole>
HTMLParseError = _html_parser.HTMLParseError
AttributeError: mole 'html.parser' has no attribute 'HTMLParseError'
(django-test) root@localhost:~/source/djangotest# django-admin startproject superlists
Traceback (most recent call last):
File "/root/envs/django-test/bin/django-admin", line 11, in <mole>
sys.exit(execute_from_command_line())
File "/root/envs/django-test/lib/python3.5/site-packages/django/core/management/__init__.py", line 385, in execute_from_command_line
utility.execute()
File "/root/envs/django-test/lib/python3.5/site-packages/django/core/management/__init__.py", line 354, in execute
django.setup()
File "/root/envs/django-test/lib/python3.5/site-packages/django/__init__.py", line 18, in setup
from django.utils.log import configure_logging
File "/root/envs/django-test/lib/python3.5/site-packages/django/utils/log.py", line 10, in <mole>
from django.views.debug import ExceptionReporter, get_exception_reporter_filter
File "/root/envs/django-test/lib/python3.5/site-packages/django/views/debug.py", line 10, in <mole>
from django.http import (HttpResponse, HttpResponseServerError,
File "/root/envs/django-test/lib/python3.5/site-packages/django/http/__init__.py", line 4, in <mole>
from django.http.response import (HttpResponse, StreamingHttpResponse,
File "/root/envs/django-test/lib/python3.5/site-packages/django/http/response.py", line 13, in <mole>
from django.core.serializers.json import DjangoJSONEncoder
File "/root/envs/django-test/lib/python3.5/site-packages/django/core/serializers/__init__.py", line 23, in <mole>
from django.core.serializers.base import SerializerDoesNotExist
File "/root/envs/django-test/lib/python3.5/site-packages/django/core/serializers/base.py", line 6, in <mole>
from django.db import models
File "/root/envs/django-test/lib/python3.5/site-packages/django/db/models/__init__.py", line 6, in <mole>
from django.db.models.query import Q, QuerySet, Prefetch # NOQA
File "/root/envs/django-test/lib/python3.5/site-packages/django/db/models/query.py", line 13, in <mole>
from django.db.models.fields import AutoField, Empty
File "/root/envs/django-test/lib/python3.5/site-packages/django/db/models/fields/__init__.py", line 18, in <mole>
from django import forms
File "/root/envs/django-test/lib/python3.5/site-packages/django/forms/__init__.py", line 6, in <mole>
from django.forms.fields import * # NOQA
File "/root/envs/django-test/lib/python3.5/site-packages/django/forms/fields.py", line 18, in <mole>
from django.forms.utils import from_current_timezone, to_current_timezone
File "/root/envs/django-test/lib/python3.5/site-packages/django/forms/utils.py", line 15, in <mole>
from django.utils.html import format_html, format_html_join, escape
File "/root/envs/django-test/lib/python3.5/site-packages/django/utils/html.py", line 16, in <mole>
from .html_parser import HTMLParser, HTMLParseError
File "/root/envs/django-test/lib/python3.5/site-packages/django/utils/html_parser.py", line 12, in <mole>
HTMLParseError = _html_parser.HTMLParseError
AttributeError: mole 'html.parser' has no attribute 'HTMLParseError'5767757677
原因
HTMLParseError在pythons3.5已經沒有了
解決方法
將python版本回退到3.3或3.4
升級django版本
Ⅲ 如何用python寫sql
python可以利用pymysql模塊操作資料庫。
什麼是 PyMySQL?
PyMySQL 是在 Python3.x 版本中用於連接 MySQL 伺服器的一個庫,Python2中則使用mysqldb。
PyMySQL 遵循 Python 資料庫 API v2.0 規范,並包含了 pure-Python MySQL 客戶端庫。
PyMySQL 安裝
在使用 PyMySQL 之前,我們需要確保 PyMySQL 已安裝。
PyMySQL 下載地址:https://github.com/PyMySQL/PyMySQL。
如果還未安裝,我們可以使用以下命令安裝最新版的 PyMySQL:
$ pip3 install PyMySQL
如果你的系統不支持 pip 命令,可以使用以下方式安裝:
1、使用 git 命令下載安裝包安裝(你也可以手動下載):
$ git clone https://github.com/PyMySQL/PyMySQL$ cd PyMySQL/$ python3 setup.py install
2、如果需要制定版本號,可以使用 curl 命令來安裝:
$ # X.X 為 PyMySQL 的版本號$ curl -L https://github.com/PyMySQL/PyMySQL/tarball/pymysql-X.X | tar xz$ cd PyMySQL*$ python3 setup.py install
$ # 現在你可以刪除 PyMySQL* 目錄
注意:請確保您有root許可權來安裝上述模塊。
安裝的過程中可能會出現"ImportError: No mole named setuptools"的錯誤提示,意思是你沒有安裝setuptools,你可以訪問https://pypi.python.org/pypi/setuptools找到各個系統的安裝方法。
Linux 系統安裝實例:
$ wget https://bootstrap.pypa.io/ez_setup.py$ python3 ez_setup.py
資料庫連接
連接資料庫前,請先確認以下事項:
您已經創建了資料庫 TESTDB.
在TESTDB資料庫中您已經創建了表 EMPLOYEE
EMPLOYEE表欄位為 FIRST_NAME, LAST_NAME, AGE, SEX 和 INCOME。
連接資料庫TESTDB使用的用戶名為 "testuser" ,密碼為 "test123",你可以可以自己設定或者直接使用root用戶名及其密碼,Mysql資料庫用戶授權請使用Grant命令。
在你的機子上已經安裝了 Python MySQLdb 模塊。
如果您對sql語句不熟悉,可以訪問我們的SQL基礎教程
- Database version : 5.5.20-log
- ..................................user_id = "test123"password = "password"con.execute('insert into Login values( %s, %s)' % (user_id, password))..................................
fetchone():該方法獲取下一個查詢結果集。結果集是一個對象
fetchall():接收全部的返回結果行.
rowcount:這是一個只讀屬性,並返回執行execute()方法後影響的行數。
- fname=Mac, lname=Mohan, age=20, sex=M, income=2000
原子性(atomicity)。一個事務是一個不可分割的工作單位,事務中包括的諸操作要麼都做,要麼都不做。
一致性(consistency)。事務必須是使資料庫從一個一致性狀態變到另一個一致性狀態。一致性與原子性是密切相關的。
隔離性(isolation)。一個事務的執行不能被其他事務干擾。即一個事務內部的操作及使用的數據對並發的其他事務是隔離的,並發執行的各個事務之間不能互相干擾。
持久性(rability)。持續性也稱永久性(permanence),指一個事務一旦提交,它對資料庫中數據的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響。
實例:
以下實例鏈接 Mysql 的 TESTDB 資料庫:
實例(Python 3.0+)
#!/usr/bin/python3
import pymysql
# 打開資料庫連接db = pymysql.connect("localhost","testuser","test123","TESTDB" )
# 使用 cursor() 方法創建一個游標對象 cursorcursor = db.cursor()
# 使用 execute() 方法執行 SQL 查詢 cursor.execute("SELECT VERSION()")
# 使用 fetchone() 方法獲取單條數據.data = cursor.fetchone()
print ("Database version : %s " % data)
# 關閉資料庫連接db.close()
執行以上腳本輸出結果如下:
創建資料庫表
如果資料庫連接存在我們可以使用execute()方法來為資料庫創建表,如下所示創建表EMPLOYEE:
實例(Python 3.0+)
#!/usr/bin/python3
import pymysql
# 打開資料庫連接db = pymysql.connect("localhost","testuser","test123","TESTDB" )
# 使用 cursor() 方法創建一個游標對象 cursorcursor = db.cursor()
# 使用 execute() 方法執行 SQL,如果表存在則刪除cursor.execute("DROP TABLE IF EXISTS EMPLOYEE")
# 使用預處理語句創建表sql = """CREATE TABLE EMPLOYEE (
FIRST_NAME CHAR(20) NOT NULL,
LAST_NAME CHAR(20),
AGE INT,
SEX CHAR(1),
INCOME FLOAT )"""
cursor.execute(sql)
# 關閉資料庫連接db.close()
資料庫插入操作
以下實例使用執行 SQL INSERT 語句向表 EMPLOYEE 插入記錄:
實例(Python 3.0+)
#!/usr/bin/python3
import pymysql
# 打開資料庫連接db = pymysql.connect("localhost","testuser","test123","TESTDB" )
# 使用cursor()方法獲取操作游標 cursor = db.cursor()
# SQL 插入語句sql = """INSERT INTO EMPLOYEE(FIRST_NAME,
LAST_NAME, AGE, SEX, INCOME)
VALUES ('Mac', 'Mohan', 20, 'M', 2000)"""try: # 執行sql語句
cursor.execute(sql)
# 提交到資料庫執行
db.commit()except: # 如果發生錯誤則回滾
db.rollback()
# 關閉資料庫連接db.close()
以上例子也可以寫成如下形式:
實例(Python 3.0+)
#!/usr/bin/python3
import pymysql
# 打開資料庫連接db = pymysql.connect("localhost","testuser","test123","TESTDB" )
# 使用cursor()方法獲取操作游標 cursor = db.cursor()
# SQL 插入語句sql = "INSERT INTO EMPLOYEE(FIRST_NAME,
LAST_NAME, AGE, SEX, INCOME)
VALUES ('%s', '%s', %s, '%s', %s)" % ('Mac', 'Mohan', 20, 'M', 2000)try: # 執行sql語句
cursor.execute(sql)
# 執行sql語句
db.commit()except: # 發生錯誤時回滾
db.rollback()
# 關閉資料庫連接db.close()
以下代碼使用變數向SQL語句中傳遞參數:
資料庫查詢操作
Python查詢Mysql使用 fetchone() 方法獲取單條數據, 使用fetchall() 方法獲取多條數據。
實例:
查詢EMPLOYEE表中salary(工資)欄位大於1000的所有數據:
實例(Python 3.0+)
#!/usr/bin/python3
import pymysql
# 打開資料庫連接db = pymysql.connect("localhost","testuser","test123","TESTDB" )
# 使用cursor()方法獲取操作游標 cursor = db.cursor()
# SQL 查詢語句sql = "SELECT * FROM EMPLOYEE
WHERE INCOME > %s" % (1000)try: # 執行SQL語句
cursor.execute(sql)
# 獲取所有記錄列表
results = cursor.fetchall()
for row in results: fname = row[0]
lname = row[1]
age = row[2]
sex = row[3]
income = row[4]
# 列印結果
print ("fname=%s,lname=%s,age=%s,sex=%s,income=%s" % (fname, lname, age, sex, income ))except: print ("Error: unable to fetch data")
# 關閉資料庫連接db.close()
以上腳本執行結果如下:
資料庫更新操作
更新操作用於更新數據表的的數據,以下實例將 TESTDB 表中 SEX 為 'M' 的 AGE 欄位遞增 1:
實例(Python 3.0+)
#!/usr/bin/python3
import pymysql
# 打開資料庫連接db = pymysql.connect("localhost","testuser","test123","TESTDB" )
# 使用cursor()方法獲取操作游標 cursor = db.cursor()
# SQL 更新語句sql = "UPDATE EMPLOYEE SET AGE = AGE + 1 WHERE SEX = '%c'" % ('M')try: # 執行SQL語句
cursor.execute(sql)
# 提交到資料庫執行
db.commit()except: # 發生錯誤時回滾
db.rollback()
# 關閉資料庫連接db.close()
刪除操作
刪除操作用於刪除數據表中的數據,以下實例演示了刪除數據表 EMPLOYEE 中 AGE 大於 20 的所有數據:
實例(Python 3.0+)
#!/usr/bin/python3
import pymysql
# 打開資料庫連接db = pymysql.connect("localhost","testuser","test123","TESTDB" )
# 使用cursor()方法獲取操作游標 cursor = db.cursor()
# SQL 刪除語句sql = "DELETE FROM EMPLOYEE WHERE AGE > %s" % (20)try: # 執行SQL語句
cursor.execute(sql)
# 提交修改
db.commit()except: # 發生錯誤時回滾
db.rollback()
# 關閉連接db.close()
執行事務
事務機制可以確保數據一致性。
事務應該具有4個屬性:原子性、一致性、隔離性、持久性。這四個屬性通常稱為ACID特性。
Python DB API 2.0 的事務提供了兩個方法 commit 或 rollback。
實例
實例(Python 3.0+)
# SQL刪除記錄語句sql = "DELETE FROM EMPLOYEE WHERE AGE > %s" % (20)try: # 執行SQL語句
cursor.execute(sql)
# 向資料庫提交
db.commit()except: # 發生錯誤時回滾
db.rollback()
對於支持事務的資料庫, 在Python資料庫編程中,當游標建立之時,就自動開始了一個隱形的資料庫事務。
commit()方法游標的所有更新操作,rollback()方法回滾當前游標的所有操作。每一個方法都開始了一個新的事務。
錯誤處理
DB API中定義了一些資料庫操作的錯誤及異常,下表列出了這些錯誤和異常:
異常
描述
Warning 當有嚴重警告時觸發,例如插入數據是被截斷等等。必須是 StandardError 的子類。
Error 警告以外所有其他錯誤類。必須是 StandardError 的子類。
InterfaceError 當有資料庫介面模塊本身的錯誤(而不是資料庫的錯誤)發生時觸發。 必須是Error的子類。
DatabaseError 和資料庫有關的錯誤發生時觸發。 必須是Error的子類。
DataError 當有數據處理時的錯誤發生時觸發,例如:除零錯誤,數據超范圍等等。 必須是DatabaseError的子類。
OperationalError 指非用戶控制的,而是操作資料庫時發生的錯誤。例如:連接意外斷開、 資料庫名未找到、事務處理失敗、內存分配錯誤等等操作資料庫是發生的錯誤。 必須是DatabaseError的子類。
IntegrityError 完整性相關的錯誤,例如外鍵檢查失敗等。必須是DatabaseError子類。
InternalError 資料庫的內部錯誤,例如游標(cursor)失效了、事務同步失敗等等。 必須是DatabaseError子類。
ProgrammingError 程序錯誤,例如數據表(table)沒找到或已存在、SQL語句語法錯誤、 參數數量錯誤等等。必須是DatabaseError的子類。
NotSupportedError 不支持錯誤,指使用了資料庫不支持的函數或API等。例如在連接對象上 使用.rollback()函數,然而資料庫並不支持事務或者事務已關閉。 必須是DatabaseError的子類。
Ⅳ python能創建sql server資料庫,執行create database語句嗎
應該可行吧,畢竟django可以通過第三方支持MSSQL的。
Ⅳ 如何用 Python 實現一個圖資料庫(Graph Database)
本文章是 重寫 500 Lines or Less 系列的其中一篇,目標是重寫 500 Lines or Less 系列的原有項目:Dagoba: an in-memory graph database。
Dagoba 是作者設計用來展示如何從零開始自己實現一個圖資料庫( Graph Database )。該名字似乎來源於作者喜歡的一個樂隊,另一個原因是它的前綴 DAG 也正好是有向無環圖 ( Directed Acyclic Graph ) 的縮寫。本文也沿用了該名稱。
圖是一種常見的數據結構,它將信息描述為若干獨立的節點( vertex ,為了和下文的邊更加對稱,本文中稱為 node ),以及把節點關聯起來的邊( edge )。我們熟悉的鏈表以及多種樹結構可以看作是符合特定規則的圖。圖在路徑選擇、推薦演算法以及神經網路等方面都是重要的核心數據結構。
既然圖的用途如此廣泛,一個重要的問題就是如何存儲它。如果在傳統的關系資料庫中存儲圖,很自然的做法就是為節點和邊各自創建一張表,並用外鍵把它們關聯起來。這樣的話,要查找某人所有的子女,就可以寫下類似下面的查詢:
還好,不算太復雜。但是如果要查找孫輩呢?那恐怕就要使用子查詢或者 CTE(Common Table Expression) 等特殊構造了。再往下想,曾孫輩又該怎麼查詢?孫媳婦呢?
這樣我們會意識到,SQL 作為查詢語言,它只是對二維數據表這種結構而設計的,用它去查詢圖的話非常笨拙,很快會變得極其復雜,也難以擴展。針對圖而言,我們希望有一種更為自然和直觀的查詢語法,類似這樣:
為了高效地存儲和查詢圖這種數據結構,圖資料庫( Graph Database )應運而生。因為和傳統的關系型資料庫存在極大的差異,所以它屬於新型資料庫也就是 NoSql 的一個分支(其他分支包括文檔資料庫、列資料庫等)。圖資料庫的主要代表包括 Neo4J 等。本文介紹的 Dagoba 則是具備圖資料庫核心功能、主要用於教學和演示的一個簡單的圖資料庫。
原文代碼是使用 JavaScript 編寫的,在定義調用介面時大量使用了原型( prototype )這種特有的語言構造。對於其他主流語言的用戶來說,原型的用法多少顯得有些別扭和不自然。
考慮到本系列其他資料庫示例大多是用 Python 實現的,本文也按照傳統,用 Python 重寫了原文的代碼。同樣延續之前的慣例,為了讓讀者更好地理解程序是如何逐步完善的,我們用迭代式的方法完成程序的各個組成部分。
原文在 500lines 系列的 Github 倉庫中只包含了實現代碼,並未包含測試。按照代碼注釋說明,測試程序位於作者的另一個代碼庫中,不過和 500lines 版本的實現似乎略有不同。
本文實現的代碼參考了原作者的測試內容,但跳過了北歐神話這個例子——我承認確實不熟悉這些神祇之間的親緣關系,相信中文背景的讀者們多數也未必了解,雖然作者很喜歡這個例子,想了想還是不要徒增困惑吧。因此本文在編寫測試用例時只參考了原文關於家族親屬的例子,放棄了神話相關的部分,盡管會減少一些趣味性,相信對於入門級的代碼來說這樣也夠用了。
本文實現程序位於代碼庫的 dagoba 目錄下。按照本系列程序的同意規則,要想直接執行各個已完成的步驟,讀者可以在根目錄下的 main.py 找到相應的代碼位置,取消注釋並運行即可。
本程序的所有步驟只需要 Python3 ,測試則使用內置的 unittest , 不需要額外的第三方庫。原則上 Python3.6 以上版本應該都可運行,但我只在 Python3.8.3 環境下完整測試過。
本文實現的程序從最簡單的案例開始,通過每個步驟逐步擴展,最終形成一個完整的程序。這些步驟包括:
接下來依次介紹各個步驟。
回想一下,圖資料庫就是一些點( node )和邊( edge )的集合。現在我們要做出的一個重大決策是如何對節點/邊進行建模。對於邊來說,必須指定它的關聯關系,也就是從哪個節點指向哪個節點。大多數情況下邊是有方向的——父子關系不指明方向可是要亂套的!
考慮到擴展性及通用性問題,我們可以把數據保存為字典( dict ),這樣可以方便地添加用戶需要的任何數據。某些數據是為資料庫內部管理而保留的,為了明確區分,可以這樣約定:以下劃線開頭的特殊欄位由資料庫內部維護,類似於私有成員,用戶不應該自己去修改它們。這也是 Python 社區普遍遵循的約定。
此外,節點和邊存在互相引用的關系。目前我們知道邊會引用到兩端的節點,後面還會看到,為了提高效率,節點也會引用到邊。如果僅僅在內存中維護它們的關系,那麼使用指針訪問是很直觀的,但資料庫必須考慮到序列化到磁碟的問題,這時指針就不再好用了。
為此,最好按照資料庫的一般要求,為每個節點維護一個主鍵( _id ),用主鍵來描述它們之間的關聯關系。
我們第一步要把資料庫的模型建立起來。為了測試目的,我們使用一個最簡單的資料庫模型,它只包含兩個節點和一條邊,如下所示:
按照 TDD 的原則,首先編寫測試:
與原文一樣,我們把資料庫管理介面命名為 Dagoba 。目前,能夠想到的最簡單的測試是確認節點和邊是否已經添加到資料庫中:
assert_item 是一個輔助方法,用於檢查字典是否包含預期的欄位。相信大家都能想到該如何實現,這里就不再列出了,讀者可參考 Github 上的完整源碼。
現在,測試是失敗的。用最簡單的辦法實現資料庫:
需要注意的是,不管添加節點還是查詢,程序都使用了拷貝後的數據副本,而不是直接使用原始數據。為什麼要這樣做?因為字典是可變的,用戶可以在任何時候修改其中的內容,如果資料庫不知道數據已經變化,就很容易發生難以追蹤的一致性問題,最糟糕的情況下會使得數據內容徹底混亂。
拷貝數據可以避免上述問題,代價則是需要佔用更多內存和處理時間。對於資料庫來說,通常查詢次數要遠遠多於修改,所以這個代價是可以接受的。
現在測試應該正常通過了。為了讓它更加完善,我們可以再測試一些邊緣情況,看看資料庫能否正確處理異常數據,比如:
例如,如果用戶嘗試添加重復主鍵,我們預期應拋出 ValueError 異常。因此編寫測試如下:
為了滿足以上測試,代碼需要稍作修改。特別是按照 id 查找主鍵是個常用操作,通過遍歷的方法效率太低了,最好是能夠通過主鍵直接訪問。因此在資料庫中再增加一個字典:
完整代碼請參考 Github 倉庫。
在上個步驟,我們在初始化資料庫時為節點明確指定了主鍵。按照資料庫設計的一般原則,主鍵最好是不具有業務含義的代理主鍵( Surrogate key ),用戶不應該關心它具體的值是什麼,因此讓資料庫去管理主鍵通常是更為合理的。當然,在部分場景下——比如導入外部數據——明確指定主鍵仍然是有用的。
為了同時支持這些要求,我們這樣約定:欄位 _id 表示節點的主鍵,如果用戶指定了該欄位,則使用用戶設置的值(當然,用戶有責任保證它們不會重復);否則,由資料庫自動為它分配一個主鍵。
如果主鍵是資料庫生成的,事先無法預知它的值是什麼,而邊( edge )必須指定它所指向的節點,因此必須在主鍵生成後才能添加。由於這個原因,在動態生成主鍵的情況下,資料庫的初始化會略微復雜一些。還是先寫一個測試:
為支持此功能,我們在資料庫中添加一個內部欄位 _next_id 用於生成主鍵,並讓 add_node 方法返回新生成的主鍵:
接下來,再確認一下邊是否可以正常訪問:
運行測試,一切正常。這個步驟很輕松地完成了,不過兩個測試( DbModelTest 和 PrimaryKeyTest )出現了一些重復代碼,比如 get_item 。我們可以把這些公用代碼提取出來。由於 get_item 內部調用了 TestCase.assertXXX 等方法,看起來應該使用繼承,但從 TestCase 派生基類容易引起一些潛在的問題,所以我轉而使用另一個技巧 Mixin :
實現資料庫模型之後,接下來就要考慮如何查詢它了。
在設計查詢時要考慮幾個問題。對於圖的訪問來說,幾乎總是由某個節點(或符合條件的某一類節點)開始,從與它相鄰的邊跳轉到其他節點,依次類推。所以鏈式調用對查詢來說是一種很自然的風格。舉例來說,要知道 Tom 的孫子養了幾只貓,可以使用類似這樣的查詢:
可以想像,以上每個方法都應該返回符合條件的節點集合。這種實現是很直觀的,不過存在一個潛在的問題:很多時候用戶只需要一小部分結果,如果它總是不計代價地給我們一個巨大的集合,會造成極大的浪費。比如以下查詢:
為了避免不必要的浪費,我們需要另外一種機制,也就是通常所稱的「懶式查詢」或「延遲查詢」。它的基本思想是,當我們調用查詢方法時,它只是把查詢條件記錄下來,而並不立即返回結果,直到明確調用某些方法時才真正去查詢資料庫。
如果讀者比較熟悉流行的 Python ORM,比如 SqlAlchemy 或者 Django ORM 的話,會知道它們幾乎都是懶式查詢的,要調用 list(result) 或者 result[0:10] 這樣的方法才能得到具體的查詢結果。
在 Dagoba 中把觸發查詢的方法定義為 run 。也就是說,以下查詢執行到 run 時才真正去查找數據:
和懶式查詢( Lazy Query )相對應的,直接返回結果的方法一般稱作主動查詢( Eager Query )。主動查詢和懶式查詢的內在查找邏輯基本上是相同的,區別只在於觸發機制不同。由於主動查詢實現起來更加簡單,出錯也更容易排查,因此我們先從主動查詢開始實現。
還是從測試開始。前面測試所用的簡單資料庫數據太少,難以滿足查詢要求,所以這一步先來創建一個更復雜的數據模型:
此關系的復雜之處之一在於反向關聯:如果 A 是 B 的哥哥,那麼 B 就是 A 的弟弟/妹妹,為了查詢到他們彼此之間的關系,正向關聯和反向關聯都需要存在,因此在初始化資料庫時需要定義的邊數量會很多。
當然,父子之間也存在反向關聯的問題,為了讓問題稍微簡化一些,我們目前只需要向下(子孫輩)查找,可以稍微減少一些關聯數量。
因此,我們定義數據模型如下。為了減少重復工作,我們通過 _backward 欄位定義反向關聯,而資料庫內部為了查詢方便,需要把它維護成兩條邊:
然後,測試一個最簡單的查詢,比如查找某人的所有孫輩:
這里 outcome/income 分別表示從某個節點出發、或到達它的節點集合。在原作者的代碼中把上述方法稱為 out/in 。當然這樣看起來更加簡潔,可惜的是 in 在 Python 中是個關鍵字,無法作為函數名。我也考慮過加個下劃線比如 out_.in_ 這種形式,但看起來也有點怪異,權衡之後還是使用了稍微啰嗦一點的名稱。
現在我們可以開始定義查詢介面了。在前面已經說過,我們計劃分別實現兩種查詢,包括主動查詢( Eager Query )以及延遲查詢( Lazy Query )。
它們的內在查詢邏輯是相通的,看起來似乎可以使用繼承。不過遵循 YAGNI 原則,目前先不這樣做,而是只定義兩個新類,在滿足測試的基礎上不斷擴展。以後我們會看到,與繼承相比,把共同的邏輯放到資料庫本身其實是更為合理的。
接下來實現訪問節點的方法。由於 EagerQuery 調用查詢方法會立即返回結果,我們把結果記錄在 _result 內部欄位中。雖然 node 方法只返回單個結果,但考慮到其他查詢方法幾乎都是返回集合,為統一起見,讓它也返回集合,這樣可以避免同時支持集合與單結果的分支處理,讓代碼更加簡潔、不容易出錯。此外,如果查詢對象不存在的話,我們只返回空集合,並不視為一個錯誤。
查詢輸入/輸出節點的方法實現類似這樣:
查找節點的核心邏輯在資料庫本身定義:
以上使用了內部定義的一些輔助查詢方法。用類似的邏輯再定義 income ,它們的實現都很簡單,讀者可以直接參考源碼,此處不再贅述。
在此步驟的最後,我們再實現一個優化。當多次調用查詢方法後,結果可能會返回重復的數據,很多時候這是不必要的。就像關系資料庫通常支持 unique/distinct 一樣,我們也希望 Dagoba 能夠過濾重復的數據。
假設我們要查詢某人所有孩子的祖父,顯然不管有多少孩子,他們的祖父應該是同一個人。因此編寫測試如下:
現在來實現 unique 。我們只要按照主鍵把重復數據去掉即可:
在上個步驟,初始化資料庫指定了雙向關聯,但並未測試它們。因為我們還沒有編寫代碼去支持它們,現在增加一個測試,它應該是失敗的:
運行測試,的確失敗了。我們看看要如何支持它。回想一下,當從邊查找節點時,使用的是以下方法:
這里也有一個潛在的問題:調用 self.edges 意味著遍歷所有邊,當資料庫內容較多時,這是巨大的浪費。為了提高性能,我們可以把與節點相關的邊記錄在節點本身,這樣要查找邊只要看節點本身即可。在初始化時定義出入邊的集合:
在添加邊時,我們要同時把它們對應的關系同時更新到節點,此外還要維護反向關聯。這涉及對字典內容的部分復制,先編寫一個輔助方法:
然後,將添加邊的實現修改如下:
這里的代碼同時添加正向關聯和反向關聯。有的朋友可能會注意到代碼略有重復,是的,但是重復僅出現在該函數內部,本著「三則重構」的原則,暫時不去提取代碼。
實現之後,前面的測試就可以正常通過了。
在這個步驟中,我們來實現延遲查詢( Lazy Query )。
延遲查詢的要求是,當調用查詢方法時並不立即執行,而是推遲到調用特定方法,比如 run 時才執行整個查詢,返回結果。
延遲查詢的實現要比主動查詢復雜一些。為了實現延遲查詢,查詢方法的實現不能直接返回結果,而是記錄要執行的動作以及傳入的參數,到調用 run 時再依次執行前面記錄下來的內容。
如果你去看作者的實現,會發現他是用一個數據結構記錄執行操作和參數,此外還有一部分邏輯用來分派對每種結構要執行的動作。這樣當然是可行的,但數據處理和分派部分的實現會比較復雜,也容易出錯。
本文的實現則選擇了另外一種不同的方法:使用 Python 的內部函數機制,把一連串查詢變換成一組函數,每個函數取上個函數的執行結果作為輸入,最後一個函數的輸出就是整個查詢的結果。由於內部函數同時也是閉包,盡管每個查詢的參數形式各不相同,但是它們都可以被閉包「捕獲」而成為內部變數,所以這些內部函數可以採用統一的形式,無需再針對每種查詢設計額外的數據結構,因而執行過程得到了很大程度的簡化。
首先還是來編寫測試。 LazyQueryTest 和 EagerQueryTest 測試用例幾乎是完全相同的(是的,兩種查詢只在於內部實現機制不同,它們的調用介面幾乎是完全一致的)。
因此我們可以把 EagerQueryTest 的測試原樣不變拷貝到 LazyQueryTest 中。當然拷貝粘貼不是個好注意,對於比較冗長而固定的初始化部分,我們可以把它提取出來作為兩個測試共享的公共函數。讀者可參考代碼中的 step04_lazy_query/tests/test_lazy_query.py 部分。
程序把查詢函數的串列執行稱為管道( pipeline ),用一個變數來記錄它:
然後依次實現各個調用介面。每種介面的實現都是類似的:用內部函數執行真正的查詢邏輯,再把這個函數添加到 pipeline 調用鏈中。比如 node 的實現類似下面:
其他介面的實現也與此類似。最後, run 函數負責執行所有查詢,返回最終結果;
完成上述實現後執行測試,確保我們的實現是正確的。
在前面我們說過,延遲查詢與主動查詢相比,最大的優勢是對於許多查詢可以按需要訪問,不需要每個步驟都返回完整結果,從而提高性能,節約查詢時間。比如說,對於下面的查詢:
以上查詢的意思是從孫輩中找到一個符合條件的節點即可。對該查詢而言,主動查詢會在調用 outcome('son') 時就遍歷所有節點,哪怕最後一步只需要第一個結果。而延遲查詢為了提高效率,應在找到符合條件的結果後立即停止。
目前我們尚未實現 take 方法。老規矩,先添加測試:
主動查詢的 take 實現比較簡單,我們只要從結果中返回前 n 條記錄:
延遲查詢的實現要復雜一些。為了避免不必要的查找,返回結果不應該是完整的列表( list ),而應該是個按需返回的可迭代對象,我們用內置函數 next 來依次返回前 n 個結果:
寫完後運行測試,確保它們是正確的。
從外部介面看,主動查詢和延遲查詢幾乎是完全相同的,所以用單純的數據測試很難確認後者的效率一定比前者高,用訪問時間來測試也並不可靠。為了測試效率,我們引入一個節點訪問次數的概念,如果延遲查詢效率更高的話,那麼它應該比主動查詢訪問節點的次數更少。
為此,編寫如下測試:
我們為 Dagoba 類添加一個成員來記錄總的節點訪問次數,以及兩個輔助方法,分別用於獲取和重置訪問次數:
然後瀏覽代碼,查找修改點。增加計數主要在從邊查找節點的時候,因此修改部分如下:
此外還有 income/outcome 方法,修改都很簡單,這里就不再列出。
實現後再次運行測試。測試通過,表明延遲查詢確實在效率上優於主動查詢。
不像關系資料庫的結構那樣固定,圖的形式可以千變萬化,查詢機制也必須足夠靈活。從原理上講,所有查詢無非是從某個節點出發按照特定方向搜索,因此用 node/income/outcome 這三個方法幾乎可以組合出任意所需的查詢。
但對於復雜查詢,寫出的代碼有時會顯得較為瑣碎和冗長,對於特定領域來說,往往存在更為簡潔的名稱,例如:母親的兄弟可簡稱為舅舅。對於這些場景,如果能夠類似 DSL (領域特定語言)那樣允許用戶根據專業要求自行擴展,從而簡化查詢,方便閱讀,無疑會更為友好。
如果讀者去看原作者的實現,會發現他是用一種特殊語法 addAlias 來定義自己想要的查詢,調用方法時再進行查詢以確定要執行的內容,其介面和內部實現都是相當復雜的。
而我希望有更簡單的方法來實現這一點。所幸 Python 是一種高度動態的語言,允許在運行時向類中增加新的成員,因此做到這一點可能比預想的還要簡單。
為了驗證這一點,編寫測試如下:
無需 Dagoba 的實現做任何改動,測試就可以通過了!其實我們要做的就是動態添加一個自定義的成員函數,按照 Python 對象機制的要求,成員函數的第一個成員應該是名為 self 的參數,但這里已經是在 UnitTest 的內部,為了和測試類本身的 self 相區分,新函數的參數增加了一個下劃線。
此外,函數應返回其所屬的對象,這是為了鏈式調用所要求的。我們看到,動態語言的靈活性使得添加新語法變得非常簡單。
到此,一個初具規模的圖資料庫就形成了。
和原文相比,本文還缺少一些內容,比如如何將資料庫序列化到磁碟。不過相信讀者都看到了,我們的資料庫內部結構基本上是簡單的原生數據結構(列表+字典),因此序列化無論用 pickle 或是 JSON 之類方法都應該是相當簡單的。有興趣的讀者可以自行完成它們。
我們的圖資料庫實現為了提高查詢性能,在節點內部存儲了邊的指針(或者說引用)。這樣做的好處是,無論資料庫有多大,從一個節點到相鄰節點的訪問是常數時間,因此數據訪問的效率非常高。
但一個潛在的問題是,如果資料庫規模非常大,已經無法整個放在內存中,或者出於安全性等原因要實現分布式訪問的話,那麼指針就無法使用了,必須要考慮其他機制來解決這個問題。分布式資料庫無論採用何種數據模型都是一個棘手的問題,在本文中我們沒有涉及。有興趣的讀者也可以考慮 500lines 系列中關於分布式和集群演算法的其他一些文章。
本文的實現和系列中其他資料庫類似,採用 Python 作為實現語言,而原作者使用的是 JavaScript ,這應該和作者的背景有關。我相信對於大多數開發者來說, Python 的對象機制比 JavaScript 基於原型的語法應該是更容易閱讀和理解的。
當然,原作者的版本比本文版本在實現上其實是更為完善的,靈活性也更好。如果想要更為優雅的實現,我們可以考慮使用 Python 元編程,那樣會更接近於作者的實現,但也會讓程序的復雜性大為增加。如果讀者有興趣,不妨對照著去讀讀原作者的版本。
Ⅵ 掌握Python 操作 MySQL 資料庫
本文Python 操作 MySQL 資料庫需要是使用到 PyMySQL 驅動
Python 操作 MySQL 前提是要安裝好 MySQL 資料庫並能正常連接使用,安裝步驟詳見下文。
注意: 安裝過程我們需要通過開啟管理員許可權來安裝,否則會由於許可權不足導致無法安裝。
首先需要先下載 MySQL 安裝包, 官網下載地址 下載對應版本即可,或直接在網上拉取並安裝:
許可權設置:
初始化 MySQL:
啟動 MySQL:
查看 MySQL 運行狀態:
Mysql安裝成功後,默認的root用戶密碼為空,你可以使用以下命令來創建root用戶的密碼:
登陸:
創建資料庫:
查看資料庫:
PyMySQL 模塊使用 pip命令進行安裝:
假如系統不支持 pip 命令,可以使用以下方式安裝:
pymysql .connect 函數:連接上資料庫
輸出結果顯示如下:表面資料庫連接成功
使用 pymysql 的 connect() 方法連接資料庫,connect 參數解釋如下:
conn.cursor():獲取游標
如果要操作資料庫,光連接數據是不夠的,咱們必須拿到操作資料庫的游標,才能進行後續的操作,游標的主要作用是用來接收資料庫操作後的返回結果,比如讀取數據、添加數據。通過獲取到的資料庫連接實例 conn 下的 cursor() 方法來創建游標,實例如下:
輸出結果為:
cursor 返回一個游標實例對象,其中包含了很多操作數據的方法,如執行sql語句,sql 執行命令: execute() 和 executemany()
execute(query,args=None):
executemany(query,args=None):
其他游標對象如下表:
完整資料庫連接操作實例如下:
以上結果輸出為:
創建表代碼如下:
如下所示資料庫表創建成功:
插入數據實現代碼:
插入數據結果:
Python查詢Mysql使用 fetchone() 方法獲取單條數據, 使用fetchall() 方法獲取多條數據。
查詢數據代碼如下:
輸出結果:
DB API中定義了一些資料庫操作的錯誤及異常,下表列出了這些錯誤和異常:
本文給大家介紹 Python 如何連接 Mysql 進行數據的增刪改查操作,文章通過簡潔的代碼方式進行示例演示,給使用 Python 操作 Mysql 的工程師提供支撐。