在 SQLAlchemy 中使用 Oracle 服务名称
Posted
技术标签:
【中文标题】在 SQLAlchemy 中使用 Oracle 服务名称【英文标题】:Using Oracle Service Names with SQLAlchemy 【发布时间】:2012-12-17 22:23:28 【问题描述】:我在使用服务名称通过 SQLAlchemy 连接到 Oracle 架构时遇到了一个令人讨厌的小问题。这是我作为脚本的代码。 (出于安全原因,尖括号之间的项目是实际值的占位符)
from sqlalchemy import create_engine
if __name__ == "__main__":
engine = create_engine("oracle+cx_oracle://<username>:<password>@<host>/devdb")
result = engine.execute("create table test_table (id NUMBER(6), name VARCHAR2(15) not NULL)")
result = engine.execute("drop table test_table")
其中“devdb”是服务名称而不是 SID。运行此脚本的结果是堆栈跟踪。
(oracle-test)[1]jgoodell@jgoodell-MBP:python$ python example.py
Traceback (most recent call last):
File "example.py", line 8, in <module>
result = engine.execute("create table test_table (id NUMBER(6), name VARCHAR2(15) not NULL)")
File "/Users/jgoodell/.virtualenvs/oracle-test/lib/python2.6/site-packages/sqlalchemy/engine/base.py", line 1621, in execute
connection = self.contextual_connect(close_with_result=True)
File "/Users/jgoodell/.virtualenvs/oracle-test/lib/python2.6/site-packages/sqlalchemy/engine/base.py", line 1669, in contextual_connect
self.pool.connect(),
File "/Users/jgoodell/.virtualenvs/oracle-test/lib/python2.6/site-packages/sqlalchemy/pool.py", line 272, in connect
return _ConnectionFairy(self).checkout()
File "/Users/jgoodell/.virtualenvs/oracle-test/lib/python2.6/site-packages/sqlalchemy/pool.py", line 425, in __init__
rec = self._connection_record = pool._do_get()
File "/Users/jgoodell/.virtualenvs/oracle-test/lib/python2.6/site-packages/sqlalchemy/pool.py", line 777, in _do_get
con = self._create_connection()
File "/Users/jgoodell/.virtualenvs/oracle-test/lib/python2.6/site-packages/sqlalchemy/pool.py", line 225, in _create_connection
return _ConnectionRecord(self)
File "/Users/jgoodell/.virtualenvs/oracle-test/lib/python2.6/site-packages/sqlalchemy/pool.py", line 318, in __init__
self.connection = self.__connect()
File "/Users/jgoodell/.virtualenvs/oracle-test/lib/python2.6/site-packages/sqlalchemy/pool.py", line 368, in __connect
connection = self.__pool._creator()
File "/Users/jgoodell/.virtualenvs/oracle-test/lib/python2.6/site-packages/sqlalchemy/engine/strategies.py", line 80, in connect
return dialect.connect(*cargs, **cparams)
File "/Users/jgoodell/.virtualenvs/oracle-test/lib/python2.6/site-packages/sqlalchemy/engine/default.py", line 279, in connect
return self.dbapi.connect(*cargs, **cparams)
sqlalchemy.exc.DatabaseError: (DatabaseError) ORA-12505: TNS:listener does not currently know of SID given in connect descriptor
None None
如果 'devdb' 是一个 SID 而不是一个服务名称,这个例子就可以正常工作,我一直在尝试连接字符串的不同排列,但没有找到任何有效的方法。 SQLAlchemy 文档中似乎也没有任何内容明确解释如何为 Oracle 连接处理 SID 的 verses 服务名称。
【问题讨论】:
@//host_name:port_number/service_name 使用 @//host_name:port_number/service_name 会引发 (DatabaseError) ORA-12541: TNS:no listener 【参考方案1】:我找到了答案,你必须使用与 tnsnames.ora 文件中相同的连接字符串,在 '@" 之后的连接字符串中,就像这样
from sqlalchemy import create_engine
if __name__ == "__main__":
engine = create_engine("oracle+cx_oracle://<username>:<password>@(DESCRIPTION = (LOAD_BALANCE=on) (FAILOVER=ON) (ADDRESS = (PROTOCOL = TCP)(HOST = <host>)(PORT = 1521)) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = devdb)))")
result = engine.execute("create table test_table (id NUMBER(6), name VARCHAR2(15) not NULL)")
result = engine.execute("drop table test_table")
这个例子运行得很好,你可以注释掉drop语句并检查数据库是否创建了表。
【讨论】:
【参考方案2】:import cx_Oracle
dsnStr = cx_Oracle.makedsn('myhost','port','MYSERVICENAME')
connect_str = 'oracle://user:password@' + dsnStr.replace('SID', 'SERVICE_NAME')
makedns 会像这样创建一个 TNS:
(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=myhost)(PORT=1530)))(CONNECT_DATA=(SID=MYSERVICENAME)))
用“SERVICE_TYPE”替换“SID”让它对我有用。
如果您使用的是烧瓶、sqlalchemy 和 oracle:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import cx_Oracle
app = Flask(__name__)
dnsStr = cx_Oracle.makedsn('my.host.com', '1530', 'my.service.name')
dnsStr = dnsString.replace('SID', 'SERVICE_NAME')
app.config['SQLALCHEMY_DATABASE_URI'] = 'oracle://myschema:mypassword@' + dnsStr
db = SQLAlchemy(app)
【讨论】:
你可以直接做cx_Oracle.makedns('myhost','port',service_name='MYSERVICENAME')
而不是替换dnsString.replace('SID', 'SERVICE_NAME')
...【参考方案3】:
模块 sqlalchemy 现在可以处理 oracle service_names。看看:
from sqlalchemy.engine import create_engine
DIALECT = 'oracle'
SQL_DRIVER = 'cx_oracle'
USERNAME = 'your_username' #enter your username
PASSWORD = 'your_password' #enter your password
HOST = 'subdomain.domain.tld' #enter the oracle db host url
PORT = 1521 # enter the oracle port number
SERVICE = 'your_oracle_service_name' # enter the oracle db service name
ENGINE_PATH_WIN_AUTH = DIALECT + '+' + SQL_DRIVER + '://' + USERNAME + ':' + PASSWORD +'@' + HOST + ':' + str(PORT) + '/?service_name=' + SERVICE
engine = create_engine(ENGINE_PATH_WIN_AUTH)
#test query
import pandas as pd
test_df = pd.read_sql_query('SELECT * FROM global_name', engine)
【讨论】:
为我工作。两天的搜索,只有一个解决方案。【参考方案4】:cx_Oracle 支持将 service_name 传递给 makedsn 函数。
http://cx-oracle.sourceforge.net/html/module.html?highlight=makedsn#cx_Oracle.makedsn
如果 create_engine() API 将 service_name 传递给它对 makedsn 进行的底层调用,那就太好了……像这样:
oracle = create_engine('oracle://user:pw@host:port', service_name='myservice')
TypeError: Invalid argument(s) 'service_name' sent to create_engine(), using configuration OracleDialect_cx_oracle/QueuePool/Engine.
Please check that the keyword arguments are appropriate for this combination of components.
【讨论】:
我在create_engine()
github.com/zzzeek/sqlalchemy/pull/152#issuecomment-299859217询问了对service_name
的支持以上是关于在 SQLAlchemy 中使用 Oracle 服务名称的主要内容,如果未能解决你的问题,请参考以下文章
如何在 SQLAlchemy 中查找列使用的 Oracle 序列的名称?
SQLAlchemy 在 Oracle DB 中批量插入 blob 数据