More ASE Quiz questions: 2010 - 2009 - 2008 - 2007 - 2006 - 2005 - 2004 - 2003 - 2002 - 2001
November 2006
In ASE, it's quite easy to write a custom string to the ASE errorlog from SQL. An application is the errorlog checking tool described here.
I have received numerous requests from RepServer DBAs to make this tool work for RepServer errorlogs as well. Which brings me to this month's question: how can you write an arbitrary string to the RepServer errorlog in a programmatically controlled way?
Answer:
It can be done, but the method depends on the RepServer version. First, RepServer 15.0 has a new command admin echo, "your-string", with_log which writes the string your-string to the RepServer errorlog:
1> admin who, echo "Hello", with_log
2> go
The errorlog will then contain the following line:
I. 2006/11/25 13:49:13. User 'sa' writes: 'Hello'
NB: without the with_log option (which is the part that's new in RS 15.0), the string is echoed to the client only.
In pre-RS 15 things are a bit more tricky, since there is no supported command to write a particular string to the errorlog. However, it is still possible -- and although there's no risk, be warned ahead that this trick is unlikely to win any prizes for elegance.
In RS 12.6 or before, writing your own custom string to the RS errorlog in a controlled way requires the following steps:
- Create a connection to a non-existing database, for example, 'XXX.XXX'. In the create connection command, specify a login name that (i) marks the unique placeholder string you're trying to write to the RS errorlog, and (ii) is guaranteed not to exist if 'XXX' would happen to be an existing dataserver, e.g.:
create connection to XXX.XXX
set error class to rs_sqlserver_error_class
set function string class to rs_sqlserver_function_class
set username to ___my_unique_string___
set password to doesntmatter
with dsi_suspended
- When you want to write your placeholder string to the RS errorlog, run these commands:
resume connection to XXX.XXX
go
suspend connection to XXX.XXX
go
Although the login into XXX.XXX will fail (that's the idea!), the name of the login -i.e. your placeholder string- is written to the RS errorlog as a result:
I. 2006/11/25 13:53:11. Trying to connect to server 'XXX'
as user '___my_unique_string___' ......
- Note that it is not a good idea to use the dataserver name of database name as your placeholder string. When starting RepServer, the name of the connection will be written to the RS errorlog as part of a warning that teh connection is down.
NB: the errorlog checking tool has been modified to work with RepServer 15.0. If you want to get it working with RS pre-15, you'll have to hack it around yourself a bit.
October 2006
As we all know, when writing a SQL statement it doesn't really matter in what order you list the tables in the from-clause; this has no impact on the query's result set.
However, there happens to be a situation where the order of the various parts in the from-clause of a select statement does actually make a difference for syntactical correctness. Do you know which?
Answer:
This particular case concerns the 'on'-clause in the ANSI-style join syntax. The on-clause is part of the from-clause, and its predicates define the join conditions between two joined tables. Before getting into the difficult part, here's an example of a three-table join in ANSI syntax:
-- define the tables
create table t1 ( a int, b int)
create table t2 ( c int, d int)
create table t3 ( e int, f int)
go
-- this query works fine
select *
from t1
inner join t2 on t1.a = t2.c
inner join t3 on t2.d = t3.f
For reference, the classic T-SQL equivalent of this statement would be as follows:
select *
from t1, t2, t3
where t1.a = t2.c
and t2.d = t3.f
Now, the ANSI join syntax requires that the tables referred in the on-clause have already shown up with an inner join operator. For example, if you swap 't2' and 't3' in the above query, you'll get error 11040. This is because the join predicate refers to 't2.c', while that table doesn't actually appear until after the next 'inner join' operator:
-- this query runs into syntax error 11040
select *
from t1
inner join t3 on t1.a = t2.c
inner join t2 on t2.d = t3.f
go
Msg 11040, Level 16, State 1
Server 'PROD', Line 3
Adaptive Server cannot perform the requested action
because column 'c' is not within the scope of the
joined table expression. Check your command for
missing or incorrect database objects, variable
names, and/or input data.
This behaviour may be somewhat surprising when you've never seen it before, since we've all been raised with the understanding that the order of the various parts of a SQL statement's from-clause doesn't make a difference. In the case of the ANSI syntax however, it does, and there's no way around it. Fortunately ASE will let you know when you got it wrong...
August 2006
We're always looking for ways to make queries run faster, or keep the response time below an certain maximum.
But what if you'd want to do the reverse, and actually want to make a
query take at least some minimum amount of time to respond?
How would you do that?
(NB: this is not as crazy an idea as it may sound -- see the answer).
Answer:
If you'd want to create some kind of delay when querying a table, you can do this by using a proxy table. The basic idea is this:
- create a proxy table, mapping to the real table
- use a dedicated login for the proxy table's CIS access (i.e. create an external login)
- set up a login trigger for that login, which contains a waitfor delay statement
- when querying the proxy table, the waitfor delay in the login trigger kicks in, thus causing a delay in query execution
The basic idea is quite simple, but there are some things to be aware of, so keep reading.
Before getting into the details, let's take a step back: why would you want to do a thing like this in the first place?
Well, it's not as outlandish as it may seem, and in fact I'm sure many readers will recognise the following situation. Imagine you have a new system, with still only a few users and little data: consequently, queries will be very fast. When the system grows to its expected size, volumes increase, and queries get slower. This is all natural from a DBA's perspective, but it may be something end-users will not like or understand (or both), so complaints may result ('but it was so fast first and now it's slow...'). One could argue this is a typical case of mismanaged end-user expectations, and from that perspective, there would have been some sense in making sure those initial response times had not been as fast as they were; hence, some kind of query slowdown mechanism could have been helpful (I'll leave it to everyone's own imagination how to take this one step further so as to magically 'improve' performance later -- but for ethic's sake, I won't recommend doing that).
Anyway, against this background, let's look at how we can create such a query slowdown:
First, let's look at the case where a proxy table is mapped to the real table. First you need a 'loopback' servername alias (if you use the MDA tables, you already have this):
sp_addserver loopback, null, @@servername
go
-- For some reason, you need to make sure the statement cache
-- is disabled, or things won't work...
sp_configure 'statement cache', 0
go
Now, create the proxy table. Rename the real table (here, 'my_table'), and create a proxy table with the same name as the original real table:
use my_db
go
sp_rename my_table, my_table_renamed
go
create proxy_table my_table at 'loopback.my_db..my_table_renamed'
go
grant all on my_table to public
go
Now, define a dedicated login for all CIS access to 'loopback':
use master
go
sp_addlogin loopback_login, mysecretcode, my_db
go
sp_addexternlogin loopback, my_login, loopback_login, mysecretcode
-- check the docs for details about sp_addexternlogin !
go
use my_db
go
sp_adduser loopback_login
go
grant all on my_table_renamed to loopback_login
go
At this point, check that queries against the proxy table return the actual table's data.
Finally, set up the login trigger:
use my_db
go
create proc my_logintrig
as
if suser_name() = 'loopback_login'
waitfor delay '00:00:10' -- perhaps a bit much...
go
grant all on my_logintrig to public
go
sp_modifylogin loopback_login, 'login script' , my_logintrig
go
Now, do a 'select * from my_table' and see how it takes at least 10 seconds (adjust the timeout as needed).
There are some things to be aware of. For example, you'll see that a second access to my_table does not wait for 10 seconds, but returns immediately. This is because the CIS connection is maintained after the first access (until the parent session disconnects), so the login triggger doesn't kick in for subsequent accesses.
There are ways to work around this though. For example, in 12.5.3 ESD#3 or later, enabling traceflag 11231 disconnects the CIS connection after each access, so a new login is performed every time. Note that this makes all CIS operations slower server-wide (but that was our intention, remember?). However, if you're using MDA tables in pre-15.0 ESD#2, be aware that this has an impact on the way data is returned from the historical MDA tables like monSysSQLText since the connection's context is not maintained between subsequent MDA queries (in 15.0 ESD#2 and later, this problem doesn't apply anymore).
In 12.5.0.3 or later, you can also use Dave Wein's sp__idleReaper to automatically kill sessions (CIS sessions, in this case: you can check for those using 'loopback_login') that have been idle for more than, say, a few seconds (although there is a risk that a new query is just being submitted while you do that...).
Lastly, in 15.0.1 (expected September 2006), a new configuration parameter is available: 'cis idle connection timeout' defines the number of seconds after which an idle CIS connection is automatically closed.
Note that the last two options won't work when the table is continuously queried (i.e. more frequent than the idle timeout setting).
Finally, an alternative approach is possible, where the proxy table is mapped to stored procedure. This procedure performs the query against the real table, after doing a waitfor delay. This is simpler because no dedicated external login is needed here; however, this works for read-only access only. Another difference is that the full column list must be specified explicitly when creating the proxy table:
sp_dropexternlogin loopback, NULL
go
use my_db
go
create proc my_proc
as
waitfor delay '00:00:10'
select * from my_table_renamed
go
create existing table my_table2 (...all columns...)
external procedure at 'loopback.my_db..my_proc'
go
grant all on my_proc to public
go
Now, you can select from my_table2.
Of course, when all your SQL is wrapped in stored procedures, you could simply build the delay into each stored procedure itself, without the need to mess around with login triggers.
July 2006
Some time ago, I was installing an EBF in my ASE server. As you know, part of this procedure is to run the installmaster, installsecurity, installmontables (etc) scripts after booting the ASE server with the new EBF.
Since I was in a rush, I kicked off two parallel isql sessions, one doing installmaster and one doing installsecurity, since this seemed a quicker way of completing the job.
Later, various system stored procedures (like sp_poolconfig and sp_modifylogin, but many others too) produced permission errors when I ran it. Why was that?
Answer:
System stored procedures often update system tables. As you know, when updating system tables manually, this requires the configuration parameter 'allow updates' to be enabled. However, the system stored procedures can also perform these updates when this option is disabled. The reason is that these procedures are created while 'allow updates' is enabled: the permission to update system tables is then 'frozen' into stored procedures that are created while this parameter is enabled.
To achieve this effect, the installmaster script (and others, like installsecurity) first enable 'allow updates' at the start of the script; at the end, they disabled it again.
What happened in my case was this: I kicked off installmaster and then installsecurity, so that they ran in parallel. Since installsecurity is much shorter than installmaster, it finished first, and in doing so it disabled 'allow updates'. Now, since this is a server-wide setting, any stored procs that were created by the still-running installmaster from that point onwards did not have the permission to update system tables frozen into it... and this leads to run-time permission errors when executing such a procedure.
Therefore, even though this may not be explicitly stated anywhere, always run the install* scripts serially.
Lesson learned: When in a rush, doing things differently than usual is rarely a good idea...
More ASE Quiz questions: 2010 - 2009 - 2008 - 2007 - 2006 - 2005 - 2004 - 2003 - 2002 - 2001
|